Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2c9466a
add http middleware
krishankumar01 Dec 23, 2025
7ccca56
add http transport
krishankumar01 Dec 29, 2025
8df0782
optimise http telemetry package
krishankumar01 Dec 29, 2025
01ecf4b
add test cases for Telemetry middleware
krishankumar01 Dec 29, 2025
0d8fda5
add auto instrumentation configs
krishankumar01 Dec 29, 2025
b2c07d5
Merge branch 'master' into kkumar-gcc/#726-auto-instrumentation-2
krishankumar01 Jan 2, 2026
395f109
add config to enable Telemetry for http clients
krishankumar01 Jan 2, 2026
bb1f529
add docs for config facade
krishankumar01 Jan 2, 2026
441ba96
add ConfigFacade nil warning
krishankumar01 Jan 2, 2026
cb3cb89
Merge branch 'master' into kkumar-gcc/#726-auto-instrumentation-2
krishankumar01 Jan 4, 2026
7c59c97
disable default telemetry
krishankumar01 Jan 4, 2026
ab72e2a
optimise transport
krishankumar01 Jan 4, 2026
413bdd6
add kill switch for instrumentation
krishankumar01 Jan 4, 2026
f999029
update the stubs
krishankumar01 Jan 4, 2026
98ec1f7
remove unnecessary handler
krishankumar01 Jan 4, 2026
ad59c56
optimise log instrumentation
krishankumar01 Jan 4, 2026
1fea34b
move route registration in the end
krishankumar01 Jan 4, 2026
794ed71
lazily initialize middleware and transport to work with new applicati…
krishankumar01 Jan 4, 2026
76021c2
optimise channel test
krishankumar01 Jan 4, 2026
eb97e1b
optimise channel test
krishankumar01 Jan 4, 2026
c23884a
merge master
krishankumar01 Jan 18, 2026
e4faad6
accept telemetry facade as an input instead of using global instance
krishankumar01 Jan 18, 2026
5141d18
use a callback to resolve the telemetry facade instance
krishankumar01 Jan 18, 2026
5a26971
optimise grpc handler to remove usage of telemetry and config facade
krishankumar01 Jan 18, 2026
5a5d9fc
optimise the grpc handler
krishankumar01 Jan 18, 2026
75130b9
Merge branch 'master' into kkumar-gcc/#726-auto-instrumentation-2
krishankumar01 Jan 20, 2026
09f4745
use telemetry transport if enabled
krishankumar01 Jan 20, 2026
9793141
optimise http auto instrumentation
krishankumar01 Jan 20, 2026
bd0645b
optimize log test cases
krishankumar01 Jan 20, 2026
f7e34b8
optimise
krishankumar01 Jan 21, 2026
777ce25
optimise
krishankumar01 Jan 21, 2026
43a7d8f
optimise
krishankumar01 Jan 26, 2026
9d3460c
optimise
krishankumar01 Jan 26, 2026
4fcd103
Merge branch 'master' into kkumar-gcc/#726-auto-instrumentation-2
krishankumar01 Jan 26, 2026
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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v3 v3.6.1
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
go.opentelemetry.io/contrib/propagators/b3 v1.39.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0
Expand Down Expand Up @@ -63,6 +64,7 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chigopher/pathlib v0.19.1 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
Expand Down Expand Up @@ -274,6 +276,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk=
go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
Expand Down
50 changes: 50 additions & 0 deletions telemetry/instrumentation/http/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package http

import (
"fmt"

"go.opentelemetry.io/otel/attribute"

"github.com/goravel/framework/contracts/http"
)

// Filter allows excluding specific requests from being traced.
type Filter func(ctx http.Context) bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the code, the specific requests won't be traced when the filter returns false. I think it's a bit confusing here. The specific requests should be traced when the filter returns false based on the annotation.

Image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the annotation


// SpanNameFormatter allows customizing the span name.
type SpanNameFormatter func(route string, ctx http.Context) string

// Option applies configuration to the server instrumentation.
type Option func(*ServerConfig)

// ServerConfig maps to "telemetry.instrumentation.http_server".
type ServerConfig struct {
Enabled bool `mapstructure:"enabled"`
ExcludedPaths []string `mapstructure:"excluded_paths"`
ExcludedMethods []string `mapstructure:"excluded_methods"`
Filters []Filter `mapstructure:"-"`
SpanNameFormatter SpanNameFormatter `mapstructure:"-"`
MetricAttributes []attribute.KeyValue `mapstructure:"-"`
}

func WithFilter(f Filter) Option {
return func(c *ServerConfig) {
c.Filters = append(c.Filters, f)
}
}

func WithSpanNameFormatter(f SpanNameFormatter) Option {
return func(c *ServerConfig) {
c.SpanNameFormatter = f
}
}

func WithMetricAttributes(attrs ...attribute.KeyValue) Option {
return func(c *ServerConfig) {
c.MetricAttributes = append(c.MetricAttributes, attrs...)
}
}

func defaultSpanNameFormatter(route string, ctx http.Context) string {
return fmt.Sprintf("%s %s", ctx.Request().Method(), route)
}
162 changes: 162 additions & 0 deletions telemetry/instrumentation/http/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package http

import (
"fmt"
"time"

"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/color"
"github.com/goravel/framework/telemetry"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)

const (
instrumentationName = "github.com/goravel/framework/telemetry/instrumentation/http"

metricRequestDuration = "http.server.request.duration"
metricRequestBodySize = "http.server.request.body.size"
metricResponseBodySize = "http.server.response.body.size"

unitSeconds = "s"
unitBytes = "By"
)

func Telemetry(opts ...Option) http.Middleware {
if telemetry.TelemetryFacade == nil {
color.Warningln("[Telemetry] Facade not initialized. HTTP middleware disabled.")
return func(ctx http.Context) { ctx.Request().Next() }
}

var cfg ServerConfig
_ = telemetry.ConfigFacade.UnmarshalKey("telemetry.instrumentation.http_server", &cfg)

for _, opt := range opts {
opt(&cfg)
}

if !cfg.Enabled {
return func(ctx http.Context) { ctx.Request().Next() }
}

if cfg.SpanNameFormatter == nil {
cfg.SpanNameFormatter = defaultSpanNameFormatter
}

tracer := telemetry.TelemetryFacade.Tracer(instrumentationName)
meter := telemetry.TelemetryFacade.Meter(instrumentationName)
propagator := telemetry.TelemetryFacade.Propagator()

durationHist, _ := meter.Float64Histogram(metricRequestDuration, metric.WithUnit(unitSeconds), metric.WithDescription("Duration of HTTP server requests"))
requestSizeHist, _ := meter.Int64Histogram(metricRequestBodySize, metric.WithUnit(unitBytes), metric.WithDescription("Size of HTTP server request body"))
responseSizeHist, _ := meter.Int64Histogram(metricResponseBodySize, metric.WithUnit(unitBytes), metric.WithDescription("Size of HTTP server response body"))

excludedPaths := make(map[string]bool, len(cfg.ExcludedPaths))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can cfg.ExcludedPaths contain *?

for _, p := range cfg.ExcludedPaths {
excludedPaths[p] = true
}
excludedMethods := make(map[string]bool, len(cfg.ExcludedMethods))
for _, m := range cfg.ExcludedMethods {
excludedMethods[m] = true
}

return func(ctx http.Context) {
req := ctx.Request()

routePath := req.OriginPath()
if routePath == "" {
routePath = req.Path()
}

if excludedPaths[routePath] || excludedMethods[req.Method()] {
req.Next()
return
}

for _, f := range cfg.Filters {
if !f(ctx) {
req.Next()
return
}
}

start := time.Now()
parentCtx := propagator.Extract(ctx.Context(), propagation.HeaderCarrier(req.Headers()))
spanName := cfg.SpanNameFormatter(routePath, ctx)

scheme := "http"
if proto := req.Header("X-Forwarded-Proto"); proto != "" {
scheme = proto
}

baseAttrs := []telemetry.KeyValue{
semconv.HTTPRequestMethodKey.String(req.Method()),
semconv.HTTPRoute(routePath),
semconv.URLScheme(scheme),
semconv.ServerAddress(req.Host()),
semconv.ClientAddress(req.Ip()),
semconv.UserAgentOriginal(req.Header("User-Agent")),
}

baseAttrs = append(baseAttrs, cfg.MetricAttributes...)

spanCtx, span := tracer.Start(parentCtx, spanName,
telemetry.WithAttributes(baseAttrs...),
telemetry.WithSpanKind(telemetry.SpanKindServer),
)

ctx.WithContext(spanCtx)

func() {
defer func() {
if r := recover(); r != nil {
err := fmt.Errorf("panic: %v", r)
span.RecordError(err)
span.SetStatus(codes.Error, "Internal Server Error")

metricAttrs := metric.WithAttributes(append(baseAttrs, semconv.HTTPResponseStatusCode(500))...)

durationHist.Record(spanCtx, time.Since(start).Seconds(), metricAttrs)
requestSizeHist.Record(spanCtx, getRequestSize(req), metricAttrs)
responseSizeHist.Record(spanCtx, 0, metricAttrs)

span.End()
panic(r)
}
}()
req.Next()
}()

status := ctx.Response().Origin().Status()

span.SetAttributes(semconv.HTTPResponseStatusCode(status))

if status >= 500 {
span.SetStatus(codes.Error, "")
} else {
span.SetStatus(codes.Ok, "")
}

if err := ctx.Err(); err != nil {
span.RecordError(err)
}

span.End()

metricAttrs := metric.WithAttributes(append(baseAttrs, semconv.HTTPResponseStatusCode(status))...)

durationHist.Record(spanCtx, time.Since(start).Seconds(), metricAttrs)
requestSizeHist.Record(spanCtx, getRequestSize(req), metricAttrs)
responseSizeHist.Record(spanCtx, int64(ctx.Response().Origin().Size()), metricAttrs)
}
}

func getRequestSize(req http.ContextRequest) int64 {
size := req.Origin().ContentLength
if size < 0 {
return 0
}
return size
}
Loading
Loading