-
Notifications
You must be signed in to change notification settings - Fork 109
feat: [#726] Add HTTP server and client telemetry instrumentation [5] #1326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds HTTP server and client telemetry instrumentation capabilities to the Goravel framework, enabling automatic tracing and metrics collection for HTTP requests and responses. This is part of the broader telemetry instrumentation feature set (issue #726).
Key Changes:
- Implemented HTTP middleware for server-side request instrumentation with configurable filtering
- Created HTTP client transport wrapper for outbound request instrumentation
- Added configuration support for HTTP server instrumentation with exclusion patterns
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
telemetry/setup/stubs.go |
Adds instrumentation configuration section with http_server settings for enabled state and path/method exclusions |
telemetry/instrumentation/http/middleware.go |
Implements Telemetry middleware that captures traces, metrics, and handles panics for incoming HTTP requests |
telemetry/instrumentation/http/middleware_test.go |
Provides test suite with test helpers for middleware functionality including success cases, exclusions, disabled state, and panic handling |
telemetry/instrumentation/http/config.go |
Defines ServerConfig structure and option functions for customizing HTTP server instrumentation behavior |
telemetry/instrumentation/http/transport.go |
Implements NewTransport function to wrap HTTP clients with telemetry instrumentation |
telemetry/instrumentation/http/transport_test.go |
Tests for transport wrapper covering fallback scenarios and successful wrapping cases |
go.mod |
Adds dependency on otelhttp instrumentation library |
go.sum |
Updates dependency checksums for otelhttp and its transitive dependency httpsnoop |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1326 +/- ##
==========================================
+ Coverage 70.57% 70.60% +0.02%
==========================================
Files 286 289 +3
Lines 17519 17777 +258
==========================================
+ Hits 12364 12551 +187
- Misses 4635 4690 +55
- Partials 520 536 +16 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@krishankumar01 No, just using the default configuration and run the project. |
|
Okay, I’ll check. I’m thinking of using a kill switch for all instrumentation in Instrumentation:
HttpServer:
Enabled: true
HttpClient:
Enabled: false
Log:
Enabled: falseIn this case, regardless of whether the log driver or HTTP middleware is registered, telemetry will not be published when it is disabled. |
@hwbrzzl I think the issue is in |
foundation/application_builder.go
Outdated
| } | ||
|
|
||
| // Register routes | ||
| for _, route := range r.routes { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving the route registration to the end, because if a user registers any gRPC stats handler or interceptors after calling routes.Grpc() (since in this file we usually call facades.Grpc().Server()), they will be ignored as the server is initialized only once in grpc/application.go.
log/application.go
Outdated
| } | ||
| case log.DriverOtel: | ||
| logLogger := telemetrylog.NewTelemetryChannel() | ||
| logLogger := telemetrylog.NewTelemetryChannel(config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won’t work if we call the otel driver logger inside providers or from the application builder callbacks, or any callback that runs before provider bootstrap, because telemetry.TelemetryFacade won’t be initialized and therefore won’t be able to lazily initialize the logger. This is expected, as telemetry is typically available only within the request lifecycle context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about passing TelemetryFacade here instead of using telemetry.TelemetryFacade directly? telemetry/instrumentation/* should not use the global telemetry.TelemetryFacade and telemetry.ConfigFacade. They should be passed into.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then how do we ensure that a telemetry log channel can accept a telemetry facade instance? When we initialize the telemetry channel in NewApplication, the telemetry facade won’t be available yet. This would require making telemetry a dependency of the log facade.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we initialize the telemetry channel in NewApplication, the telemetry facade won’t be available yet.
It should be impossible if the telemetry facade is registered, the telemetry channel should not be initialized during registering service providers but during booting. Return an error here if the telemetry facade is nil during booting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if the user sets the OTEL driver as the default or includes it in the stack, then log.NewApplication will try to resolve the telemetry channel instance, which depends on the telemetry facade. Since telemetry depends on log facade it will register and boot after it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically,
facades.*should be used inside a callback function if users want to use them inbootstrap.go.
Can we also add an annotation here explaining the different scenarios in which this(WithCallback) should be used? Since this is called before bootstrapping the service providers, it would be helpful to clarify that. Could you please add that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optimization is a bit complex, I need about two or three days. Please continue other parts for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, In mean time I will work on #1339.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughThis PR introduces comprehensive telemetry instrumentation support across HTTP clients, HTTP servers, logging, and gRPC services. It adds a telemetry resolver abstraction, configuration-driven feature flags for each instrumentation domain, lazy telemetry initialization, and conditional transport/handler wrapping to enable observability without breaking existing functionality. Changes
Sequence Diagram(s)sequenceDiagram
actor App as Application
participant Config as Config Service
participant Factory as Component Factory
participant TelemetryResolver as Telemetry Resolver
participant Telemetry as Telemetry Instance
participant Component as Instrumented Component
App->>Config: Load instrumentation config
App->>Factory: Initialize with TelemetryResolver
Factory->>Config: Check if instrumentation enabled
alt Instrumentation Disabled
Factory->>Component: Create base component (no wrapping)
else Instrumentation Enabled
Factory->>TelemetryResolver: Resolve telemetry
TelemetryResolver->>Telemetry: Return Telemetry instance
Factory->>Component: Create and wrap with OTEL instrumentation
Component->>Telemetry: Use TracerProvider, MeterProvider, Propagator
end
App->>Component: Use instrumented component
Component->>Telemetry: Emit traces/metrics
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
http/client/factory.go (1)
213-221: Bug: Fake transport wrapping bypasses telemetry transport.When both telemetry is enabled and fake state is active, the fake transport wraps
baseTransportinstead oftransport, which loses the telemetry instrumentation layer.The wrapping chain should be:
FakeTransport → TelemetryTransport → baseTransport, but currently it becomesFakeTransport → baseTransport.Suggested fix
var transport http.RoundTripper = baseTransport if cfg.EnableTelemetry && r.telemetryResolver != nil { transport = telemetryhttp.NewTransport(r.config, r.telemetryResolver(), transport) } if state != nil { // If testing mode is active, wrap the real transport with our interceptor. - transport = NewFakeTransport(state, baseTransport, r.json) + transport = NewFakeTransport(state, transport, r.json) }
🤖 Fix all issues with AI agents
In `@telemetry/instrumentation/http/middleware.go`:
- Around line 137-146: The slice baseAttrs is reused then appended to (when
combining with r.cfg.MetricAttributes and other attributes), which can mutate
shared backing storage across concurrent requests; fix by making a
capacity-limited copy before appending (e.g., create a new slice with capacity
== len(baseAttrs) via baseCopy := baseAttrs[:len(baseAttrs):len(baseAttrs)] or
baseCopy := append([]telemetry.KeyValue(nil), baseAttrs...) and then append to
baseCopy) and use that copy wherever you currently call append(baseAttrs, ...)
so each request gets its own backing array.
In `@telemetry/instrumentation/log/channel.go`:
- Around line 29-40: The Handle method on TelemetryChannel calls
r.config.GetBool and r.config.GetString without guarding r.config; add a
nil-check for r.config at the start of TelemetryChannel.Handle and if r.config
is nil return a disabled &handler{enabled:false} (similar to the gRPC handlers)
to avoid a panic; ensure subsequent uses (instrumentName retrieval and
enabled=true) only run when r.config is non-nil so references to
r.config.GetBool/GetString are safe.
🧹 Nitpick comments (3)
telemetry/instrumentation/http/middleware_test.go (1)
32-165: Good test coverage for core middleware functionality.The test suite covers the essential scenarios: successful tracing, custom filter blocking, excluded paths, config-based disabling, and panic propagation. The table-driven approach with setup callbacks is clean and maintainable.
As noted in previous reviews, consider adding tests for:
WithSpanNameFormatteroptionWithMetricAttributesoptionExcludedMethodsconfiguration- Nil telemetry facade handling
These can be addressed in a follow-up to complete the coverage.
telemetry/instrumentation/http/middleware.go (1)
68-70: Consider handling histogram creation errors.The errors from
Float64HistogramandInt64Histogramare silently discarded. While metric creation failures are typically non-fatal, logging a warning would aid debugging when telemetry isn't working as expected.♻️ Suggested improvement
- 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")) + durationHist, err := meter.Float64Histogram(metricRequestDuration, metric.WithUnit(unitSeconds), metric.WithDescription("Duration of HTTP server requests")) + if err != nil { + color.Warningln("Failed to create duration histogram:", err) + } + requestSizeHist, err := meter.Int64Histogram(metricRequestBodySize, metric.WithUnit(unitBytes), metric.WithDescription("Size of HTTP server request body")) + if err != nil { + color.Warningln("Failed to create request size histogram:", err) + } + responseSizeHist, err := meter.Int64Histogram(metricResponseBodySize, metric.WithUnit(unitBytes), metric.WithDescription("Size of HTTP server response body")) + if err != nil { + color.Warningln("Failed to create response size histogram:", err) + }http/client/factory_test.go (1)
473-506: Good integration test, minor redundancy.The test properly verifies telemetry integration by checking that the transport is wrapped with
otelhttp.Transport. However, lines 504-505 are redundant sinces.IsType(&otelhttp.Transport{}, ...)on line 502 already verifies the type.Optional: Remove redundant assertion
s.NotNil(httpClient.Transport) s.IsType(&otelhttp.Transport{}, httpClient.Transport) - - _, isPlainTransport := httpClient.Transport.(*http.Transport) - s.False(isPlainTransport) }
hwbrzzl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, left some questions. Could you add some screenshots for the real effect?
| // HTTP Server Instrumentation | ||
| // | ||
| // Configures the telemetry middleware for incoming HTTP requests. | ||
| "http_server": map[string]any{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about http.server, http.client, grpc.server, grpc.client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won’t using a dot make it look nested, even though it’s at the same level?
| // Disabling this acts as a global kill switch for sending logs to the OTel exporter, | ||
| // which can be useful for reducing cost/noise without changing logging config. | ||
| "log": map[string]any{ | ||
| "enabled": config.Env("OTEL_LOG_ENABLED", true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The log instrumentation will be enabled when instrumentation.log.enabled = true && log.channel contains Otel, correct? I wonder if it's necessary, one switch should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mainly for consistency with the other instrumentation. It might feel unnecessary, but the others also have a global kill switch.
| // "http.clients.{client_name}.enable_telemetry" to false for the | ||
| // corresponding client configuration. | ||
| "http_client": map[string]any{ | ||
| "enabled": config.Env("OTEL_HTTP_CLIENT_ENABLED", true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really. This is more like a global kill switch. For example, if you have 5 HTTP clients, instead of disabling all 5 individually, you can disable them all directly from here.



📑 Description
RelatedTo goravel/goravel#726
This pull request introduces OpenTelemetry instrumentation and configuration support to the HTTP client, improves dependency injection for telemetry components, and updates related tests and service provider registration. The changes make it possible to enable tracing and metrics for HTTP client requests through configuration, and ensure that telemetry dependencies are properly injected throughout the codebase.
OpenTelemetry integration and configuration:
EnableTelemetryfield to thehttp/client/config.goConfigstruct, allowing HTTP clients to enable OpenTelemetry tracing and metrics via configuration.http/client/factory.go) to accept a telemetry resolver and wrap the transport with OpenTelemetry instrumentation when enabled. [1] [2] [3]Dependency injection and service provider updates:
http/service_provider.go) to inject the telemetry resolver and config facade when creating the HTTP client factory, ensuring proper wiring of dependencies. [1] [2]Testing improvements:
http/client/factory_test.go) to use mock telemetry and config, and added an integration test verifying telemetry instrumentation is applied to the HTTP client transport. [1] [2]Telemetry contract changes:
Resolvertype alias to the telemetry contract for improved dependency injection.Go module dependency updates:
httpsnoopas dependencies ingo.mod. [1] [2]✅ Checks
Summary by CodeRabbit
Release Notes
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.