diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85e7c1d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 0d469f2..0000000 --- a/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @coralogix/team-veni-vici \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7d0ae2f --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module com.coralogix.opentelemetry-go + +go 1.17 + +require ( + github.com/stretchr/testify v1.8.1 + go.opentelemetry.io/otel v1.11.2 + go.opentelemetry.io/otel/sdk v1.11.2 + go.opentelemetry.io/otel/trace v1.11.2 + +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4a240ff --- /dev/null +++ b/go.sum @@ -0,0 +1,32 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sampler/coralogix-sampler.go b/sampler/coralogix-sampler.go new file mode 100644 index 0000000..308d175 --- /dev/null +++ b/sampler/coralogix-sampler.go @@ -0,0 +1,87 @@ +package sampler + +import ( + "context" + "go.opentelemetry.io/otel/attribute" + traceSdk "go.opentelemetry.io/otel/sdk/trace" + traceCore "go.opentelemetry.io/otel/trace" +) + +const ( + TransactionIdentifier = "cgx.transaction" + TransactionIdentifierTraceState = "cgx_transaction" + DistributedTransactionIdentifier = "cgx.transaction.distributed" + DistributedTransactionIdentifierTraceState = "cgx_transaction_distributed" +) + +type CoralogixSampler struct { + adaptedSampler traceSdk.Sampler +} + +func NewCoralogixSampler(adaptedSampler traceSdk.Sampler) *CoralogixSampler { + if adaptedSampler == nil { + panic("sampler is null") + } + return &CoralogixSampler{ + adaptedSampler: adaptedSampler, + } +} + +func (s *CoralogixSampler) ShouldSample(parameters traceSdk.SamplingParameters) traceSdk.SamplingResult { + adaptedSamplingResult := s.adaptedSampler.ShouldSample(parameters) + + return s.generateTransactionSamplingResult(parameters.ParentContext, parameters.Name, adaptedSamplingResult) +} + +func (s *CoralogixSampler) generateTransactionSamplingResult(ctx context.Context, name string, adaptedSamplingResult traceSdk.SamplingResult) traceSdk.SamplingResult { + newTracingState := s.generateNewTraceState(ctx, name, adaptedSamplingResult) + newAttributes := s.injectAttributes(adaptedSamplingResult, newTracingState) + return traceSdk.SamplingResult{ + Decision: adaptedSamplingResult.Decision, + Attributes: newAttributes, + Tracestate: newTracingState, + } +} + +func (s *CoralogixSampler) injectAttributes(adaptedSamplingResult traceSdk.SamplingResult, newTracingState traceCore.TraceState) []attribute.KeyValue { + sampledAttributes := adaptedSamplingResult.Attributes + + transactionIdentifier := attribute.String(TransactionIdentifier, newTracingState.Get(TransactionIdentifierTraceState)) + distributedTransactionIdentifier := attribute.String(DistributedTransactionIdentifier, newTracingState.Get(DistributedTransactionIdentifierTraceState)) + + return append(sampledAttributes, transactionIdentifier, distributedTransactionIdentifier) +} + +func (s *CoralogixSampler) getDescription() string { + return "coralogix-sampler" +} + +func (s *CoralogixSampler) generateNewTraceState(ctx context.Context, name string, samplingResult traceSdk.SamplingResult) traceCore.TraceState { + parentSpanContext := s.getParentSpanContext(ctx) + parentTraceState := samplingResult.Tracestate + + if !parentSpanContext.IsRemote() && parentTraceState.Get(TransactionIdentifierTraceState) != "" { + return parentTraceState + } + + parentTraceState, err := parentTraceState.Insert(TransactionIdentifierTraceState, name) + if err != nil { + return parentTraceState + } + if parentTraceState.Get(DistributedTransactionIdentifierTraceState) == "" { + parentTraceState, err = parentTraceState.Insert(DistributedTransactionIdentifierTraceState, name) + if err != nil { + return parentTraceState + } + } + + return parentTraceState +} + +func (s *CoralogixSampler) getParentSpanContext(ctx context.Context) traceCore.SpanContext { + span := traceCore.SpanFromContext(ctx) + if span != nil { + return span.SpanContext() + } + return traceCore.SpanContext{} +} diff --git a/sampler/coralogix-sampler_test.go b/sampler/coralogix-sampler_test.go new file mode 100644 index 0000000..8354298 --- /dev/null +++ b/sampler/coralogix-sampler_test.go @@ -0,0 +1,194 @@ +package sampler + +import ( + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" + traceSdk "go.opentelemetry.io/otel/sdk/trace" + "testing" +) + +import ( + "context" + traceCore "go.opentelemetry.io/otel/trace" +) + +const ( + spanName = "spanName" +) + +func TestCoralogixSampler_ShouldSample(t *testing.T) { + t.Run("When_alwaysSampler_Should_AppendAttributesAndState", func(t *testing.T) { + alwaysSampler := traceSdk.AlwaysSample() + coralogixSampler := NewCoralogixSampler(alwaysSampler) + + // Act + parameters := traceSdk.SamplingParameters{ + ParentContext: context.Background(), + Name: spanName, + Attributes: []attribute.KeyValue{}, + } + result := coralogixSampler.ShouldSample(parameters) + + expectedAttributes := []attribute.KeyValue{ + attribute.String(TransactionIdentifier, spanName), + attribute.String(DistributedTransactionIdentifier, spanName), + } + + expectedTraceState := traceCore.TraceState{} + + expectedTraceState, _ = expectedTraceState.Insert(TransactionIdentifierTraceState, spanName) + expectedTraceState, _ = expectedTraceState.Insert(DistributedTransactionIdentifierTraceState, spanName) + + assert.Equal(t, traceSdk.RecordAndSample, result.Decision) + assert.ElementsMatch(t, expectedAttributes, result.Attributes) + assert.Equal(t, expectedTraceState, result.Tracestate) + }) + + t.Run("When_NeverSample_Should_AppendAttributesAndState", func(t *testing.T) { + neverSampler := traceSdk.NeverSample() + coralogixSampler := NewCoralogixSampler(neverSampler) + + // Act + parameters := traceSdk.SamplingParameters{ + ParentContext: context.Background(), + Name: spanName, + Attributes: []attribute.KeyValue{}, + } + result := coralogixSampler.ShouldSample(parameters) + + expectedAttributes := []attribute.KeyValue{ + attribute.String(TransactionIdentifier, spanName), + attribute.String(DistributedTransactionIdentifier, spanName), + } + + expectedTraceState := traceCore.TraceState{} + + expectedTraceState, _ = expectedTraceState.Insert(TransactionIdentifierTraceState, spanName) + expectedTraceState, _ = expectedTraceState.Insert(DistributedTransactionIdentifierTraceState, spanName) + + assert.Equal(t, traceSdk.Drop, result.Decision) + assert.ElementsMatch(t, expectedAttributes, result.Attributes) + assert.Equal(t, expectedTraceState, result.Tracestate) + }) + + t.Run("When_CustomSamplerIsNull_ShouldFailInit", func(t *testing.T) { + assert.Panics(t, func() { + _ = NewCoralogixSampler(nil) + }) + }) + + t.Run("When_ParentContextExistsAndNotRemote_ShouldCopyParentTraceState", func(t *testing.T) { + alwaysSampler := traceSdk.AlwaysSample() + coralogixSampler := NewCoralogixSampler(alwaysSampler) + + traceState := traceCore.TraceState{} + + traceState, _ = traceState.Insert(TransactionIdentifierTraceState, "fatherSpanName") + traceState, _ = traceState.Insert(DistributedTransactionIdentifierTraceState, "fatherSpanName") + + parentSpan := traceCore.NewSpanContext(traceCore.SpanContextConfig{ + TraceID: traceCore.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, + SpanID: traceCore.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}, + TraceFlags: traceCore.FlagsSampled, + TraceState: traceState, + Remote: false, + }) + parentCtx := traceCore.ContextWithSpanContext(context.Background(), parentSpan) + + // Act + parameters := traceSdk.SamplingParameters{ + ParentContext: parentCtx, + Name: spanName, + Attributes: []attribute.KeyValue{}, + } + result := coralogixSampler.ShouldSample(parameters) + + expectedAttributes := []attribute.KeyValue{ + attribute.String(TransactionIdentifier, "fatherSpanName"), + attribute.String(DistributedTransactionIdentifier, "fatherSpanName"), + } + expectedTraceState := traceCore.TraceState{} + expectedTraceState, _ = traceState.Insert(TransactionIdentifierTraceState, "fatherSpanName") + expectedTraceState, _ = traceState.Insert(DistributedTransactionIdentifierTraceState, "fatherSpanName") + + assert.ElementsMatch(t, expectedAttributes, result.Attributes) + assert.Equal(t, expectedTraceState, result.Tracestate) + }) + t.Run("When_ParentContextExistsAndRemote_ShouldCopyParentTraceState", func(t *testing.T) { + alwaysSampler := traceSdk.AlwaysSample() + coralogixSampler := NewCoralogixSampler(alwaysSampler) + + traceState := traceCore.TraceState{} + + traceState, _ = traceState.Insert(TransactionIdentifierTraceState, "fatherSpanName") + traceState, _ = traceState.Insert(DistributedTransactionIdentifierTraceState, "fatherSpanName") + + parentSpan := traceCore.NewSpanContext(traceCore.SpanContextConfig{ + TraceID: traceCore.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, + SpanID: traceCore.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}, + TraceFlags: traceCore.FlagsSampled, + TraceState: traceState, + Remote: true, + }) + parentCtx := traceCore.ContextWithSpanContext(context.Background(), parentSpan) + + // Act + parameters := traceSdk.SamplingParameters{ + ParentContext: parentCtx, + Name: spanName, + Attributes: []attribute.KeyValue{}, + } + result := coralogixSampler.ShouldSample(parameters) + + expectedAttributes := []attribute.KeyValue{ + attribute.String(TransactionIdentifier, "spanName"), + attribute.String(DistributedTransactionIdentifier, "fatherSpanName"), + } + expectedTraceState := traceCore.TraceState{} + expectedTraceState, _ = expectedTraceState.Insert(TransactionIdentifierTraceState, spanName) + expectedTraceState, _ = expectedTraceState.Insert(DistributedTransactionIdentifierTraceState, "fatherSpanName") + + assert.ElementsMatch(t, expectedAttributes, result.Attributes) + assert.Equal(t, expectedTraceState.Get(TransactionIdentifierTraceState), result.Tracestate.Get(TransactionIdentifierTraceState)) + assert.Equal(t, expectedTraceState.Get(DistributedTransactionIdentifierTraceState), result.Tracestate.Get(DistributedTransactionIdentifierTraceState)) + }) + + t.Run("When_ParentContextExistsAndRemote_ShouldCopyParentTraceState", func(t *testing.T) { + alwaysSampler := traceSdk.AlwaysSample() + coralogixSampler := NewCoralogixSampler(alwaysSampler) + + traceState := traceCore.TraceState{} + + traceState, _ = traceState.Insert(TransactionIdentifierTraceState, "fatherSpanName") + traceState, _ = traceState.Insert(DistributedTransactionIdentifierTraceState, "fatherSpanName") + + parentSpan := traceCore.NewSpanContext(traceCore.SpanContextConfig{ + TraceID: traceCore.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, + SpanID: traceCore.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}, + TraceFlags: traceCore.FlagsSampled, + TraceState: traceState, + Remote: true, + }) + parentCtx := traceCore.ContextWithSpanContext(context.Background(), parentSpan) + + // Act + parameters := traceSdk.SamplingParameters{ + ParentContext: parentCtx, + Name: spanName, + Attributes: []attribute.KeyValue{}, + } + result := coralogixSampler.ShouldSample(parameters) + + expectedAttributes := []attribute.KeyValue{ + attribute.String(TransactionIdentifier, "spanName"), + attribute.String(DistributedTransactionIdentifier, "fatherSpanName"), + } + expectedTraceState := traceCore.TraceState{} + expectedTraceState, _ = expectedTraceState.Insert(TransactionIdentifierTraceState, spanName) + expectedTraceState, _ = expectedTraceState.Insert(DistributedTransactionIdentifierTraceState, "fatherSpanName") + + assert.ElementsMatch(t, expectedAttributes, result.Attributes) + assert.Equal(t, expectedTraceState.Get(TransactionIdentifierTraceState), result.Tracestate.Get(TransactionIdentifierTraceState)) + assert.Equal(t, expectedTraceState.Get(DistributedTransactionIdentifierTraceState), result.Tracestate.Get(DistributedTransactionIdentifierTraceState)) + }) +}