From c1d9e50b4f037c7d3e1ff63115e47a4ea3f6a350 Mon Sep 17 00:00:00 2001 From: Igor Khodyrev Date: Tue, 16 Jul 2024 16:48:30 +0300 Subject: [PATCH 1/2] otelfiber: add WithCustomMetricAttrbutes option --- otelfiber/config.go | 27 ++++++++----- otelfiber/fiber.go | 4 +- otelfiber/otelfiber_test/fiber_test.go | 54 ++++++++++++++++++++++++++ otelfiber/semconv.go | 4 ++ 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/otelfiber/config.go b/otelfiber/config.go index bc8aec92..ab588253 100644 --- a/otelfiber/config.go +++ b/otelfiber/config.go @@ -10,15 +10,16 @@ import ( // config is used to configure the Fiber middleware. type config struct { - Next func(*fiber.Ctx) bool - TracerProvider oteltrace.TracerProvider - MeterProvider otelmetric.MeterProvider - Port *int - Propagators propagation.TextMapPropagator - ServerName *string - SpanNameFormatter func(*fiber.Ctx) string - CustomAttributes func(*fiber.Ctx) []attribute.KeyValue - collectClientIP bool + Next func(*fiber.Ctx) bool + TracerProvider oteltrace.TracerProvider + MeterProvider otelmetric.MeterProvider + Port *int + Propagators propagation.TextMapPropagator + ServerName *string + SpanNameFormatter func(*fiber.Ctx) string + CustomAttributes func(*fiber.Ctx) []attribute.KeyValue + CustomMetricAttributes func(*fiber.Ctx) []attribute.KeyValue + collectClientIP bool } // Option specifies instrumentation configuration options. @@ -98,6 +99,14 @@ func WithCustomAttributes(f func(ctx *fiber.Ctx) []attribute.KeyValue) Option { }) } +// WithCustomMetricAttributes specifies a function that will be called on every +// request and the returned attributes will be added to the metrics. +func WithCustomMetricAttributes(f func(ctx *fiber.Ctx) []attribute.KeyValue) Option { + return optionFunc(func(cfg *config) { + cfg.CustomMetricAttributes = f + }) +} + // WithCollectClientIP specifies whether to collect the client's IP address // from the request. This is enabled by default. func WithCollectClientIP(collect bool) Option { diff --git a/otelfiber/fiber.go b/otelfiber/fiber.go index a4541ef4..508528c0 100644 --- a/otelfiber/fiber.go +++ b/otelfiber/fiber.go @@ -132,13 +132,13 @@ func Middleware(opts ...Option) fiber.Handler { semconv.HTTPAttributesFromHTTPStatusCode(c.Response().StatusCode()), semconv.HTTPRouteKey.String(c.Route().Path), // no need to copy c.Route().Path: route strings should be immutable across app lifecycle ) - + var responseSize int64 requestSize := int64(len(c.Request().Body())) if c.GetRespHeader("Content-Type") != "text/event-stream" { responseSize = int64(len(c.Response().Body())) } - + defer func() { responseMetricAttrs = append( responseMetricAttrs, diff --git a/otelfiber/otelfiber_test/fiber_test.go b/otelfiber/otelfiber_test/fiber_test.go index e3499a1c..32c87fb7 100644 --- a/otelfiber/otelfiber_test/fiber_test.go +++ b/otelfiber/otelfiber_test/fiber_test.go @@ -447,6 +447,60 @@ func TestCustomAttributes(t *testing.T) { assert.Contains(t, attr, attribute.String("http.query_params", "foo=bar")) } +func TestCustomMetricAttributes(t *testing.T) { + reader := metric.NewManualReader() + provider := metric.NewMeterProvider(metric.WithReader(reader)) + + serverName := "foobar" + port := 8080 + route := "/foo" + + app := fiber.New() + app.Use( + otelfiber.Middleware( + otelfiber.WithMeterProvider(provider), + otelfiber.WithPort(port), + otelfiber.WithServerName(serverName), + otelfiber.WithCustomMetricAttributes(func(ctx *fiber.Ctx) []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.Key("http.query_params").String(ctx.Request().URI().QueryArgs().String()), + } + }), + ), + ) + + app.Get(route, func(ctx *fiber.Ctx) error { + return ctx.SendStatus(http.StatusOK) + }) + + r := httptest.NewRequest(http.MethodGet, "/foo?foo=bar", nil) + resp, _ := app.Test(r) + + // do and verify the request + require.Equal(t, http.StatusOK, resp.StatusCode) + + metrics := metricdata.ResourceMetrics{} + err := reader.Collect(context.Background(), &metrics) + assert.NoError(t, err) + assert.Len(t, metrics.ScopeMetrics, 1) + + requestAttrs := []attribute.KeyValue{ + semconv.HTTPFlavorKey.String(fmt.Sprintf("1.%d", r.ProtoMinor)), + semconv.HTTPMethodKey.String(http.MethodGet), + semconv.HTTPSchemeHTTP, + semconv.NetHostNameKey.String(r.Host), + semconv.NetHostPortKey.Int(port), + semconv.HTTPServerNameKey.String(serverName), + attribute.String("http.query_params", "foo=bar"), + } + responseAttrs := append( + semconv.HTTPAttributesFromHTTPStatusCode(200), + semconv.HTTPRouteKey.String(route), + ) + + assertScopeMetrics(t, metrics.ScopeMetrics[0], route, requestAttrs, append(requestAttrs, responseAttrs...)) +} + func TestOutboundTracingPropagation(t *testing.T) { sr := new(tracetest.SpanRecorder) provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) diff --git a/otelfiber/semconv.go b/otelfiber/semconv.go index c5ed04bb..2cf89544 100644 --- a/otelfiber/semconv.go +++ b/otelfiber/semconv.go @@ -26,6 +26,10 @@ func httpServerMetricAttributesFromRequest(c *fiber.Ctx, cfg config) []attribute attrs = append(attrs, semconv.HTTPServerNameKey.String(*cfg.ServerName)) } + if cfg.CustomMetricAttributes != nil { + attrs = append(attrs, cfg.CustomMetricAttributes(c)...) + } + return attrs } From 8f3bc050e5451ce4fd866d096769c2ec08fb3234 Mon Sep 17 00:00:00 2001 From: Igor Khodyrev Date: Mon, 22 Jul 2024 15:46:06 +0300 Subject: [PATCH 2/2] Add AddCustomMetricAttributes to README.md --- otelfiber/README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/otelfiber/README.md b/otelfiber/README.md index 5d3a215b..200a05c5 100644 --- a/otelfiber/README.md +++ b/otelfiber/README.md @@ -34,17 +34,18 @@ otelfiber.Middleware(opts ...otelfiber.Option) fiber.Handler You can configure the middleware using functional parameters -| Function | Argument Type | Description | Default | -| :------------------ | :-------------------------------- | :--------------------------------------------------------------------------------- | :-------------------------------------------------------------------- | -| `WithNext` | `func(*fiber.Ctx) bool` | Define a function to skip this middleware when returned true .| nil | -| `WithTracerProvider` | `oteltrace.TracerProvider` | Specifies a tracer provider to use for creating a tracer. | nil - the global tracer provider is used | -| `WithMeterProvider` | `otelmetric.MeterProvider` | Specifies a meter provider to use for reporting. | nil - the global meter provider is used | -| `WithPort` | `int` | Specifies the value to use when setting the `net.host.port` attribute on metrics/spans. | Defaults to (`80` for `http`, `443` for `https`) | -| `WithPropagators` | `propagation.TextMapPropagator` | Specifies propagators to use for extracting information from the HTTP requests. | If none are specified, global ones will be used | -| `WithServerName` | `string` | Specifies the value to use when setting the `http.server_name` attribute on metrics/spans. | - | -| `WithSpanNameFormatter` | `func(*fiber.Ctx) string` | Takes a function that will be called on every request and the returned string will become the span Name. | Default formatter returns the route pathRaw | -| `WithCustomAttributes` | `func(*fiber.Ctx) []attribute.KeyValue` | Define a function to add custom attributes to the span. | nil | -| `WithCollectClientIP` | `bool` | Specifies whether to collect the client's IP address from the request. | true | +| Function | Argument Type | Description | Default | +| :------------------------ | :-------------------------------- | :--------------------------------------------------------------------------------- | :-------------------------------------------------------------------- | +| `WithNext` | `func(*fiber.Ctx) bool` | Define a function to skip this middleware when returned true .| nil | +| `WithTracerProvider` | `oteltrace.TracerProvider` | Specifies a tracer provider to use for creating a tracer. | nil - the global tracer provider is used | +| `WithMeterProvider` | `otelmetric.MeterProvider` | Specifies a meter provider to use for reporting. | nil - the global meter provider is used | +| `WithPort` | `int` | Specifies the value to use when setting the `net.host.port` attribute on metrics/spans. | Defaults to (`80` for `http`, `443` for `https`) | +| `WithPropagators` | `propagation.TextMapPropagator` | Specifies propagators to use for extracting information from the HTTP requests. | If none are specified, global ones will be used | +| `WithServerName` | `string` | Specifies the value to use when setting the `http.server_name` attribute on metrics/spans. | - | +| `WithSpanNameFormatter` | `func(*fiber.Ctx) string` | Takes a function that will be called on every request and the returned string will become the span Name. | Default formatter returns the route pathRaw | +| `WithCustomAttributes` | `func(*fiber.Ctx) []attribute.KeyValue` | Define a function to add custom attributes to the span. | nil | +| `WithCustomMetricAttributes` | `func(*fiber.Ctx) []attribute.KeyValue` | Define a function to add custom attributes to the metrics. | nil | +| `WithCollectClientIP` | `bool` | Specifies whether to collect the client's IP address from the request. | true | ## Usage