-
Notifications
You must be signed in to change notification settings - Fork 0
AB#486991 open telemetry #34
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
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 pull request adds OpenTelemetry instrumentation to the codebase as an alternative/complementary telemetry solution alongside the existing DataDog StatsD integration. The implementation introduces support for OpenTelemetry traces, metrics, and logs, enabling observability through OTLP exporters and optional Prometheus metrics.
Key Changes
- Added comprehensive OpenTelemetry SDK setup with support for traces, metrics, and logs via OTLP exporters
- Refactored shared logging structures (
logMsg,iso8601) fromstatsd.gotomiddlewares.gofor reuse across telemetry implementations - Extended metrics monitoring to emit both DataDog and OpenTelemetry metrics simultaneously
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| middlewares/otel.go | New file implementing OpenTelemetry setup, log writer, and event logger integration with zap |
| middlewares/statsd.go | Removed logMsg struct and related code, now consolidated in middlewares.go |
| middlewares/middlewares.go | Added shared logMsg struct and iso8601 constant moved from statsd.go, plus encoding/json import |
| middlewares/metrics.go | Extended monitor() function to emit OpenTelemetry metrics alongside DataDog metrics |
| go.mod | Added OpenTelemetry dependencies and updated Go version/toolchain specifications |
| go.sum | Updated checksums for new and updated dependencies |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
middlewares/metrics.go
Outdated
| if err != nil { | ||
| return | ||
| } | ||
| ctx := context.Background() |
Copilot
AI
Dec 9, 2025
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.
Using context.Background() here loses any context propagation from the HTTP request. Consider passing the request context (e.g., from r.Context()) through to this function to maintain context propagation for tracing and cancellation.
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.
We don't have the context for now.
| func (o OpenTelemetryWriter) Write(p []byte) (n int, err error) { | ||
| var msg logMsg | ||
| if err = json.Unmarshal(p, &msg); err != nil { | ||
| return | ||
| } | ||
| n = len(p) | ||
|
|
||
| var timestamp time.Time | ||
| timestamp, err = time.Parse(iso8601, msg.Timestamp) | ||
| if err != nil { | ||
| return | ||
| } | ||
|
|
||
| tags := msg.Tags | ||
| if tags == nil { | ||
| tags = []string{} | ||
| } | ||
| tagValues := make([]log.Value, len(tags)) | ||
| for i, v := range tags { | ||
| tagValues[i] = log.StringValue(v) | ||
| } | ||
| tagValue := log.SliceValue(tagValues...) | ||
|
|
||
| severity := log.SeverityDebug | ||
| severityText := "debug" | ||
| if sm, ok := severityMap[msg.Level]; ok { | ||
| severity = sm | ||
| severityText = msg.Level | ||
| } | ||
|
|
||
| record := log.Record{} | ||
| record.SetTimestamp(timestamp) | ||
| record.SetEventName(msg.Name) | ||
| record.SetSeverity(severity) | ||
| record.SetSeverityText(severityText) | ||
| record.AddAttributes(log.KeyValue{ | ||
| Key: "tags", | ||
| Value: tagValue, | ||
| }) | ||
| record.SetBody(log.StringValue(msg.Text())) | ||
|
|
||
| o.logger.Emit(o.ctx, record) | ||
| return | ||
| } |
Copilot
AI
Dec 9, 2025
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 error handling in the Write method is incorrect. When an error occurs (lines 155-156 or 161-163), the function returns with n = 0 initially, but if the error occurs after line 158, n is already set to len(p). This violates the io.Writer contract which states that if an error is returned, the number of bytes written should accurately reflect what was successfully written (typically 0 for a failed write). The n value should only be set at the end if the write is successful.
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.
It is not a big problem but this is true, We should follow this pattern
middlewares/metrics.go
Outdated
| oCount, err := oMeter.Int64Counter("http_request_count") | ||
| if err != nil { | ||
| return | ||
| } | ||
| oDuration, err := oMeter.Float64Histogram("http_request_duration") | ||
| if err != nil { | ||
| return | ||
| } | ||
| oStatusCount, err := oMeter.Int64Counter(fmt.Sprintf("http_request_status_%s", statusType(httpCode))) | ||
| if err != nil { | ||
| return | ||
| } |
Copilot
AI
Dec 9, 2025
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.
Creating new metric instruments (Counter/Histogram) on every request is inefficient. These instruments should be created once at initialization time and reused. Consider creating these instruments in an init function or during setup, and storing them as package-level variables or in a struct.
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.
Thank you for your work on this! I noticed that in the official OpenTelemetry example, variables like oCount, oDuration, and oStatusCount are created once at startup, since they are not global. While the end result is the same, creating these instruments on every request could be inefficient.
I think We could store the instruments as package-level variables, and for dynamic counters like http_request_status_%s, use a map to create them only if they don’t already exist. What do you think about this approach? Let me know if there’s a specific reason for the current implementation!
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.
Yeah, I was thinking about this last night, and we probably need a global map of counters etc. Will get this done this afternoon.
middlewares/metrics.go
Outdated
| oCount, err := oMeter.Int64Counter("http_request_count") | ||
| if err != nil { | ||
| return | ||
| } | ||
| oDuration, err := oMeter.Float64Histogram("http_request_duration") | ||
| if err != nil { | ||
| return | ||
| } | ||
| oStatusCount, err := oMeter.Int64Counter(fmt.Sprintf("http_request_status_%s", statusType(httpCode))) | ||
| if err != nil { | ||
| return | ||
| } |
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.
Thank you for your work on this! I noticed that in the official OpenTelemetry example, variables like oCount, oDuration, and oStatusCount are created once at startup, since they are not global. While the end result is the same, creating these instruments on every request could be inefficient.
I think We could store the instruments as package-level variables, and for dynamic counters like http_request_status_%s, use a map to create them only if they don’t already exist. What do you think about this approach? Let me know if there’s a specific reason for the current implementation!
middlewares/metrics.go
Outdated
| if err != nil { | ||
| return | ||
| } | ||
| ctx := context.Background() |
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.
We don't have the context for now.
| func (o OpenTelemetryWriter) Write(p []byte) (n int, err error) { | ||
| var msg logMsg | ||
| if err = json.Unmarshal(p, &msg); err != nil { | ||
| return | ||
| } | ||
| n = len(p) | ||
|
|
||
| var timestamp time.Time | ||
| timestamp, err = time.Parse(iso8601, msg.Timestamp) | ||
| if err != nil { | ||
| return | ||
| } | ||
|
|
||
| tags := msg.Tags | ||
| if tags == nil { | ||
| tags = []string{} | ||
| } | ||
| tagValues := make([]log.Value, len(tags)) | ||
| for i, v := range tags { | ||
| tagValues[i] = log.StringValue(v) | ||
| } | ||
| tagValue := log.SliceValue(tagValues...) | ||
|
|
||
| severity := log.SeverityDebug | ||
| severityText := "debug" | ||
| if sm, ok := severityMap[msg.Level]; ok { | ||
| severity = sm | ||
| severityText = msg.Level | ||
| } | ||
|
|
||
| record := log.Record{} | ||
| record.SetTimestamp(timestamp) | ||
| record.SetEventName(msg.Name) | ||
| record.SetSeverity(severity) | ||
| record.SetSeverityText(severityText) | ||
| record.AddAttributes(log.KeyValue{ | ||
| Key: "tags", | ||
| Value: tagValue, | ||
| }) | ||
| record.SetBody(log.StringValue(msg.Text())) | ||
|
|
||
| o.logger.Emit(o.ctx, record) | ||
| return | ||
| } |
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.
It is not a big problem but this is true, We should follow this pattern
go.mod
Outdated
| go 1.24 | ||
| go 1.24.0 | ||
|
|
||
| toolchain go1.24.4 |
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.
It is not common on our projects to have this, I usually avoid to do this, is this expected?
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, I'm pretty sure GoLand "helpfully" added this for me. I'll revert 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.
This is apparently a new thing as of go 1.21 - just running go build or go mod tidy from the command line insists on adding it. 😖 I think we have to live with it. cli/cli#9489
gzapatas
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.
LGTM!
https://dev.azure.com/nintex/Nintex/_workitems/edit/486991
This will be the basis for v1.0.23. Clortho PR: https://github.com/skuid/clortho/pull/79