-
Notifications
You must be signed in to change notification settings - Fork 357
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add opentelemetry-jaeger-tracing example
- Loading branch information
Showing
5 changed files
with
727 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# opentelemetry-jaeger-tracing | ||
|
||
This is a runnable example of req, which uses the built-in tiny github sdk built on req to query and display the information of the specified user. | ||
|
||
Best of all, it integrates seamlessly with jaeger tracing and is very easy to extend. | ||
|
||
## How to run | ||
|
||
First, use `docker` or `podman` to start a test jeager container (see jeager official doc: [ Getting Started](https://www.jaegertracing.io/docs/1.37/getting-started/#all-in-one)). | ||
|
||
Then, run example: | ||
|
||
```bash | ||
go run . | ||
``` | ||
```txt | ||
Please give a github username: | ||
``` | ||
|
||
Input a github username, e.g. `imroc`: | ||
|
||
```bash | ||
$ go run . | ||
Please give a github username: imroc | ||
The moust popular repo of roc (https://imroc.cc) is req, which have 2500 stars | ||
``` | ||
|
||
Then enter the Jaeger UI with browser (`http://127.0.0.1:16686/`), checkout the tracing details. | ||
|
||
Run example again, try to input some username that doesn't exist, and check the error log in Jaeger UI. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package github | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/imroc/req/v3" | ||
"go.opentelemetry.io/otel/attribute" | ||
"go.opentelemetry.io/otel/codes" | ||
"go.opentelemetry.io/otel/trace" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// Client is the go client for GitHub API. | ||
type Client struct { | ||
*req.Client | ||
} | ||
|
||
// NewClient create a GitHub client. | ||
func NewClient() *Client { | ||
c := req.C(). | ||
// All GitHub API requests need this header. | ||
SetCommonHeader("Accept", "application/vnd.github.v3+json"). | ||
// All GitHub API requests use the same base URL. | ||
SetBaseURL("https://api.github.com"). | ||
// EnableDump at the request level in request middleware which dump content into | ||
// memory (not print to stdout), we can record dump content only when unexpected | ||
// exception occurs, it is helpful to troubleshoot problems in production. | ||
OnBeforeRequest(func(c *req.Client, r *req.Request) error { | ||
if r.RetryAttempt > 0 { // Ignore on retry. | ||
return nil | ||
} | ||
r.EnableDump() | ||
return nil | ||
}). | ||
// Unmarshal response body into an APIError struct when status >= 400. | ||
SetCommonError(&APIError{}). | ||
// Handle common exceptions in response middleware. | ||
OnAfterResponse(func(client *req.Client, resp *req.Response) error { | ||
if resp.Err != nil { // Ignore if there is an underlying error. | ||
return nil | ||
} | ||
if err, ok := resp.Error().(*APIError); ok { // Server returns an error message. | ||
// Convert it to human-readable go error. | ||
resp.Err = err | ||
return nil | ||
} | ||
// Corner case: neither an error response nor a success response, | ||
// dump content to help troubleshoot. | ||
if !resp.IsSuccess() { | ||
return fmt.Errorf("bad response, raw dump:\n%s", resp.Dump()) | ||
} | ||
return nil | ||
}) | ||
|
||
return &Client{ | ||
Client: c, | ||
} | ||
} | ||
|
||
// LoginWithToken login with GitHub personal access token. | ||
// GitHub API doc: https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso | ||
func (c *Client) LoginWithToken(token string) *Client { | ||
c.SetCommonHeader("Authorization", "token "+token) | ||
return c | ||
} | ||
|
||
// APIError represents the error message that GitHub API returns. | ||
// GitHub API doc: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#client-errors | ||
type APIError struct { | ||
Message string `json:"message"` | ||
DocumentationUrl string `json:"documentation_url,omitempty"` | ||
Errors []struct { | ||
Resource string `json:"resource"` | ||
Field string `json:"field"` | ||
Code string `json:"code"` | ||
} `json:"errors,omitempty"` | ||
} | ||
|
||
// Error convert APIError to a human readable error and return. | ||
func (e *APIError) Error() string { | ||
msg := fmt.Sprintf("API error: %s", e.Message) | ||
if e.DocumentationUrl != "" { | ||
return fmt.Sprintf("%s (see doc %s)", msg, e.DocumentationUrl) | ||
} | ||
if len(e.Errors) == 0 { | ||
return msg | ||
} | ||
errs := []string{} | ||
for _, err := range e.Errors { | ||
errs = append(errs, fmt.Sprintf("resource:%s field:%s code:%s", err.Resource, err.Field, err.Code)) | ||
} | ||
return fmt.Sprintf("%s (%s)", msg, strings.Join(errs, " | ")) | ||
} | ||
|
||
// SetDebug enable debug if set to true, disable debug if set to false. | ||
func (c *Client) SetDebug(enable bool) *Client { | ||
if enable { | ||
c.EnableDebugLog() | ||
c.EnableDumpAll() | ||
} else { | ||
c.DisableDebugLog() | ||
c.DisableDumpAll() | ||
} | ||
return c | ||
} | ||
|
||
type apiNameType int | ||
|
||
const apiNameKey apiNameType = iota | ||
|
||
// SetTracer set the tracer of opentelemetry. | ||
func (c *Client) SetTracer(tracer trace.Tracer) { | ||
c.WrapRoundTripFunc(func(rt req.RoundTripper) req.RoundTripFunc { | ||
return func(r *req.Request) (resp *req.Response, err error) { | ||
ctx := r.Context() | ||
spanName := ctx.Value(apiNameKey).(string) | ||
_, span := tracer.Start(r.Context(), spanName) | ||
defer span.End() | ||
span.SetAttributes( | ||
attribute.String("http.url", r.URL.String()), | ||
attribute.String("http.method", r.Method), | ||
attribute.String("http.req.header", r.HeaderToString()), | ||
) | ||
if len(r.Body) > 0 { | ||
span.SetAttributes( | ||
attribute.String("http.req.body", string(r.Body)), | ||
) | ||
} | ||
resp, err = rt.RoundTrip(r) | ||
if err != nil { | ||
span.RecordError(err) | ||
span.SetStatus(codes.Error, err.Error()) | ||
return | ||
} | ||
span.SetAttributes( | ||
attribute.Int("http.status_code", resp.StatusCode), | ||
attribute.String("http.resp.header", resp.HeaderToString()), | ||
attribute.String("resp.resp.body", resp.String()), | ||
) | ||
return | ||
} | ||
}) | ||
} | ||
|
||
func withAPIName(ctx context.Context, name string) context.Context { | ||
if ctx == nil { | ||
ctx = context.Background() | ||
} | ||
return context.WithValue(ctx, apiNameKey, name) | ||
} | ||
|
||
type UserProfile struct { | ||
Name string `json:"name"` | ||
Blog string `json:"blog"` | ||
} | ||
|
||
// GetUserProfile returns the user profile for the specified user. | ||
// Github API doc: https://docs.github.com/en/rest/users/users#get-a-user | ||
func (c *Client) GetUserProfile(ctx context.Context, username string) (user *UserProfile, err error) { | ||
err = c.Get("/users/{username}"). | ||
SetPathParam("username", username). | ||
Do(withAPIName(ctx, "GetUserProfile")). | ||
Into(&user) | ||
return | ||
} | ||
|
||
type Repo struct { | ||
Name string `json:"name"` | ||
Star int `json:"stargazers_count"` | ||
} | ||
|
||
// ListUserRepo returns a list of public repositories for the specified user | ||
// Github API doc: https://docs.github.com/en/rest/repos/repos#list-repositories-for-a-user | ||
func (c *Client) ListUserRepo(ctx context.Context, username string, page int) (repos []*Repo, err error) { | ||
err = c.Get("/users/{username}/repos"). | ||
SetQueryParamsAnyType(map[string]any{ | ||
"type": "owner", | ||
"page": strconv.Itoa(page), | ||
"per_page": "100", | ||
"sort": "updated", | ||
"direction": "desc", | ||
}). | ||
SetPathParam("username", username). | ||
Do(withAPIName(ctx, "ListUserRepo")). | ||
Into(&repos) | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
module opentelemetry-jaeger-tracing | ||
|
||
go 1.18 | ||
|
||
replace github.com/imroc/req/v3 => ../../ | ||
|
||
require ( | ||
github.com/imroc/req/v3 v3.0.0 | ||
go.opentelemetry.io/otel v1.9.0 | ||
go.opentelemetry.io/otel/exporters/jaeger v1.9.0 | ||
go.opentelemetry.io/otel/sdk v1.9.0 | ||
go.opentelemetry.io/otel/trace v1.9.0 | ||
) | ||
|
||
require ( | ||
github.com/cheekybits/genny v1.0.0 // indirect | ||
github.com/fsnotify/fsnotify v1.5.4 // indirect | ||
github.com/go-logr/logr v1.2.3 // indirect | ||
github.com/go-logr/stdr v1.2.2 // indirect | ||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect | ||
github.com/hashicorp/errwrap v1.1.0 // indirect | ||
github.com/hashicorp/go-multierror v1.1.1 // indirect | ||
github.com/lucas-clemente/quic-go v0.28.1 // indirect | ||
github.com/marten-seemann/qpack v0.2.1 // indirect | ||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect | ||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect | ||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect | ||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect | ||
github.com/nxadm/tail v1.4.8 // indirect | ||
github.com/onsi/ginkgo v1.16.5 // indirect | ||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect | ||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect | ||
golang.org/x/net v0.0.0-20220809012201-f428fae20770 // indirect | ||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect | ||
golang.org/x/text v0.3.7 // indirect | ||
golang.org/x/tools v0.1.12 // indirect | ||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect | ||
) |
Oops, something went wrong.