Skip to content
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

feat: Add initial OpenTelemetry support #537

Merged
merged 92 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
fbdee03
Initial commit
cleptric Dec 14, 2022
10d9fc7
Propagtor stub
cleptric Dec 14, 2022
94528bf
Some wip
cleptric Dec 14, 2022
3017b5f
Rename module
cleptric Jan 10, 2023
cd5eb04
Merge branch 'master' into otel
cleptric Jan 10, 2023
2aa76fb
OnStart
cleptric Jan 10, 2023
eb8ca94
WIP OnEnd
cleptric Jan 10, 2023
c92d7c1
Add more deps
cleptric Jan 10, 2023
a167dc8
a few TODOs
tonyo Jan 10, 2023
bcc5883
tracing: Add constants for trace/baggage headers
tonyo Jan 11, 2023
3828a27
otel: Take sentry-go dep from the parent dir
tonyo Jan 11, 2023
387cccb
Update propagator skeleton
tonyo Jan 11, 2023
0b46067
propagator: Add basic tests for Extract
tonyo Jan 11, 2023
d1f4230
propagator: Basic Inject
tonyo Jan 11, 2023
8f516ec
propagator: Simplify and add some questions
tonyo Jan 11, 2023
e56106e
Merge branch 'master' into otel
cleptric Jan 12, 2023
9a3084a
WIP: span processor
cleptric Jan 12, 2023
3eadbb0
propagator: Finalize Extract
tonyo Jan 12, 2023
b7faad3
dynamic_sampling: Update baggage functions
tonyo Jan 13, 2023
fb41831
Use the global span map
tonyo Jan 13, 2023
ac162af
propagator: Do not always freeze DSC
tonyo Jan 13, 2023
5e3f864
Export some stuff
cleptric Jan 13, 2023
fc9d4fc
Revert
cleptric Jan 13, 2023
a89ccc1
Update sentryr request
tonyo Jan 13, 2023
0750a71
Merge branch 'master' into otel
cleptric Jan 16, 2023
19ce9da
Merge branch 'master' into otel
cleptric Jan 16, 2023
d5eb007
propagator: Propagate sentry-trace and baggage properly
tonyo Jan 16, 2023
56cbcf6
propagator: Rename file
tonyo Jan 16, 2023
26a8413
Add IsTransaction()
cleptric Jan 16, 2023
a403b63
Remove comment
cleptric Jan 16, 2023
dd212e9
Add the otel context and transaction name on the scope
cleptric Jan 16, 2023
b186935
Initialize map to prevent a panic
cleptric Jan 16, 2023
8469abc
Improve span attribute parsing
cleptric Jan 16, 2023
9311e75
Add note
cleptric Jan 16, 2023
07e7fb0
Add more TODOs
cleptric Jan 16, 2023
6dfa76b
Revert Span.ToBaggage() changes
tonyo Jan 17, 2023
c467a4b
propagator: use IsTransaction
tonyo Jan 17, 2023
933f741
test: Add a helper function to compare MapCarrier
tonyo Jan 17, 2023
4d0ed1d
Remove print
tonyo Jan 17, 2023
4874be9
Improve map init
cleptric Jan 17, 2023
682664c
Set default span op to “”
cleptric Jan 18, 2023
016f018
fix: Ensure correct sentry-trace header
tonyo Jan 18, 2023
3fab098
Set otel context
cleptric Jan 18, 2023
ff24d1e
Add a few TODOs/comments
tonyo Jan 18, 2023
6b42a3f
Basic debug info
tonyo Jan 18, 2023
d045c79
span-processor: Set trace-id and parent-span-id on the new transaction
tonyo Jan 18, 2023
700c534
Add SetDynamicSamplingContext method
tonyo Jan 18, 2023
665338c
Merge remote-tracking branch 'origin/master' into otel
tonyo Jan 18, 2023
0304bb0
propagator: Fix tests
tonyo Jan 18, 2023
14777ba
Improve span status mapping
cleptric Jan 18, 2023
67ec4d9
Add Span.SetData()
cleptric Jan 18, 2023
38df414
Add some comments
cleptric Jan 18, 2023
6b2e602
Add more span attributes
cleptric Jan 18, 2023
5ab22eb
Remove obsolete TODO
cleptric Jan 18, 2023
1ae2455
Remove TODO
cleptric Jan 19, 2023
0b6c649
Revert DynamicSamplingContext.ToBaggage
tonyo Jan 19, 2023
f66284c
Merge branch 'master' into otel
cleptric Jan 19, 2023
3c506e5
Revert tracing_test.go changes
tonyo Jan 19, 2023
3f3bc99
Fix merge error
cleptric Jan 19, 2023
0008bdd
Pass trace parent context from propagator to span-processor
tonyo Jan 19, 2023
e914634
Add go-cmp
tonyo Jan 19, 2023
87ec1ab
propagator: Add tests for Inject
tonyo Jan 19, 2023
ab628b4
propagator: More tests
tonyo Jan 20, 2023
9de4965
Merge branch 'master' into otel
cleptric Jan 23, 2023
cc4a7e9
Prioritize HTTP/gRPC status codes
cleptric Jan 23, 2023
bf8c6da
Fix propagator tests
tonyo Jan 23, 2023
07748a6
span-processor: Move span map to a separate file
tonyo Jan 23, 2023
4776471
Revert changes in tracing.go
tonyo Jan 24, 2023
f875078
Revert SpanSampled
tonyo Jan 24, 2023
ab64b89
Merge remote-tracking branch 'origin/master' into otel
tonyo Jan 24, 2023
692bd01
Add go-build guard: require at least Go 1.18
tonyo Jan 24, 2023
610c931
Rename internal dir
tonyo Jan 24, 2023
43f9988
workaround: Add "dummy" package
tonyo Jan 24, 2023
00c1f94
Fix test-coverage
tonyo Jan 24, 2023
ca61857
Merge remote-tracking branch 'origin/master' into otel
tonyo Jan 24, 2023
c833e55
span-processor: start tests
tonyo Jan 24, 2023
10061fe
span-processor: Basic tests for OnStart and OnEnd
tonyo Jan 25, 2023
6cc830b
span-processor: Update ShutDown and ForceFlush
tonyo Jan 25, 2023
34cc87f
Simplify context key operations
tonyo Jan 26, 2023
786e7b9
Remove a few TODOs
tonyo Jan 26, 2023
7e95e60
span-processor: Some more tests for OnEnd, Shutdown, ForceFlush
tonyo Jan 27, 2023
9f5114b
helpers_test: Copy TransportMock from the root package
tonyo Jan 27, 2023
fd95102
sentryrequest: Pass context to avoid hub issues
tonyo Jan 27, 2023
a746f3d
span-processor: Fixes and tests
tonyo Jan 27, 2023
bdbff3d
span-processor: Add asserts for span attributes
tonyo Jan 27, 2023
6bb3415
span-processor: Minor test cleanup
tonyo Jan 27, 2023
ed94b89
Add Sentry event processor to link events and transactions
tonyo Jan 30, 2023
2ece482
Remove bogus asserts
tonyo Jan 30, 2023
50233c7
Add a few comments
tonyo Jan 30, 2023
7890597
Use sentry.Logger for debug output
tonyo Jan 30, 2023
9e3b31f
Minor updates to constructor tests
tonyo Jan 30, 2023
b2c35df
Remove debug output
tonyo Jan 31, 2023
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
8 changes: 8 additions & 0 deletions otel/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//go:build go1.18

package sentryotel

// Context keys to be used with context.WithValue(...) and ctx.Value(...)
type dynamicSamplingContextKey struct{}
type sentryTraceHeaderContextKey struct{}
type sentryTraceParentContextKey struct{}
38 changes: 38 additions & 0 deletions otel/event_processor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build go1.18

package sentryotel

import (
"github.com/getsentry/sentry-go"
"go.opentelemetry.io/otel/trace"
)

// linkTraceContextToErrorEvent is a Sentry event processor that attaches trace information
// to the error event.
//
// Caveat: hint.Context should contain a valid context populated by OpenTelemetry's span context.
func linkTraceContextToErrorEvent(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
if hint == nil || hint.Context == nil {
return event
}
otelSpanContext := trace.SpanContextFromContext(hint.Context)
var sentrySpan *sentry.Span
if otelSpanContext.IsValid() {
sentrySpan, _ = sentrySpanMap.Get(otelSpanContext.SpanID())
}
if sentrySpan == nil {
return event
}

traceContext := event.Contexts["trace"]
if len(traceContext) > 0 {
// trace context is already set, not touching it
return event
}
event.Contexts["trace"] = map[string]interface{}{
"trace_id": sentrySpan.TraceID.String(),
"span_id": sentrySpan.SpanID.String(),
"parent_span_id": sentrySpan.ParentSpanID.String(),
}
return event
}
68 changes: 68 additions & 0 deletions otel/event_processor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//go:build go1.18

package sentryotel

import (
"errors"
"testing"

"github.com/getsentry/sentry-go"
)

func TestLinkTraceContextToErrorEventWithEmptyTraceContext(t *testing.T) {
_, _, tracer := setupSpanProcessorTest()
ctx, otelSpan := tracer.Start(emptyContextWithSentry(), "spanName")
sentrySpan, _ := sentrySpanMap.Get(otelSpan.SpanContext().SpanID())

hub := sentry.GetHubFromContext(ctx)
client := hub.Client()
client.CaptureException(
errors.New("new sentry exception"),
&sentry.EventHint{Context: ctx},
nil,
)

transport := client.Transport.(*TransportMock)
events := transport.Events()
assertEqual(t, len(events), 1)
err := events[0]
exception := err.Exception[0]
assertEqual(t, exception.Type, "*errors.errorString")
assertEqual(t, exception.Value, "new sentry exception")
assertEqual(t, err.Type, "")
assertEqual(t,
err.Contexts["trace"],
map[string]interface{}{
"trace_id": sentrySpan.TraceID.String(),
"span_id": sentrySpan.SpanID.String(),
"parent_span_id": sentrySpan.ParentSpanID.String(),
},
)
}

func TestLinkTraceContextToErrorEventDoesNotTouchExistingTraceContext(t *testing.T) {
_, _, tracer := setupSpanProcessorTest()
ctx, _ := tracer.Start(emptyContextWithSentry(), "spanName")

hub := sentry.GetHubFromContext(ctx)
hub.Scope().SetContext("trace", map[string]interface{}{"trace_id": "123"})
client := hub.Client()
client.CaptureException(
errors.New("new sentry exception with existing trace context"),
&sentry.EventHint{Context: ctx},
hub.Scope(),
)

transport := client.Transport.(*TransportMock)
events := transport.Events()
assertEqual(t, len(events), 1)
err := events[0]
exception := err.Exception[0]
assertEqual(t, exception.Type, "*errors.errorString")
assertEqual(t, exception.Value, "new sentry exception with existing trace context")
assertEqual(t, err.Type, "")
assertEqual(t,
err.Contexts["trace"],
map[string]interface{}{"trace_id": "123"},
)
}
20 changes: 20 additions & 0 deletions otel/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module github.com/getsentry/sentry-go/otel

go 1.19

require (
github.com/getsentry/sentry-go v0.17.0
github.com/google/go-cmp v0.5.9
go.opentelemetry.io/otel v1.11.2
go.opentelemetry.io/otel/sdk v1.11.2
go.opentelemetry.io/otel/trace v1.11.2
)

replace github.com/getsentry/sentry-go => ../

require (
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
)
24 changes: 24 additions & 0 deletions otel/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0=
go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI=
go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU=
go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU=
go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0=
go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
181 changes: 181 additions & 0 deletions otel/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//go:build go1.18

package sentryotel

// TODO(anton): This is a copy of helpers_test.go in the repo root.
// We should figure out how to share testing helpers.

import (
"encoding/hex"
"fmt"
"log"
"reflect"
"sort"
"sync"
"testing"
"time"

"github.com/getsentry/sentry-go"
"github.com/google/go-cmp/cmp"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)

func assertEqual(t *testing.T, got, want interface{}, userMessage ...interface{}) {
t.Helper()

if !reflect.DeepEqual(got, want) {
logFailedAssertion(t, formatUnequalValues(got, want), userMessage...)
}
}

func assertNotEqual(t *testing.T, got, want interface{}, userMessage ...interface{}) {
t.Helper()

if reflect.DeepEqual(got, want) {
logFailedAssertion(t, formatUnequalValues(got, want), userMessage...)
}
}

func logFailedAssertion(t *testing.T, summary string, userMessage ...interface{}) {
t.Helper()
text := summary

if len(userMessage) > 0 {
if message, ok := userMessage[0].(string); ok {
if message != "" && len(userMessage) > 1 {
text = fmt.Sprintf(message, userMessage[1:]...) + text
} else if message != "" {
text = fmt.Sprint(message) + text
}
}
}

t.Error(text)
}

func formatUnequalValues(got, want interface{}) string {
var a, b string

if reflect.TypeOf(got) != reflect.TypeOf(want) {
a, b = fmt.Sprintf("%T(%#v)", got, got), fmt.Sprintf("%T(%#v)", want, want)
} else {
a, b = fmt.Sprintf("%#v", got), fmt.Sprintf("%#v", want)
}

return fmt.Sprintf("\ngot: %s\nwant: %s", a, b)
}

// assertMapCarrierEqual compares two values of type propagation.MapCarrier and raises an
// assertion error if the values differ.
//
// It is needed because some headers (e.g. "baggage") might contain the same set of values/attributes,
// (and therefore be semantically equal), but serialized in different order.
func assertMapCarrierEqual(t *testing.T, got, want propagation.MapCarrier, userMessage ...interface{}) {
// Make sure that keys are the same
gotKeysSorted := got.Keys()
sort.Strings(gotKeysSorted)
wantKeysSorted := want.Keys()
sort.Strings(wantKeysSorted)

if diff := cmp.Diff(wantKeysSorted, gotKeysSorted); diff != "" {
t.Errorf("Comparing MapCarrier keys (-want +got):\n%s", diff)
}

for _, key := range gotKeysSorted {
gotValue := got.Get(key)
wantValue := want.Get(key)

// Ignore serialization order for baggage values
if key == sentry.SentryBaggageHeader {
gotBaggage, gotErr := baggage.Parse(gotValue)
wantBaggage, wantErr := baggage.Parse(wantValue)

if diff := cmp.Diff(wantErr, gotErr); diff != "" {
t.Errorf("Comparing Baggage parsing errors (-want +got):\n%s", diff)
}

// sortedBaggage = gotBaggage.Members()

if diff := cmp.Diff(
wantBaggage,
gotBaggage,
cmp.AllowUnexported(baggage.Member{}, baggage.Baggage{}),
); diff != "" {
t.Errorf("Comparing Baggage values (-want +got):\n%s", diff)
}
continue
}

// Everything else: do the exact comparison
if diff := cmp.Diff(wantValue, gotValue); diff != "" {
t.Errorf("Comparing MapCarrier values (-want +got):\n%s", diff)
}
}
}

// FIXME: copied from tracing_test.go
func TraceIDFromHex(s string) sentry.TraceID {
var id sentry.TraceID
_, err := hex.Decode(id[:], []byte(s))
if err != nil {
panic(err)
}
return id
}

func SpanIDFromHex(s string) sentry.SpanID {
var id sentry.SpanID
_, err := hex.Decode(id[:], []byte(s))
if err != nil {
panic(err)
}
return id
}

func stringPtr(s string) *string {
return &s
}

func otelTraceIDFromHex(s string) trace.TraceID {
traceID, err := trace.TraceIDFromHex(s)
if err != nil {
log.Fatalf("Cannot make a TraceID from the hex string: '%s'", s)
}
return traceID
}

func otelSpanIDFromHex(s string) trace.SpanID {
spanID, err := trace.SpanIDFromHex(s)
if err != nil {
log.Fatalf("Cannot make a SPanID from the hex string: '%s'", s)
}
return spanID
}

// FIXME(anton): copie from mocks_test.go

type TransportMock struct {
mu sync.Mutex
events []*sentry.Event
lastEvent *sentry.Event
}

func (t *TransportMock) Configure(options sentry.ClientOptions) {}
func (t *TransportMock) SendEvent(event *sentry.Event) {
t.mu.Lock()
defer t.mu.Unlock()
t.events = append(t.events, event)
t.lastEvent = event
}
func (t *TransportMock) Flush(timeout time.Duration) bool {
return true
}
func (t *TransportMock) Events() []*sentry.Event {
t.mu.Lock()
defer t.mu.Unlock()
return t.events
}

//
9 changes: 9 additions & 0 deletions otel/internal/dummy/dummy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dummy

// This package is intentionally left empty.
// Reason: the otel module currenty requires go>=1.18. All files in the module have '//go:build go1.18' guards, so
// with go1.17 "go test" might fail with the error: "go: warning: "./..." matched no packages; no packages to test".
// As a workaround, we added this empty "dummy" package, which is the only package without the compiler version restrictions,
// so at least the compiler doesn't complain that there are no packages to test.
//
// This file and package can be removed when we drop support for 1.17.
Loading