-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
25 changed files
with
799 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
GOLANG_VERSION?=1.15 | ||
GOLANG_VERSION?=1.17 | ||
|
||
.PHONY: dev.up | ||
dev.up: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
module github.com/etf1/kafka-transformer | ||
|
||
go 1.15 | ||
go 1.17 | ||
|
||
require github.com/confluentinc/confluent-kafka-go v1.5.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# OpenTelemetry Instrumentation | ||
|
||
This package provides an instrumentation of [OpenTelemetry](https://github.com/open-telemetry). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package otel | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"time" | ||
|
||
"github.com/confluentinc/confluent-kafka-go/kafka" | ||
"github.com/etf1/kafka-transformer/instrumentation/otel/internal" | ||
"github.com/etf1/kafka-transformer/pkg/instrument" | ||
|
||
"go.opentelemetry.io/contrib" | ||
"go.opentelemetry.io/otel" | ||
"go.opentelemetry.io/otel/attribute" | ||
"go.opentelemetry.io/otel/propagation" | ||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0" | ||
oteltrace "go.opentelemetry.io/otel/trace" | ||
) | ||
|
||
// Collector is the OpenTelemetry instrumentation collector. | ||
type Collector struct { | ||
ctx context.Context | ||
tracer oteltrace.Tracer | ||
propagator propagation.TextMapPropagator | ||
consumerGroupID string | ||
spans *sync.Map | ||
} | ||
|
||
// NewCollector instanciates a new Collector. | ||
func NewCollector(opts ...Option) *Collector { | ||
cfg := &config{ | ||
tracerProvider: otel.GetTracerProvider(), | ||
propagator: otel.GetTextMapPropagator(), | ||
tracerName: tracerName, | ||
} | ||
|
||
for _, o := range opts { | ||
o.apply(cfg) | ||
} | ||
|
||
return &Collector{ | ||
ctx: context.Background(), | ||
tracer: cfg.tracerProvider.Tracer( | ||
cfg.tracerName, | ||
oteltrace.WithInstrumentationVersion(contrib.SemVersion()), | ||
), | ||
propagator: cfg.propagator, | ||
consumerGroupID: cfg.consumerGroupID, | ||
spans: &sync.Map{}, | ||
} | ||
} | ||
|
||
func (c *Collector) attrsByOperationAndMessage(operation internal.Operation, msg *kafka.Message) []attribute.KeyValue { | ||
attributes := []attribute.KeyValue{ | ||
internal.KafkaSystemKey(), | ||
internal.KafkaOperation(operation), | ||
semconv.MessagingDestinationKindTopic, | ||
} | ||
|
||
switch operation { | ||
case internal.OperationConsume: | ||
attributes = append( | ||
attributes, | ||
internal.KafkaConsumerGroupID(c.consumerGroupID), | ||
) | ||
} | ||
|
||
if msg != nil { | ||
attributes = append( | ||
attributes, | ||
internal.KafkaMessageKey(string(msg.Key)), | ||
semconv.MessagingKafkaPartitionKey.Int(int(msg.TopicPartition.Partition)), | ||
) | ||
attributes = append(attributes, internal.KafkaMessageHeaders(msg.Headers)...) | ||
|
||
if topic := msg.TopicPartition.Topic; topic != nil { | ||
attributes = append(attributes, internal.KafkaDestinationTopic(*topic)) | ||
} | ||
} | ||
|
||
return attributes | ||
} | ||
|
||
func (c *Collector) startSpan(operationName internal.Operation, msg *kafka.Message) oteltrace.Span { | ||
opts := []oteltrace.SpanStartOption{ | ||
oteltrace.WithSpanKind(oteltrace.SpanKindConsumer), | ||
} | ||
|
||
carrier := NewMessageCarrier(msg) | ||
ctx := c.propagator.Extract(c.ctx, carrier) | ||
|
||
ctx, span := c.tracer.Start(ctx, string(operationName), opts...) | ||
|
||
c.propagator.Inject(ctx, carrier) | ||
|
||
span.SetAttributes(c.attrsByOperationAndMessage(operationName, msg)...) | ||
|
||
return span | ||
} | ||
|
||
// Before is triggered before an event occurred. | ||
func (c *Collector) Before(message *kafka.Message, action instrument.Action, _ time.Time) { | ||
if message == nil { | ||
return | ||
} | ||
|
||
var operation internal.Operation | ||
switch action { | ||
case instrument.KafkaProducerProduce: | ||
operation = internal.OperationProduce | ||
|
||
case instrument.KafkaConsumerConsume: | ||
operation = internal.OperationConsume | ||
|
||
case instrument.TransformerTransform: | ||
operation = internal.OperationTransform | ||
|
||
case instrument.ProjectorProject: | ||
operation = internal.OperationProject | ||
|
||
default: | ||
return | ||
} | ||
|
||
span := c.startSpan(operation, message) | ||
|
||
c.spans.Store(message, span) | ||
} | ||
|
||
// After is triggered before an event occurred. | ||
func (c *Collector) After(message *kafka.Message, action instrument.Action, err error, _ time.Time) { | ||
if message == nil { | ||
return | ||
} | ||
|
||
if value, ok := c.spans.LoadAndDelete(message); ok { | ||
span := value.(oteltrace.Span) | ||
endSpan(span, err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package otel | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/confluentinc/confluent-kafka-go/kafka" | ||
"github.com/etf1/kafka-transformer/pkg/instrument" | ||
"github.com/stretchr/testify/assert" | ||
"go.opentelemetry.io/otel" | ||
"go.opentelemetry.io/otel/trace" | ||
) | ||
|
||
func TestNewCollector(t *testing.T) { | ||
// When | ||
collector := NewCollector() | ||
|
||
// Then | ||
assert.IsType(t, new(Collector), collector) | ||
|
||
assert.Equal(t, context.Background(), collector.ctx) | ||
assert.Equal(t, otel.GetTextMapPropagator(), collector.propagator) | ||
assert.Equal(t, new(sync.Map), collector.spans) | ||
} | ||
|
||
func TestNewCollector_WithTracerProvider(t *testing.T) { | ||
// Given | ||
tracerProvider := otel.GetTracerProvider() | ||
|
||
// When | ||
collector := NewCollector(WithTracerProvider(tracerProvider)) | ||
|
||
// Then | ||
assert.IsType(t, new(Collector), collector) | ||
|
||
assert.Equal(t, context.Background(), collector.ctx) | ||
assert.Equal(t, otel.GetTextMapPropagator(), collector.propagator) | ||
assert.Equal(t, new(sync.Map), collector.spans) | ||
} | ||
|
||
func TestCollector_Before_Produce(t *testing.T) { | ||
// Given | ||
tracerProvider := otel.GetTracerProvider() | ||
|
||
collector := NewCollector(WithTracerProvider(tracerProvider)) | ||
|
||
message := &kafka.Message{ | ||
Key: []byte("key"), | ||
Value: []byte("value"), | ||
} | ||
|
||
// When | ||
collector.Before(message, instrument.KafkaProducerProduce, time.Now()) | ||
|
||
// Then | ||
value, ok := collector.spans.Load(message) | ||
span := value.(trace.Span) | ||
|
||
assert.True(t, ok) | ||
assert.Equal(t, "00000000000000000000000000000000", span.SpanContext().TraceID().String()) | ||
assert.Equal(t, "0000000000000000", span.SpanContext().SpanID().String()) | ||
} | ||
|
||
func TestCollector_Before_Consume(t *testing.T) { | ||
// Given | ||
tracerProvider := otel.GetTracerProvider() | ||
|
||
collector := NewCollector(WithTracerProvider(tracerProvider)) | ||
|
||
message := &kafka.Message{ | ||
Key: []byte("key"), | ||
Value: []byte("value"), | ||
} | ||
|
||
// When | ||
collector.Before(message, instrument.KafkaConsumerConsume, time.Now()) | ||
|
||
// Then | ||
value, ok := collector.spans.Load(message) | ||
span := value.(trace.Span) | ||
|
||
assert.True(t, ok) | ||
assert.Equal(t, "00000000000000000000000000000000", span.SpanContext().TraceID().String()) | ||
assert.Equal(t, "0000000000000000", span.SpanContext().SpanID().String()) | ||
} | ||
|
||
func TestCollector_Before_Transform(t *testing.T) { | ||
// Given | ||
tracerProvider := otel.GetTracerProvider() | ||
|
||
collector := NewCollector(WithTracerProvider(tracerProvider)) | ||
|
||
message := &kafka.Message{ | ||
Key: []byte("key"), | ||
Value: []byte("value"), | ||
} | ||
|
||
// When | ||
collector.Before(message, instrument.TransformerTransform, time.Now()) | ||
|
||
// Then | ||
value, ok := collector.spans.Load(message) | ||
span := value.(trace.Span) | ||
|
||
assert.True(t, ok) | ||
assert.Equal(t, "00000000000000000000000000000000", span.SpanContext().TraceID().String()) | ||
assert.Equal(t, "0000000000000000", span.SpanContext().SpanID().String()) | ||
} | ||
|
||
func TestCollector_Before_Project(t *testing.T) { | ||
// Given | ||
tracerProvider := otel.GetTracerProvider() | ||
|
||
collector := NewCollector(WithTracerProvider(tracerProvider)) | ||
|
||
message := &kafka.Message{ | ||
Key: []byte("key"), | ||
Value: []byte("value"), | ||
} | ||
|
||
// When | ||
collector.Before(message, instrument.ProjectorProject, time.Now()) | ||
|
||
// Then | ||
value, ok := collector.spans.Load(message) | ||
span := value.(trace.Span) | ||
|
||
assert.True(t, ok) | ||
assert.Equal(t, "00000000000000000000000000000000", span.SpanContext().TraceID().String()) | ||
assert.Equal(t, "0000000000000000", span.SpanContext().SpanID().String()) | ||
} | ||
|
||
func TestCollector_Before_AnotherAction(t *testing.T) { | ||
// Given | ||
tracerProvider := otel.GetTracerProvider() | ||
|
||
collector := NewCollector(WithTracerProvider(tracerProvider)) | ||
|
||
message := &kafka.Message{ | ||
Key: []byte("key"), | ||
Value: []byte("value"), | ||
} | ||
|
||
// When | ||
collector.Before(message, instrument.OverallTime, time.Now()) | ||
|
||
// Then | ||
value, ok := collector.spans.Load(message) | ||
|
||
assert.Nil(t, value) | ||
assert.False(t, ok) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package otel | ||
|
||
import ( | ||
"go.opentelemetry.io/otel/codes" | ||
oteltrace "go.opentelemetry.io/otel/trace" | ||
) | ||
|
||
const ( | ||
// tracerName is the technical name of the tracer. | ||
tracerName = "github.com/etf1/kafka-transformer/instrumentation/otel" | ||
) | ||
|
||
func endSpan(s oteltrace.Span, err error) { | ||
if err != nil { | ||
s.SetStatus(codes.Error, err.Error()) | ||
} | ||
s.End() | ||
} |
Oops, something went wrong.