Skip to content

Commit 0017ddb

Browse files
authored
Merge branch 'main' into codeboten/otelconf-unmarshal
2 parents 9402070 + 01ec8a6 commit 0017ddb

File tree

5 files changed

+142
-5
lines changed

5 files changed

+142
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1717
- Add unmarshaling and validation for `BatchLogRecordProcessor`, `BatchSpanProcessor`, and `PeriodicMetricReader` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8049)
1818
- Add unmarshaling and validation for `TextMapPropagator` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8052)
1919
- Add unmarshaling and validation for `OTLPHttpExporter`, `OTLPGrpcExporter`, `OTLPGrpcMetricExporter` and `OTLPHttpMetricExporter` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8112)
20+
- Add a `WithSpanNameFormatter` option to `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo`. (#7986)
2021
- Add unmarshaling and validation for `AttributeType`, `AttributeNameValue`, `SimpleSpanProcessor`, `SimpleLogRecordProcessor`, `ZipkinSpanExporter`, `NameStringValuePair`, `InstrumentType` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8127)
2122

2223
### Changed

instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/config.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo"
55

66
import (
7+
"go.mongodb.org/mongo-driver/v2/event"
78
"go.opentelemetry.io/otel"
89
"go.opentelemetry.io/otel/trace"
910
)
@@ -18,6 +19,8 @@ type config struct {
1819
Tracer trace.Tracer
1920

2021
CommandAttributeDisabled bool
22+
23+
SpanNameFormatter SpanNameFormatterFunc
2124
}
2225

2326
// newConfig returns a config with all Options set.
@@ -26,6 +29,16 @@ func newConfig(opts ...Option) config {
2629
TracerProvider: otel.GetTracerProvider(),
2730
CommandAttributeDisabled: true,
2831
}
32+
33+
cfg.SpanNameFormatter = func(event *event.CommandStartedEvent) string {
34+
collection, _ := extractCollection(event)
35+
if collection != "" {
36+
return collection + "." + event.CommandName
37+
}
38+
39+
return event.CommandName
40+
}
41+
2942
for _, opt := range opts {
3043
opt.apply(&cfg)
3144
}
@@ -48,6 +61,22 @@ func (o optionFunc) apply(c *config) {
4861
o(c)
4962
}
5063

64+
// SpanNameFormatterFunc is a function that resolves the span name given an
65+
// *event.CommandStartedEvent.
66+
type SpanNameFormatterFunc func(e *event.CommandStartedEvent) string
67+
68+
// WithSpanNameFormatter specifies a function that resolves the span name given an
69+
// *event.CommandStartedEvent. If none is specified, the default resolver is used,
70+
// which returns "<collection>.<command>" if the collection is non-empty,
71+
// and just "<command>" otherwise.
72+
func WithSpanNameFormatter(resolver SpanNameFormatterFunc) Option {
73+
return optionFunc(func(cfg *config) {
74+
if resolver != nil {
75+
cfg.SpanNameFormatter = resolver
76+
}
77+
})
78+
}
79+
5180
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
5281
// If none is specified, the global provider is used.
5382
func WithTracerProvider(provider trace.TracerProvider) Option {

instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/example_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ package otelmongo_test
55

66
import (
77
"context"
8+
"fmt"
89

910
"go.mongodb.org/mongo-driver/v2/bson"
11+
"go.mongodb.org/mongo-driver/v2/event"
1012
"go.mongodb.org/mongo-driver/v2/mongo"
1113
"go.mongodb.org/mongo-driver/v2/mongo/options"
1214

@@ -45,3 +47,37 @@ func Example() {
4547
panic(err)
4648
}
4749
}
50+
51+
func ExampleWithSpanNameFormatter() {
52+
// connect to MongoDB
53+
opts := options.Client()
54+
opts.Monitor = otelmongo.NewMonitor(
55+
otelmongo.WithSpanNameFormatter(func(event *event.CommandStartedEvent) string {
56+
// optionally, the collection name can be extracted for more
57+
// descriptive span names; see the extractCollection helper function
58+
// in the otelmongo package for an example of how to do this.
59+
return fmt.Sprintf("my-prefix-%s", event.CommandName)
60+
}),
61+
)
62+
opts.ApplyURI("mongodb://localhost:27017")
63+
client, err := mongo.Connect(opts)
64+
if err != nil {
65+
panic(err)
66+
}
67+
68+
defer func() {
69+
if err := client.Disconnect(context.TODO()); err != nil {
70+
panic(err)
71+
}
72+
}()
73+
db := client.Database("mystore")
74+
inventory := db.Collection("inventory")
75+
76+
_, err = inventory.InsertOne(context.TODO(), bson.D{
77+
{Key: "item", Value: "canvas"},
78+
// [..]
79+
})
80+
if err != nil {
81+
panic(err)
82+
}
83+
}

instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/mongo.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ type monitor struct {
3030
}
3131

3232
func (m *monitor) Started(ctx context.Context, evt *event.CommandStartedEvent) {
33-
var spanName string
34-
3533
hostname, port := peerInfo(evt)
3634

3735
attrs := []attribute.KeyValue{
@@ -45,11 +43,14 @@ func (m *monitor) Started(ctx context.Context, evt *event.CommandStartedEvent) {
4543
if !m.cfg.CommandAttributeDisabled {
4644
attrs = append(attrs, semconv.DBQueryText(sanitizeCommand(evt.Command)))
4745
}
48-
if collection, err := extractCollection(evt); err == nil && collection != "" {
49-
spanName = collection + "."
46+
47+
collection, err := extractCollection(evt)
48+
if err == nil && collection != "" {
5049
attrs = append(attrs, semconv.DBCollectionName(collection))
5150
}
52-
spanName += evt.CommandName
51+
52+
spanName := m.cfg.SpanNameFormatter(evt)
53+
5354
opts := []trace.SpanStartOption{
5455
trace.WithSpanKind(trace.SpanKindClient),
5556
trace.WithAttributes(attrs...),
@@ -105,6 +106,7 @@ func sanitizeCommand(command bson.Raw) string {
105106
// For CRUD operations, this is the first key/value string pair in the bson
106107
// document where key == "<operation>" (e.g. key == "insert").
107108
// For database meta-level operations, such a key may not exist.
109+
// This function returns the collection name or an error if no collection can be determined.
108110
func extractCollection(evt *event.CommandStartedEvent) (string, error) {
109111
elt, err := evt.Command.IndexErr(0)
110112
if err != nil {

instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/mongo_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,75 @@ import (
2424

2525
type validator func(sdktrace.ReadOnlySpan) bool
2626

27+
func TestWithSpanNameFormatter(t *testing.T) {
28+
tt := []struct {
29+
title string
30+
operation func(context.Context, *mongo.Database) (any, error)
31+
expectedSpanName string
32+
mockResponses []bson.D
33+
SpanNameFormatter SpanNameFormatterFunc
34+
}{
35+
{
36+
title: "insert using default SpanNameFormatter",
37+
operation: func(ctx context.Context, db *mongo.Database) (any, error) {
38+
return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}})
39+
},
40+
expectedSpanName: "test-collection.insert",
41+
SpanNameFormatter: nil,
42+
mockResponses: []bson.D{{{Key: "ok", Value: 1}}},
43+
},
44+
{
45+
title: "delete with custom SpanNameFormatter",
46+
operation: func(ctx context.Context, db *mongo.Database) (any, error) {
47+
return db.Collection("test-collection").DeleteOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}})
48+
},
49+
SpanNameFormatter: func(event *event.CommandStartedEvent) string {
50+
return "my-" + event.CommandName + "-span"
51+
},
52+
expectedSpanName: "my-delete-span",
53+
mockResponses: []bson.D{{{Key: "ok", Value: 1}}},
54+
},
55+
}
56+
for _, tc := range tt {
57+
t.Run(tc.title, func(t *testing.T) {
58+
md := drivertest.NewMockDeployment()
59+
60+
sr := tracetest.NewSpanRecorder()
61+
provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
62+
63+
ctx, cancel := context.WithTimeout(t.Context(), time.Second*3)
64+
defer cancel()
65+
66+
ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test")
67+
addr := "mongodb://localhost:27017/?connect=direct"
68+
opts := options.Client()
69+
opts.Deployment = md //nolint:staticcheck // This method is the current documented way to set the mongodb mock. See https://github.com/mongodb/mongo-go-driver/blob/v2.0.0/x/mongo/driver/drivertest/opmsg_deployment_test.go#L24
70+
opts.Monitor = NewMonitor(
71+
WithTracerProvider(provider),
72+
WithSpanNameFormatter(tc.SpanNameFormatter),
73+
)
74+
opts.ApplyURI(addr)
75+
76+
md.AddResponses(tc.mockResponses...)
77+
client, err := mongo.Connect(opts)
78+
defer func() {
79+
e := client.Disconnect(t.Context())
80+
require.NoError(t, e)
81+
}()
82+
require.NoError(t, err)
83+
84+
_, err = tc.operation(ctx, client.Database("test-database"))
85+
require.NoError(t, err)
86+
87+
span.End()
88+
spans := sr.Ended()
89+
90+
s := spans[0]
91+
assert.Equal(t, tc.expectedSpanName, s.Name())
92+
})
93+
}
94+
}
95+
2796
func TestDBCrudOperation(t *testing.T) {
2897
commonValidators := []validator{
2998
func(s sdktrace.ReadOnlySpan) bool {

0 commit comments

Comments
 (0)