From 346655de7308e25cf46be7e5ad2ed9e38e0b502a Mon Sep 17 00:00:00 2001 From: m1x0n Date: Sun, 19 Jun 2022 11:02:58 +0300 Subject: [PATCH 1/2] Added possibility to customize span operationName Extended TracerConfig with OperationNameFunc which allows to override default operation name during span creation. If there is no OperationNameFunc provided then fallback will be used which covers most of the use-cases. --- jaegertracing/jaegertracing.go | 22 +++++++--- jaegertracing/jaegertracing_test.go | 63 ++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/jaegertracing/jaegertracing.go b/jaegertracing/jaegertracing.go index 6809afd..3adf550 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(ctx 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(ctx echo.Context) string { + req := ctx.Request() + return "HTTP " + req.Method + " URL: " + ctx.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..8fa311e 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(ctx echo.Context) string { + // This is an example of operation name customization + // In most cases default formatting is more than enough + req := ctx.Request() + opName := "HTTP " + req.Method + + path := ctx.Path() + paramNames := ctx.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()) +} From 9b786c3f079fbcaff30b6c371878262cc396b3ae Mon Sep 17 00:00:00 2001 From: m1x0n Date: Sun, 19 Jun 2022 22:40:47 +0300 Subject: [PATCH 2/2] Renaming according to echo conventions --- jaegertracing/jaegertracing.go | 8 ++++---- jaegertracing/jaegertracing_test.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/jaegertracing/jaegertracing.go b/jaegertracing/jaegertracing.go index 3adf550..123d635 100644 --- a/jaegertracing/jaegertracing.go +++ b/jaegertracing/jaegertracing.go @@ -64,7 +64,7 @@ type ( LimitSize int // OperationNameFunc composes operation name based on context. Can be used to override default naming - OperationNameFunc func(ctx echo.Context) string + OperationNameFunc func(c echo.Context) string } ) @@ -255,9 +255,9 @@ func generateToken() string { return fmt.Sprintf("%x", b) } -func defaultOperationName(ctx echo.Context) string { - req := ctx.Request() - return "HTTP " + req.Method + " URL: " + ctx.Path() +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 diff --git a/jaegertracing/jaegertracing_test.go b/jaegertracing/jaegertracing_test.go index 8fa311e..893bc48 100644 --- a/jaegertracing/jaegertracing_test.go +++ b/jaegertracing/jaegertracing_test.go @@ -354,21 +354,21 @@ func TestTraceWithCustomOperationName(t *testing.T) { e.Use(TraceWithConfig(TraceConfig{ Tracer: tracer, ComponentName: "EchoTracer", - OperationNameFunc: func(ctx echo.Context) string { + OperationNameFunc: func(c echo.Context) string { // This is an example of operation name customization // In most cases default formatting is more than enough - req := ctx.Request() + req := c.Request() opName := "HTTP " + req.Method - path := ctx.Path() - paramNames := ctx.ParamNames() + path := c.Path() + paramNames := c.ParamNames() for _, name := range paramNames { from := ":" + name to := "{" + name + "}" path = strings.ReplaceAll(path, from, to) } - + return opName + " " + path }, }))