diff --git a/jaegertracing/jaegertracing.go b/jaegertracing/jaegertracing.go index 6809afd..123d635 100644 --- a/jaegertracing/jaegertracing.go +++ b/jaegertracing/jaegertracing.go @@ -62,6 +62,9 @@ type ( // http body limit size (in bytes) // NOTE: don't specify values larger than 60000 as jaeger can't handle values in span.LogKV larger than 60000 bytes LimitSize int + + // OperationNameFunc composes operation name based on context. Can be used to override default naming + OperationNameFunc func(c echo.Context) string } ) @@ -72,8 +75,9 @@ var ( ComponentName: defaultComponentName, IsBodyDump: false, - LimitHTTPBody: true, - LimitSize: 60_000, + LimitHTTPBody: true, + LimitSize: 60_000, + OperationNameFunc: defaultOperationName, } ) @@ -130,6 +134,9 @@ func TraceWithConfig(config TraceConfig) echo.MiddlewareFunc { if config.ComponentName == "" { config.ComponentName = defaultComponentName } + if config.OperationNameFunc == nil { + config.OperationNameFunc = defaultOperationName + } return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { @@ -138,7 +145,7 @@ func TraceWithConfig(config TraceConfig) echo.MiddlewareFunc { } req := c.Request() - opname := "HTTP " + req.Method + " URL: " + c.Path() + opname := config.OperationNameFunc(c) realIP := c.RealIP() requestID := getRequestID(c) // request-id generated by reverse-proxy @@ -218,10 +225,10 @@ func TraceWithConfig(config TraceConfig) echo.MiddlewareFunc { func limitString(str string, size int) string { if len(str) > size { - return str[:size/2] + "\n---- skipped ----\n" + str[len(str)-size/2:] + return str[:size/2] + "\n---- skipped ----\n" + str[len(str)-size/2:] } - return str + return str } func logError(span opentracing.Span, err error) { @@ -248,6 +255,11 @@ func generateToken() string { return fmt.Sprintf("%x", b) } +func defaultOperationName(c echo.Context) string { + req := c.Request() + return "HTTP " + req.Method + " URL: " + c.Path() +} + // TraceFunction wraps funtion with opentracing span adding tags for the function name and caller details func TraceFunction(ctx echo.Context, fn interface{}, params ...interface{}) (result []reflect.Value) { // Get function name diff --git a/jaegertracing/jaegertracing_test.go b/jaegertracing/jaegertracing_test.go index d86c0bc..893bc48 100644 --- a/jaegertracing/jaegertracing_test.go +++ b/jaegertracing/jaegertracing_test.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strings" "testing" "github.com/labstack/echo/v4" @@ -101,11 +102,14 @@ func (tr *mockTracer) currentSpan() *mockSpan { } func (tr *mockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { - tr.hasStartSpanWithOption = (len(opts) > 0) + tr.hasStartSpanWithOption = len(opts) > 0 if tr.span != nil { + tr.span.opName = operationName return tr.span } - return createSpan(tr) + span := createSpan(tr) + span.opName = operationName + return span } func (tr *mockTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error { @@ -325,3 +329,58 @@ func TestTraceWithoutLimitHTTPBody(t *testing.T) { assert.Equal(t, "123456789012345678901234567890", tracer.currentSpan().getLog("http.req.body")) assert.Equal(t, "Hi 123456789012345678901234567890", tracer.currentSpan().getLog("http.resp.body")) } + +func TestTraceWithDefaultOperationName(t *testing.T) { + tracer := createMockTracer() + + e := echo.New() + e.Use(Trace(tracer)) + + e.GET("/trace", func(c echo.Context) error { + return c.String(http.StatusOK, "Hi") + }) + + req := httptest.NewRequest(http.MethodGet, "/trace", nil) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(t, "HTTP GET URL: /trace", tracer.currentSpan().getOpName()) +} + +func TestTraceWithCustomOperationName(t *testing.T) { + tracer := createMockTracer() + + e := echo.New() + e.Use(TraceWithConfig(TraceConfig{ + Tracer: tracer, + ComponentName: "EchoTracer", + OperationNameFunc: func(c echo.Context) string { + // This is an example of operation name customization + // In most cases default formatting is more than enough + req := c.Request() + opName := "HTTP " + req.Method + + path := c.Path() + paramNames := c.ParamNames() + + for _, name := range paramNames { + from := ":" + name + to := "{" + name + "}" + path = strings.ReplaceAll(path, from, to) + } + + return opName + " " + path + }, + })) + + e.GET("/trace/:traceID/spans/:spanID", func(c echo.Context) error { + return c.String(http.StatusOK, "Hi") + }) + + req := httptest.NewRequest(http.MethodGet, "/trace/123456/spans/123", nil) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(t, true, tracer.currentSpan().isFinished()) + assert.Equal(t, "HTTP GET /trace/{traceID}/spans/{spanID}", tracer.currentSpan().getOpName()) +}