Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The next release will require at least [Go 1.24].
- Add the `WithLoggerProviderOptions`, `WithMeterProviderOptions` and `WithTracerProviderOptions` options to `NewSDK` to allow passing custom options to providers in `go.opentelemetry.io/contrib/otelconf`. (#7552)
- Added V2 version of AWS EC2 detector `go.opentelemetry.io/contrib/detectors/aws/ec2/v2` due to deprecation of `github.com/aws/aws-sdk-go`. (#6961)
- Support testing of [Go 1.25]. (#7732)
- Add support for HTTP server metrics in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho`. (#7668)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,34 @@
package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
oteltrace "go.opentelemetry.io/otel/trace"
)

// config is used to configure the mux middleware.
type config struct {
TracerProvider oteltrace.TracerProvider
Propagators propagation.TextMapPropagator
Skipper middleware.Skipper
TracerProvider oteltrace.TracerProvider
MeterProvider metric.MeterProvider
Propagators propagation.TextMapPropagator
Skipper middleware.Skipper
MetricAttributeFn MetricAttributeFn
EchoMetricAttributeFn EchoMetricAttributeFn
}

// MetricAttributeFn is used to extract additional attributes from the http.Request
// and return them as a slice of attribute.KeyValue.
type MetricAttributeFn func(*http.Request) []attribute.KeyValue

// EchoMetricAttributeFn is used to extract additional attributes from the echo.Context
// and return them as a slice of attribute.KeyValue.
type EchoMetricAttributeFn func(echo.Context) []attribute.KeyValue

// Option specifies instrumentation configuration options.
type Option interface {
apply(*config)
Expand All @@ -38,6 +54,16 @@ func WithPropagators(propagators propagation.TextMapPropagator) Option {
})
}

// WithMeterProvider specifies a meter provider to use for creating a meter.
// If none is specified, the global provider is used.
func WithMeterProvider(provider metric.MeterProvider) Option {
return optionFunc(func(cfg *config) {
if provider != nil {
cfg.MeterProvider = provider
}
})
}

// WithTracerProvider specifies a tracer provider to use for creating a tracer.
// If none is specified, the global provider is used.
func WithTracerProvider(provider oteltrace.TracerProvider) Option {
Expand All @@ -54,3 +80,23 @@ func WithSkipper(skipper middleware.Skipper) Option {
cfg.Skipper = skipper
})
}

// WithMetricAttributeFn specifies a function that extracts additional attributes from the http.Request
// and returns them as a slice of attribute.KeyValue.
//
// If attributes are duplicated between this method and `WithEchoMetricAttributeFn`, the attributes in this method will be overridden.
func WithMetricAttributeFn(f MetricAttributeFn) Option {
return optionFunc(func(cfg *config) {
cfg.MetricAttributeFn = f
})
}

// WithEchoMetricAttributeFn specifies a function that extracts additional attributes from the echo.Context
// and returns them as a slice of attribute.KeyValue.
//
// If attributes are duplicated between this method and `WithMetricAttributeFn`, the attributes in this method will be used.
func WithEchoMetricAttributeFn(f EchoMetricAttributeFn) Option {
return optionFunc(func(cfg *config) {
cfg.EchoMetricAttributeFn = f
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"net/http"
"slices"
"strings"
"time"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
oteltrace "go.opentelemetry.io/otel/trace"

Expand Down Expand Up @@ -40,15 +42,23 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc {
if cfg.Propagators == nil {
cfg.Propagators = otel.GetTextMapPropagator()
}

if cfg.MeterProvider == nil {
cfg.MeterProvider = otel.GetMeterProvider()
}
if cfg.Skipper == nil {
cfg.Skipper = middleware.DefaultSkipper
}

semconvSrv := semconv.NewHTTPServer(nil)
meter := cfg.MeterProvider.Meter(
ScopeName,
metric.WithInstrumentationVersion(Version()),
)

semconvSrv := semconv.NewHTTPServer(meter)

return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
requestStartTime := time.Now()
if cfg.Skipper(c) {
return next(c)
}
Expand Down Expand Up @@ -91,8 +101,35 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc {
span.SetStatus(semconvSrv.Status(status))
span.SetAttributes(semconvSrv.ResponseTraceAttrs(semconv.ResponseTelemetry{
StatusCode: status,
WriteBytes: c.Response().Size,
})...)

// Record the server-side attributes.
var additionalAttributes []attribute.KeyValue
if path := c.Path(); path != "" {
additionalAttributes = append(additionalAttributes, semconvSrv.Route(path))
}
if cfg.MetricAttributeFn != nil {
additionalAttributes = append(additionalAttributes, cfg.MetricAttributeFn(request)...)
}
if cfg.EchoMetricAttributeFn != nil {
additionalAttributes = append(additionalAttributes, cfg.EchoMetricAttributeFn(c)...)
}

semconvSrv.RecordMetrics(ctx, semconv.ServerMetricData{
ServerName: service,
ResponseSize: c.Response().Size,
MetricAttributes: semconv.MetricAttributes{
Req: request,
StatusCode: status,
AdditionalAttributes: additionalAttributes,
},
MetricData: semconv.MetricData{
RequestSize: request.ContentLength,
ElapsedTime: float64(time.Since(requestStartTime)) / float64(time.Millisecond),
},
})

return err
}
}
Expand Down
Loading
Loading