@@ -22,6 +22,7 @@ package jaegertracing
2222
2323import (
2424 "bytes"
25+ "crypto/rand"
2526 "errors"
2627 "fmt"
2728 "io"
@@ -54,6 +55,13 @@ type (
5455
5556 // add req body & resp body to tracing tags
5657 IsBodyDump bool
58+
59+ // prevent logging long http request bodies
60+ LimitHTTPBody bool
61+
62+ // http body limit size (in bytes)
63+ // NOTE: don't specify values larger than 60000 as jaeger can't handle values in span.LogKV larger than 60000 bytes
64+ LimitSize int
5765 }
5866)
5967
6371 Skipper : middleware .DefaultSkipper ,
6472 ComponentName : defaultComponentName ,
6573 IsBodyDump : false ,
74+
75+ LimitHTTPBody : true ,
76+ LimitSize : 60_000 ,
6677 }
6778)
6879
@@ -128,82 +139,115 @@ func TraceWithConfig(config TraceConfig) echo.MiddlewareFunc {
128139
129140 req := c .Request ()
130141 opname := "HTTP " + req .Method + " URL: " + c .Path ()
142+ realIP := c .RealIP ()
143+ requestID := getRequestID (c ) // request-id generated by reverse-proxy
144+
131145 var sp opentracing.Span
132- tr := config .Tracer
133- if ctx , err := tr .Extract (opentracing .HTTPHeaders ,
134- opentracing .HTTPHeadersCarrier (req .Header )); err != nil {
135- sp = tr .StartSpan (opname )
146+ var err error
147+
148+ ctx , err := config .Tracer .Extract (
149+ opentracing .HTTPHeaders ,
150+ opentracing .HTTPHeadersCarrier (req .Header ),
151+ )
152+
153+ if err != nil {
154+ sp = config .Tracer .StartSpan (opname )
136155 } else {
137- sp = tr .StartSpan (opname , ext .RPCServerOption (ctx ))
156+ sp = config . Tracer .StartSpan (opname , ext .RPCServerOption (ctx ))
138157 }
158+ defer sp .Finish ()
139159
140160 ext .HTTPMethod .Set (sp , req .Method )
141161 ext .HTTPUrl .Set (sp , req .URL .String ())
142162 ext .Component .Set (sp , config .ComponentName )
163+ sp .SetTag ("client_ip" , realIP )
164+ sp .SetTag ("request_id" , requestID )
143165
144166 // Dump request & response body
145- resBody := new (bytes. Buffer )
167+ var respDumper * responseDumper
146168 if config .IsBodyDump {
147169 // request
148170 reqBody := []byte {}
149- if c .Request ().Body != nil { // Read
171+ if c .Request ().Body != nil {
150172 reqBody , _ = ioutil .ReadAll (c .Request ().Body )
151- sp .SetTag ("http.req.body" , string (reqBody ))
173+
174+ if config .LimitHTTPBody {
175+ sp .LogKV ("http.req.body" , limitString (string (reqBody ), config .LimitSize ))
176+ } else {
177+ sp .LogKV ("http.req.body" , string (reqBody ))
178+ }
152179 }
153180
154- req .Body = ioutil .NopCloser (bytes .NewBuffer (reqBody )) // Reset
181+ req .Body = ioutil .NopCloser (bytes .NewBuffer (reqBody )) // reset original request body
155182
156183 // response
157- mw := io .MultiWriter (c .Response ().Writer , resBody )
158- writer := & bodyDumpResponseWriter {Writer : mw , ResponseWriter : c .Response ().Writer }
159- c .Response ().Writer = writer
184+ respDumper = newResponseDumper (c .Response ())
185+ c .Response ().Writer = respDumper
160186 }
161187
188+ // setup request context - add opentracing span
162189 req = req .WithContext (opentracing .ContextWithSpan (req .Context (), sp ))
163190 c .SetRequest (req )
164191
165- var err error
166- defer func () {
167- committed := c .Response ().Committed
168- status := c .Response ().Status
169-
170- if err != nil {
171- var httpError * echo.HTTPError
172- if errors .As (err , & httpError ) {
173- if httpError .Code != 0 {
174- status = httpError .Code
175- }
176- sp .SetTag ("error.message" , httpError .Message )
177- } else {
178- sp .SetTag ("error.message" , err .Error ())
179- }
180- if status == http .StatusOK {
181- // this is ugly workaround for cases when httpError.code == 0 or error was not httpError and status
182- // in request was 200 (OK). In these cases replace status with something that represents an error
183- // it could be that error handlers or middlewares up in chain will output different status code to
184- // client. but at least we send something better than 200 to jaeger
185- status = http .StatusInternalServerError
186- }
187- }
192+ // call next middleware / controller
193+ err = next (c )
194+ if err != nil {
195+ c .Error (err ) // call custom registered error handler
196+ }
188197
189- ext .HTTPStatusCode .Set (sp , uint16 (status ))
190- if status >= http .StatusInternalServerError || ! committed {
191- ext .Error .Set (sp , true )
192- }
198+ status := c .Response ().Status
199+ ext .HTTPStatusCode .Set (sp , uint16 (status ))
193200
194- // Dump response body
195- if config .IsBodyDump {
196- sp .SetTag ("http.resp.body" , resBody .String ())
201+ if err != nil {
202+ logError (sp , err )
203+ }
204+
205+ // Dump response body
206+ if config .IsBodyDump {
207+ if config .LimitHTTPBody {
208+ sp .LogKV ("http.resp.body" , limitString (respDumper .GetResponse (), config .LimitSize ))
209+ } else {
210+ sp .LogKV ("http.resp.body" , respDumper .GetResponse ())
197211 }
212+ }
198213
199- sp .Finish ()
200- }()
201- err = next (c )
202- return err
214+ return nil // error was already processed with ctx.Error(err)
203215 }
204216 }
205217}
206218
219+ func limitString (str string , size int ) string {
220+ if len (str ) > size {
221+ return str [:size / 2 ] + "\n ---- skipped ----\n " + str [len (str )- size / 2 :]
222+ }
223+
224+ return str
225+ }
226+
227+ func logError (span opentracing.Span , err error ) {
228+ var httpError * echo.HTTPError
229+ if errors .As (err , & httpError ) {
230+ span .LogKV ("error.message" , httpError .Message )
231+ } else {
232+ span .LogKV ("error.message" , err .Error ())
233+ }
234+ span .SetTag ("error" , true )
235+ }
236+
237+ func getRequestID (ctx echo.Context ) string {
238+ requestID := ctx .Request ().Header .Get (echo .HeaderXRequestID ) // request-id generated by reverse-proxy
239+ if requestID == "" {
240+ requestID = generateToken () // missed request-id from proxy, we generate it manually
241+ }
242+ return requestID
243+ }
244+
245+ func generateToken () string {
246+ b := make ([]byte , 16 )
247+ rand .Read (b )
248+ return fmt .Sprintf ("%x" , b )
249+ }
250+
207251// TraceFunction wraps funtion with opentracing span adding tags for the function name and caller details
208252func TraceFunction (ctx echo.Context , fn interface {}, params ... interface {}) (result []reflect.Value ) {
209253 // Get function name
0 commit comments