Skip to content

Commit 304bbdf

Browse files
arun0009Arun Gopalpuri
andauthored
Add zipkin tracing middleware (#48)
Co-authored-by: Arun Gopalpuri <[email protected]>
1 parent 7944b61 commit 304bbdf

File tree

7 files changed

+825
-2
lines changed

7 files changed

+825
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ Name | Description | Author
1414
session | Session middleware backed by [gorilla/sessions](https://github.com/gorilla/sessions) | [vishr](https://github.com/vishr)
1515
jaegertracing | Jaeger tracer middleware | [carlosedp](https://github.com/carlosedp)
1616
prometheus | Prometheus metrics | [carlosedp](https://github.com/carlosedp)
17-
pprof | pprof middlware | [arun0009](https://github.com/arun0009)
17+
pprof | pprof middlware | [arun0009](https://github.com/arun0009)
18+
zipkin | [Zipkin](https://github.com/openzipkin/zipkin-go) middleware | [arun0009](https://github.com/arun0009)

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ require (
1010
github.com/labstack/echo/v4 v4.1.6
1111
github.com/labstack/gommon v0.2.9
1212
github.com/opentracing/opentracing-go v1.1.0
13+
github.com/openzipkin/zipkin-go v0.2.5
1314
github.com/pkg/errors v0.9.1 // indirect
1415
github.com/prometheus/client_golang v1.5.1
15-
github.com/prometheus/common v0.10.0 // indirect
16+
github.com/prometheus/common v0.10.0
1617
github.com/prometheus/procfs v0.2.0 // indirect
1718
github.com/stretchr/testify v1.4.0
1819
github.com/uber-go/atomic v1.4.0 // indirect

go.sum

Lines changed: 69 additions & 0 deletions
Large diffs are not rendered by default.

zipkintracing/README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Tracing Library for Go
2+
3+
This library provides tracing for go using [Zipkin](https://zipkin.io/)
4+
5+
## Usage
6+
7+
### Server Tracing Middleware & http client tracing
8+
9+
```go
10+
package main
11+
12+
import (
13+
"github.com/labstack/echo-contrib/zipkintracing"
14+
"github.com/labstack/echo/v4"
15+
"github.com/openzipkin/zipkin-go"
16+
zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http"
17+
zipkinHttpReporter "github.com/openzipkin/zipkin-go/reporter/http"
18+
"io/ioutil"
19+
"net/http"
20+
)
21+
22+
func main() {
23+
e := echo.New()
24+
endpoint, err := zipkin.NewEndpoint("echo-service", "")
25+
if err != nil {
26+
e.Logger.Fatalf("error creating zipkin endpoint: %s", err.Error())
27+
}
28+
reporter := zipkinHttpReporter.NewReporter("http://localhost:9411/api/v2/spans")
29+
traceTags := make(map[string]string)
30+
traceTags["availability_zone"] = "us-east-1"
31+
tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(traceTags))
32+
client, _ := zipkinhttp.NewClient(tracer, zipkinhttp.ClientTrace(true))
33+
if err != nil {
34+
e.Logger.Fatalf("tracing init failed: %s", err.Error())
35+
}
36+
//Wrap & Use trace server middleware, this traces all server calls
37+
e.Use(zipkintracing.TraceServer(tracer))
38+
//....
39+
e.GET("/echo", func(c echo.Context) error {
40+
//trace http request calls.
41+
req, _ := http.NewRequest("GET", "https://echo.labstack.com/", nil)
42+
resp, _ := zipkintracing.DoHTTP(c, req, client)
43+
body, _ := ioutil.ReadAll(resp.Body)
44+
return c.String(http.StatusOK, string(body))
45+
})
46+
47+
defer reporter.Close() //defer close reporter
48+
e.Logger.Fatal(e.Start(":8080"))
49+
}
50+
```
51+
### Reverse Proxy Tracing
52+
53+
```go
54+
package main
55+
56+
import (
57+
"github.com/labstack/echo-contrib/zipkintracing"
58+
"github.com/labstack/echo/v4"
59+
"github.com/labstack/echo/v4/middleware"
60+
"github.com/openzipkin/zipkin-go"
61+
zipkinHttpReporter "github.com/openzipkin/zipkin-go/reporter/http"
62+
"net/http/httputil"
63+
"net/url"
64+
)
65+
66+
func main() {
67+
e := echo.New()
68+
//new tracing instance
69+
endpoint, err := zipkin.NewEndpoint("echo-service", "")
70+
if err != nil {
71+
e.Logger.Fatalf("error creating zipkin endpoint: %s", err.Error())
72+
}
73+
reporter := zipkinHttpReporter.NewReporter("http://localhost:9411/api/v2/spans")
74+
traceTags := make(map[string]string)
75+
traceTags["availability_zone"] = "us-east-1"
76+
tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(traceTags))
77+
if err != nil {
78+
e.Logger.Fatalf("tracing init failed: %s", err.Error())
79+
}
80+
//....
81+
e.GET("/echo", func(c echo.Context) error {
82+
proxyURL, _ := url.Parse("https://echo.labstack.com/")
83+
httputil.NewSingleHostReverseProxy(proxyURL)
84+
return nil
85+
}, zipkintracing.TraceProxy(tracer))
86+
87+
defer reporter.Close() //close reporter
88+
e.Logger.Fatal(e.Start(":8080"))
89+
90+
}
91+
```
92+
93+
### Trace function calls
94+
95+
To trace function calls e.g. to trace `s3Func`
96+
97+
```go
98+
package main
99+
100+
import (
101+
"github.com/labstack/echo-contrib/zipkintracing"
102+
"github.com/labstack/echo/v4"
103+
"github.com/openzipkin/zipkin-go"
104+
)
105+
106+
func s3Func(c echo.Context, tracer *zipkin.Tracer) {
107+
defer zipkintracing.TraceFunc(c, "s3_read", zipkintracing.DefaultSpanTags, tracer)()
108+
//s3Func logic here...
109+
}
110+
```
111+
112+
### Create Child Span
113+
114+
```go
115+
package main
116+
117+
import (
118+
"github.com/labstack/echo-contrib/zipkintracing"
119+
"github.com/labstack/echo/v4"
120+
"github.com/openzipkin/zipkin-go"
121+
)
122+
123+
func traceWithChildSpan(c echo.Context, tracer *zipkin.Tracer) {
124+
span := zipkintracing.StartChildSpan(c, "someMethod", tracer)
125+
//func logic.....
126+
span.Finish()
127+
}
128+
```

zipkintracing/response_writer.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package zipkintracing
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"net"
7+
"net/http"
8+
)
9+
10+
// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
11+
// the response. It is recommended that middleware handlers use this construct to wrap a response writer
12+
// if the functionality calls for it.
13+
type ResponseWriter interface {
14+
http.ResponseWriter
15+
http.Flusher
16+
// Status returns the status code of the response or 0 if the response has
17+
// not been written
18+
Status() int
19+
// Written returns whether or not the ResponseWriter has been written.
20+
Written() bool
21+
// Size returns the size of the response body.
22+
Size() int
23+
// Before allows for a function to be called before the ResponseWriter has been written to. This is
24+
// useful for setting headers or any other operations that must happen before a response has been written.
25+
Before(func(ResponseWriter))
26+
}
27+
28+
type beforeFunc func(ResponseWriter)
29+
30+
// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
31+
func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
32+
nrw := &responseWriter{
33+
ResponseWriter: rw,
34+
}
35+
36+
return nrw
37+
}
38+
39+
type responseWriter struct {
40+
http.ResponseWriter
41+
status int
42+
size int
43+
beforeFuncs []beforeFunc
44+
}
45+
46+
func (rw *responseWriter) WriteHeader(s int) {
47+
rw.status = s
48+
rw.callBefore()
49+
rw.ResponseWriter.WriteHeader(s)
50+
}
51+
52+
func (rw *responseWriter) Write(b []byte) (int, error) {
53+
if !rw.Written() {
54+
// The status will be StatusOK if WriteHeader has not been called yet
55+
rw.WriteHeader(http.StatusOK)
56+
}
57+
size, err := rw.ResponseWriter.Write(b)
58+
rw.size += size
59+
return size, err
60+
}
61+
62+
func (rw *responseWriter) Status() int {
63+
return rw.status
64+
}
65+
66+
func (rw *responseWriter) Size() int {
67+
return rw.size
68+
}
69+
70+
func (rw *responseWriter) Written() bool {
71+
return rw.status != 0
72+
}
73+
74+
func (rw *responseWriter) Before(before func(ResponseWriter)) {
75+
rw.beforeFuncs = append(rw.beforeFuncs, before)
76+
}
77+
78+
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
79+
hijacker, ok := rw.ResponseWriter.(http.Hijacker)
80+
if !ok {
81+
return nil, nil, errors.New("the ResponseWriter doesn't support the Hijacker interface")
82+
}
83+
return hijacker.Hijack()
84+
}
85+
86+
func (rw *responseWriter) callBefore() {
87+
for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
88+
rw.beforeFuncs[i](rw)
89+
}
90+
}
91+
92+
func (rw *responseWriter) Flush() {
93+
flusher, ok := rw.ResponseWriter.(http.Flusher)
94+
if ok {
95+
if !rw.Written() {
96+
// The status will be StatusOK if WriteHeader has not been called yet
97+
rw.WriteHeader(http.StatusOK)
98+
}
99+
flusher.Flush()
100+
}
101+
}
102+
103+
func (rw *responseWriter) CloseNotify() <-chan bool {
104+
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
105+
}

0 commit comments

Comments
 (0)