@@ -22,6 +22,7 @@ package jaegertracing
22
22
23
23
import (
24
24
"bytes"
25
+ "crypto/rand"
25
26
"errors"
26
27
"fmt"
27
28
"io"
@@ -54,6 +55,13 @@ type (
54
55
55
56
// add req body & resp body to tracing tags
56
57
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
57
65
}
58
66
)
59
67
63
71
Skipper : middleware .DefaultSkipper ,
64
72
ComponentName : defaultComponentName ,
65
73
IsBodyDump : false ,
74
+
75
+ LimitHTTPBody : true ,
76
+ LimitSize : 60_000 ,
66
77
}
67
78
)
68
79
@@ -128,82 +139,115 @@ func TraceWithConfig(config TraceConfig) echo.MiddlewareFunc {
128
139
129
140
req := c .Request ()
130
141
opname := "HTTP " + req .Method + " URL: " + c .Path ()
142
+ realIP := c .RealIP ()
143
+ requestID := getRequestID (c ) // request-id generated by reverse-proxy
144
+
131
145
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 )
136
155
} else {
137
- sp = tr .StartSpan (opname , ext .RPCServerOption (ctx ))
156
+ sp = config . Tracer .StartSpan (opname , ext .RPCServerOption (ctx ))
138
157
}
158
+ defer sp .Finish ()
139
159
140
160
ext .HTTPMethod .Set (sp , req .Method )
141
161
ext .HTTPUrl .Set (sp , req .URL .String ())
142
162
ext .Component .Set (sp , config .ComponentName )
163
+ sp .SetTag ("client_ip" , realIP )
164
+ sp .SetTag ("request_id" , requestID )
143
165
144
166
// Dump request & response body
145
- resBody := new (bytes. Buffer )
167
+ var respDumper * responseDumper
146
168
if config .IsBodyDump {
147
169
// request
148
170
reqBody := []byte {}
149
- if c .Request ().Body != nil { // Read
171
+ if c .Request ().Body != nil {
150
172
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
+ }
152
179
}
153
180
154
- req .Body = ioutil .NopCloser (bytes .NewBuffer (reqBody )) // Reset
181
+ req .Body = ioutil .NopCloser (bytes .NewBuffer (reqBody )) // reset original request body
155
182
156
183
// 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
160
186
}
161
187
188
+ // setup request context - add opentracing span
162
189
req = req .WithContext (opentracing .ContextWithSpan (req .Context (), sp ))
163
190
c .SetRequest (req )
164
191
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
+ }
188
197
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 ))
193
200
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 ())
197
211
}
212
+ }
198
213
199
- sp .Finish ()
200
- }()
201
- err = next (c )
202
- return err
214
+ return nil // error was already processed with ctx.Error(err)
203
215
}
204
216
}
205
217
}
206
218
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
+
207
251
// TraceFunction wraps funtion with opentracing span adding tags for the function name and caller details
208
252
func TraceFunction (ctx echo.Context , fn interface {}, params ... interface {}) (result []reflect.Value ) {
209
253
// Get function name
0 commit comments