diff --git a/go.mod b/go.mod index 9429a82..3b68280 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/go-coldbrew/log v0.2.7 github.com/go-coldbrew/options v0.2.6 github.com/google/uuid v1.6.0 - github.com/opentracing/opentracing-go v1.2.0 github.com/rollbar/rollbar-go v1.4.8 + go.opentelemetry.io/otel/trace v1.42.0 google.golang.org/grpc v1.79.3 ) @@ -236,6 +236,7 @@ require ( go-simpler.org/sloglint v0.11.1 // indirect go.augendre.info/arangolint v0.4.0 // indirect go.augendre.info/fatcontext v0.9.0 // indirect + go.opentelemetry.io/otel v1.42.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect @@ -250,8 +251,8 @@ require ( golang.org/x/text v0.35.0 // indirect golang.org/x/tools v0.43.0 // indirect golang.org/x/vuln v1.1.4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index cc4e508..5abe39f 100644 --- a/go.sum +++ b/go.sum @@ -553,8 +553,6 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -756,6 +754,10 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -1096,8 +1098,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1124,8 +1126,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/notifier/README.md b/notifier/README.md index 6f90625..fea58dc 100755 --- a/notifier/README.md +++ b/notifier/README.md @@ -39,7 +39,7 @@ import "github.com/go-coldbrew/errors/notifier" -## func [Close]() +## func [Close]() ```go func Close() @@ -48,7 +48,7 @@ func Close() Close closes the airbrake notifier and flushes pending Sentry events. Sentry events are flushed with a 2 second timeout. You should call Close before app shutdown. Close doesn't call os.Exit. -## func [GetTraceHeaderName]() +## func [GetTraceHeaderName]() ```go func GetTraceHeaderName() string @@ -57,7 +57,7 @@ func GetTraceHeaderName() string GetTraceHeaderName gets the header name for trace id default is x\-trace\-id -## func [GetTraceId]() +## func [GetTraceId]() ```go func GetTraceId(ctx context.Context) string @@ -66,7 +66,7 @@ func GetTraceId(ctx context.Context) string GetTraceId fetches traceID from context if no trace id is found then it will return empty string -## func [InitAirbrake]() +## func [InitAirbrake]() ```go func InitAirbrake(projectID int64, projectKey string) @@ -75,7 +75,7 @@ func InitAirbrake(projectID int64, projectKey string) InitAirbrake inits airbrake configuration projectID: airbrake project id projectKey: airbrake project key -## func [InitRollbar]() +## func [InitRollbar]() ```go func InitRollbar(token, env string) @@ -84,7 +84,7 @@ func InitRollbar(token, env string) InitRollbar inits rollbar configuration token: rollbar token env: rollbar environment -## func [InitSentry]() +## func [InitSentry]() ```go func InitSentry(dsn string) @@ -93,7 +93,7 @@ func InitSentry(dsn string) InitSentry inits sentry configuration dsn: sentry dsn -## func [Notify]() +## func [Notify]() ```go func Notify(err error, rawData ...interface{}) error @@ -102,7 +102,7 @@ func Notify(err error, rawData ...interface{}) error Notify notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata -## func [NotifyAsync]() +## func [NotifyAsync]() ```go func NotifyAsync(err error, rawData ...interface{}) error @@ -111,7 +111,7 @@ func NotifyAsync(err error, rawData ...interface{}) error NotifyAsync sends an error notification asynchronously with bounded concurrency. If the async notification pool is full, the notification is dropped to prevent goroutine explosion under sustained error bursts. Returns the original error for convenience. -## func [NotifyOnPanic]() +## func [NotifyOnPanic]() ```go func NotifyOnPanic(rawData ...interface{}) @@ -120,7 +120,7 @@ func NotifyOnPanic(rawData ...interface{}) NotifyOnPanic notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata this function should be called in defer example: defer NotifyOnPanic\(ctx, "some data"\) example: defer NotifyOnPanic\(ctx, "some data", Tags\{"tag1": "value1"\}\) -## func [NotifyWithExclude]() +## func [NotifyWithExclude]() ```go func NotifyWithExclude(err error, rawData ...interface{}) error @@ -129,7 +129,7 @@ func NotifyWithExclude(err error, rawData ...interface{}) error NotifyWithExclude notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata -## func [NotifyWithLevel]() +## func [NotifyWithLevel]() ```go func NotifyWithLevel(err error, level string, rawData ...interface{}) error @@ -138,7 +138,7 @@ func NotifyWithLevel(err error, level string, rawData ...interface{}) error NotifyWithLevel notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify level: error level rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata -## func [NotifyWithLevelAndSkip]() +## func [NotifyWithLevelAndSkip]() ```go func NotifyWithLevelAndSkip(err error, skip int, level string, rawData ...interface{}) error @@ -147,7 +147,7 @@ func NotifyWithLevelAndSkip(err error, skip int, level string, rawData ...interf NotifyWithLevelAndSkip notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify skip: skip stack frames when notify error level: error level rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata -## func [SetEnvironment]() +## func [SetEnvironment]() ```go func SetEnvironment(env string) @@ -156,7 +156,7 @@ func SetEnvironment(env string) SetEnvironment sets the environment. The environment is used to distinguish errors occurring in different -## func [SetHostname]() +## func [SetHostname]() ```go func SetHostname(name string) @@ -165,7 +165,7 @@ func SetHostname(name string) SetHostname sets the hostname of the server. The hostname is used to identify the server that logged an error. -## func [SetMaxAsyncNotifications]() +## func [SetMaxAsyncNotifications]() ```go func SetMaxAsyncNotifications(n int) @@ -174,7 +174,7 @@ func SetMaxAsyncNotifications(n int) SetMaxAsyncNotifications sets the maximum number of concurrent async notification goroutines. When the limit is reached, new async notifications are dropped to prevent goroutine explosion under sustained error bursts. Default is 1000. Can only be called once; subsequent calls are no\-ops. -## func [SetRelease]() +## func [SetRelease]() ```go func SetRelease(rel string) @@ -183,7 +183,7 @@ func SetRelease(rel string) SetRelease sets the release tag. The release tag is used to group errors together by release. -## func [SetServerRoot]() +## func [SetServerRoot]() ```go func SetServerRoot(path string) @@ -192,7 +192,7 @@ func SetServerRoot(path string) SetServerRoot sets the root directory of the project. The root directory is used to trim prefixes from filenames in stack traces. -## func [SetTraceHeaderName]() +## func [SetTraceHeaderName]() ```go func SetTraceHeaderName(name string) @@ -201,7 +201,7 @@ func SetTraceHeaderName(name string) SetTraceHeaderName sets the header name for trace id default is x\-trace\-id -## func [SetTraceId]() +## func [SetTraceId]() ```go func SetTraceId(ctx context.Context) context.Context @@ -210,7 +210,7 @@ func SetTraceId(ctx context.Context) context.Context SetTraceId updates the traceID based on context values if no trace id is found then it will create one and update the context You should use the context returned by this function instead of the one passed -## func [UpdateTraceId]() +## func [UpdateTraceId]() ```go func UpdateTraceId(ctx context.Context, traceID string) context.Context @@ -219,7 +219,7 @@ func UpdateTraceId(ctx context.Context, traceID string) context.Context UpdateTraceId force updates the traced id to provided id if no trace id is found then it will create one and update the context You should use the context returned by this function instead of the one passed -## type [Tags]() +## type [Tags]() diff --git a/notifier/notifier.go b/notifier/notifier.go index 83db796..165ec9e 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -17,8 +17,8 @@ import ( "github.com/go-coldbrew/log/loggers" "github.com/go-coldbrew/options" "github.com/google/uuid" - stdopentracing "github.com/opentracing/opentracing-go" rollbar "github.com/rollbar/rollbar-go" + oteltrace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" ) @@ -354,16 +354,20 @@ func doNotify(err error, skip int, level string, rawData ...interface{}) error { } } - // try to fetch a traceID and context from rawData - var traceID string + // try to fetch a correlation ID and OTEL trace ID from rawData + var correlationID string + var otelTraceID string ctx := context.Background() for _, d := range list { if c, ok := d.(context.Context); ok { - if span := stdopentracing.SpanFromContext(c); span != nil { - traceID = span.BaggageItem("trace") - } - if strings.TrimSpace(traceID) == "" { - traceID = GetTraceId(c) + // Application-level correlation ID (set via SetTraceId) takes precedence. + correlationID = GetTraceId(c) + // OTEL distributed trace ID is captured separately for linking. + if span := oteltrace.SpanFromContext(c); span.SpanContext().IsValid() { + otelTraceID = span.SpanContext().TraceID().String() + if strings.TrimSpace(correlationID) == "" { + correlationID = otelTraceID + } } ctx = c break @@ -379,8 +383,11 @@ func doNotify(err error, skip int, level string, rawData ...interface{}) error { n.Context[k] = v } } - if traceID != "" { - n.Context["traceId"] = traceID + if correlationID != "" { + n.Context["traceId"] = correlationID + } + if otelTraceID != "" { + n.Context["otelTraceId"] = otelTraceID } airbrake.SendNoticeAsync(n) } @@ -391,8 +398,11 @@ func doNotify(err error, skip int, level string, rawData ...interface{}) error { for k, v := range parsedData { extras[k] = v } - if traceID != "" { - extras["traceId"] = traceID + if correlationID != "" { + extras["traceId"] = correlationID + } + if otelTraceID != "" { + extras["otelTraceId"] = otelTraceID } extras["server"] = map[string]interface{}{"hostname": getHostname(), "root": getServerRoot()} rollbar.ErrorWithStackSkipWithExtras(level, errWithStack, skip+1, extras) @@ -523,8 +533,8 @@ func SetTraceId(ctx context.Context) context.Context { traceID = strings.Join(id, ",") } } - if span := stdopentracing.SpanFromContext(ctx); span != nil && strings.TrimSpace(traceID) == "" { - traceID = span.BaggageItem("trace") + if span := oteltrace.SpanFromContext(ctx); span.SpanContext().IsValid() && strings.TrimSpace(traceID) == "" { + traceID = span.SpanContext().TraceID().String() } // if no trace id then create one if strings.TrimSpace(traceID) == "" {