From 95dcf65b9bc8d42052124d96eefbf95db1e2ad15 Mon Sep 17 00:00:00 2001 From: Alaric Whitney Date: Fri, 27 Dec 2024 17:09:27 -0600 Subject: [PATCH] feat: adding instrumentation support for mongo-driver/v2 --- instrumentation/README.md | 1 + .../mongo-driver/v2/mongo/otelmongo/config.go | 68 +++++ .../mongo-driver/v2/mongo/otelmongo/doc.go | 15 + .../v2/mongo/otelmongo/example_test.go | 41 +++ .../mongo-driver/v2/mongo/otelmongo/go.mod | 29 ++ .../mongo-driver/v2/mongo/otelmongo/go.sum | 76 +++++ .../mongo-driver/v2/mongo/otelmongo/mongo.go | 150 ++++++++++ .../v2/mongo/otelmongo/test/doc.go | 11 + .../v2/mongo/otelmongo/test/go.mod | 38 +++ .../v2/mongo/otelmongo/test/go.sum | 82 ++++++ .../v2/mongo/otelmongo/test/mongo_test.go | 260 ++++++++++++++++++ .../v2/mongo/otelmongo/test/version.go | 17 ++ .../v2/mongo/otelmongo/test/version_test.go | 24 ++ .../v2/mongo/otelmongo/version.go | 17 ++ .../v2/mongo/otelmongo/version_test.go | 24 ++ versions.yaml | 2 + 16 files changed, 855 insertions(+) create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/config.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/doc.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/example_test.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.mod create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.sum create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/mongo.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/doc.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.mod create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.sum create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/mongo_test.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version_test.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version.go create mode 100644 instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version_test.go diff --git a/instrumentation/README.md b/instrumentation/README.md index 77fe61c45c1..306f6b2750e 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -48,6 +48,7 @@ The following instrumentation packages are provided for popular Go packages and | [github.com/gorilla/mux](./github.com/gorilla/mux/otelmux) | | ✓ | | [github.com/labstack/echo](./github.com/labstack/echo/otelecho) | | ✓ | | [go.mongodb.org/mongo-driver](./go.mongodb.org/mongo-driver/mongo/otelmongo) | | ✓ | +| [go.mongodb.org/mongo-driver/v2](./go.mongodb.org/mongo-driver/v2/mongo/otelmongo) | | ✓ | | [google.golang.org/grpc](./google.golang.org/grpc/otelgrpc) | ✓ | ✓ | | [host](./host) | ✓ | | | [net/http](./net/http/otelhttp) | ✓ | ✓ | diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/config.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/config.go new file mode 100644 index 00000000000..07e36c87660 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/config.go @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" + +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" +) + +// ScopeName is the instrumentation scope name. +const ScopeName = "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" + +// config is used to configure the mongo tracer. +type config struct { + TracerProvider trace.TracerProvider + + Tracer trace.Tracer + + CommandAttributeDisabled bool +} + +// newConfig returns a config with all Options set. +func newConfig(opts ...Option) config { + cfg := config{ + TracerProvider: otel.GetTracerProvider(), + CommandAttributeDisabled: true, + } + for _, opt := range opts { + opt.apply(&cfg) + } + + cfg.Tracer = cfg.TracerProvider.Tracer( + ScopeName, + trace.WithInstrumentationVersion(Version()), + ) + return cfg +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return optionFunc(func(cfg *config) { + if provider != nil { + cfg.TracerProvider = provider + } + }) +} + +// WithCommandAttributeDisabled specifies if the MongoDB command is added as an attribute to Spans or not. +// This is disabled by default and the MongoDB command will not be added as an attribute +// to Spans if this option is not provided. +func WithCommandAttributeDisabled(disabled bool) Option { + return optionFunc(func(cfg *config) { + cfg.CommandAttributeDisabled = disabled + }) +} diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/doc.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/doc.go new file mode 100644 index 00000000000..e7a95f89267 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/doc.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package otelmongo instruments go.mongodb.org/mongo-driver/mongo. +// +// This package is compatible with v0.2.0 of +// go.mongodb.org/mongo-driver/mongo. +// +// `NewMonitor` will return an event.CommandMonitor which is used to trace +// requests. +// +// This code was originally based on the following: +// - https://github.com/DataDog/dd-trace-go/tree/02f0449efa3cb382d499fadc873957385dcb2192/contrib/go.mongodb.org/mongo-driver/mongo +// - https://github.com/DataDog/dd-trace-go/tree/v1.23.3/ddtrace/ext +package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/example_test.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/example_test.go new file mode 100644 index 00000000000..c6c03826287 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/example_test.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelmongo_test + +import ( + "context" + + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" + + "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" +) + +func Example() { + // connect to MongoDB + opts := options.Client() + opts.Monitor = otelmongo.NewMonitor() + opts.ApplyURI("mongodb://localhost:27017") + client, err := mongo.Connect(opts) + if err != nil { + panic(err) + } + db := client.Database("example") + inventory := db.Collection("inventory") + + _, err = inventory.InsertOne(context.Background(), bson.D{ + {Key: "item", Value: "canvas"}, + {Key: "qty", Value: 100}, + {Key: "attributes", Value: bson.A{"cotton"}}, + {Key: "size", Value: bson.D{ + {Key: "h", Value: 28}, + {Key: "w", Value: 35.5}, + {Key: "uom", Value: "cm"}, + }}, + }) + if err != nil { + panic(err) + } +} diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.mod b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.mod new file mode 100644 index 00000000000..c5469b9d0b9 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.mod @@ -0,0 +1,29 @@ +module go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo + +go 1.22.0 + +require ( + github.com/stretchr/testify v1.10.0 + go.mongodb.org/mongo-driver/v2 v2.0.0 + go.opentelemetry.io/otel v1.33.0 + go.opentelemetry.io/otel/trace v1.33.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.sum b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.sum new file mode 100644 index 00000000000..d0d169d2446 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/go.sum @@ -0,0 +1,76 @@ +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.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver/v2 v2.0.0 h1:Jfd7XpdZa9yk3eY774bO7SWVb30noLSirL9nKTpavhI= +go.mongodb.org/mongo-driver/v2 v2.0.0/go.mod h1:nSjmNq4JUstE8IRZKTktLgMHM4F1fccL6HGX1yh+8RA= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/mongo.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/mongo.go new file mode 100644 index 00000000000..3a04fa3921e --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/mongo.go @@ -0,0 +1,150 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" + + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/event" +) + +type spanKey struct { + ConnectionID string + RequestID int64 +} + +type monitor struct { + sync.Mutex + spans map[spanKey]trace.Span + cfg config +} + +func (m *monitor) Started(ctx context.Context, evt *event.CommandStartedEvent) { + var spanName string + + hostname, port := peerInfo(evt) + + attrs := []attribute.KeyValue{ + semconv.DBSystemMongoDB, + semconv.DBOperation(evt.CommandName), + semconv.DBName(evt.DatabaseName), + semconv.NetPeerName(hostname), + semconv.NetPeerPort(port), + semconv.NetTransportTCP, + } + if !m.cfg.CommandAttributeDisabled { + attrs = append(attrs, semconv.DBStatement(sanitizeCommand(evt.Command))) + } + if collection, err := extractCollection(evt); err == nil && collection != "" { + spanName = collection + "." + attrs = append(attrs, semconv.DBMongoDBCollection(collection)) + } + spanName += evt.CommandName + opts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(attrs...), + } + _, span := m.cfg.Tracer.Start(ctx, spanName, opts...) + key := spanKey{ + ConnectionID: evt.ConnectionID, + RequestID: evt.RequestID, + } + m.Lock() + m.spans[key] = span + m.Unlock() +} + +func (m *monitor) Succeeded(ctx context.Context, evt *event.CommandSucceededEvent) { + m.Finished(&evt.CommandFinishedEvent, nil) +} + +func (m *monitor) Failed(ctx context.Context, evt *event.CommandFailedEvent) { + m.Finished(&evt.CommandFinishedEvent, fmt.Errorf("%s", evt.Failure)) +} + +func (m *monitor) Finished(evt *event.CommandFinishedEvent, err error) { + key := spanKey{ + ConnectionID: evt.ConnectionID, + RequestID: evt.RequestID, + } + m.Lock() + span, ok := m.spans[key] + if ok { + delete(m.spans, key) + } + m.Unlock() + if !ok { + return + } + + if err != nil { + span.SetStatus(codes.Error, err.Error()) + } + + span.End() +} + +// TODO sanitize values where possible, then re-enable `db.statement` span attributes default. +// TODO limit maximum size. +func sanitizeCommand(command bson.Raw) string { + b, _ := bson.MarshalExtJSON(command, false, false) + return string(b) +} + +// extractCollection extracts the collection for the given mongodb command event. +// For CRUD operations, this is the first key/value string pair in the bson +// document where key == "" (e.g. key == "insert"). +// For database meta-level operations, such a key may not exist. +func extractCollection(evt *event.CommandStartedEvent) (string, error) { + elt, err := evt.Command.IndexErr(0) + if err != nil { + return "", err + } + if key, err := elt.KeyErr(); err == nil && key == evt.CommandName { + var v bson.RawValue + if v, err = elt.ValueErr(); err != nil || v.Type != bson.TypeString { + return "", err + } + return v.StringValue(), nil + } + return "", errors.New("collection name not found") +} + +// NewMonitor creates a new mongodb event CommandMonitor. +func NewMonitor(opts ...Option) *event.CommandMonitor { + cfg := newConfig(opts...) + m := &monitor{ + spans: make(map[spanKey]trace.Span), + cfg: cfg, + } + return &event.CommandMonitor{ + Started: m.Started, + Succeeded: m.Succeeded, + Failed: m.Failed, + } +} + +func peerInfo(evt *event.CommandStartedEvent) (hostname string, port int) { + hostname = evt.ConnectionID + port = 27017 + if idx := strings.IndexByte(hostname, '['); idx >= 0 { + hostname = hostname[:idx] + } + if idx := strings.IndexByte(hostname, ':'); idx >= 0 { + port = func(p int, e error) int { return p }(strconv.Atoi(hostname[idx+1:])) + hostname = hostname[:idx] + } + return hostname, port +} diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/doc.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/doc.go new file mode 100644 index 00000000000..91f42a97c48 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/doc.go @@ -0,0 +1,11 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package test validates the otelmongo instrumentation with the default SDK. + +This package is in a separate module from the instrumentation it tests to +isolate the dependency of the default SDK and not impose this as a transitive +dependency for users. +*/ +package test // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test" diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.mod b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.mod new file mode 100644 index 00000000000..1af1073901a --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.mod @@ -0,0 +1,38 @@ +module go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test + +go 1.22.0 + +require ( + github.com/stretchr/testify v1.10.0 + go.mongodb.org/mongo-driver/v2 v2.0.0 + go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo v0.0.0-00010101000000-000000000000 + go.opentelemetry.io/otel v1.33.0 + go.opentelemetry.io/otel/sdk v1.33.0 + go.opentelemetry.io/otel/trace v1.33.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace ( + go.opentelemetry.io/contrib => ../../../../../.. + go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo => ../ +) diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.sum b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.sum new file mode 100644 index 00000000000..071688c8174 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/go.sum @@ -0,0 +1,82 @@ +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.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver/v2 v2.0.0 h1:Jfd7XpdZa9yk3eY774bO7SWVb30noLSirL9nKTpavhI= +go.mongodb.org/mongo-driver/v2 v2.0.0/go.mod h1:nSjmNq4JUstE8IRZKTktLgMHM4F1fccL6HGX1yh+8RA= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/mongo_test.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/mongo_test.go new file mode 100644 index 00000000000..b5b53c32377 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/mongo_test.go @@ -0,0 +1,260 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/x/mongo/driver/drivertest" + "go.mongodb.org/mongo-driver/v2/mongo/options" + + "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "go.opentelemetry.io/otel/trace" +) + +type validator func(sdktrace.ReadOnlySpan) bool + +func TestDBCrudOperation(t *testing.T) { + commonValidators := []validator{ + func(s sdktrace.ReadOnlySpan) bool { + return assert.Equal(t, "test-collection.insert", s.Name(), "expected %s", s.Name()) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Contains(t, s.Attributes(), attribute.String("db.operation", "insert")) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Contains(t, s.Attributes(), attribute.String("db.mongodb.collection", "test-collection")) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Equal(t, codes.Unset, s.Status().Code) + }, + } + + tt := []struct { + title string + operation func(context.Context, *mongo.Database) (interface{}, error) + mockResponses []bson.D + excludeCommand bool + validators []validator + }{ + { + title: "insert", + operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { + return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) + }, + mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, + excludeCommand: false, + validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { + for _, attr := range s.Attributes() { + if attr.Key == "db.statement" { + return assert.Contains(t, attr.Value.AsString(), `"test-item":"test-value"`) + } + } + return false + }), + }, + { + title: "insert", + operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { + return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) + }, + mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, + excludeCommand: true, + validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { + for _, attr := range s.Attributes() { + if attr.Key == "db.statement" { + return false + } + } + return true + }), + }, + } + for _, tc := range tt { + tc := tc + + title := tc.title + if tc.excludeCommand { + title = title + "/excludeCommand" + } else { + title = title + "/includeCommand" + } + + t.Run(title, func(t *testing.T) { + md := drivertest.NewMockDeployment() + + sr := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") + + addr := "mongodb://localhost:27017/?connect=direct" + opts := options.Client() + opts.Deployment = md + opts.Monitor = otelmongo.NewMonitor( + otelmongo.WithTracerProvider(provider), + otelmongo.WithCommandAttributeDisabled(tc.excludeCommand), + ) + opts.ApplyURI(addr) + + md.AddResponses(tc.mockResponses...) + client, err := mongo.Connect(opts) + if err != nil { + t.FailNow() + } + + _, err = tc.operation(ctx, client.Database("test-database")) + if err != nil { + t.Error(err) + } + + span.End() + + spans := sr.Ended() + if !assert.Len(t, spans, 2, "expected 2 spans, received %d", len(spans)) { + t.FailNow() + } + assert.Len(t, spans, 2) + assert.Equal(t, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) + assert.Equal(t, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) + assert.Equal(t, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) + + s := spans[0] + assert.Equal(t, trace.SpanKindClient, s.SpanKind()) + attrs := s.Attributes() + assert.Contains(t, attrs, attribute.String("db.system", "mongodb")) + assert.Contains(t, attrs, attribute.String("net.peer.name", "")) + assert.Contains(t, attrs, attribute.Int64("net.peer.port", int64(27017))) + assert.Contains(t, attrs, attribute.String("net.transport", "ip_tcp")) + assert.Contains(t, attrs, attribute.String("db.name", "test-database")) + for _, v := range tc.validators { + assert.True(t, v(s)) + } + }) + } +} + +func TestDBCollectionAttribute(t *testing.T) { + tt := []struct { + title string + operation func(context.Context, *mongo.Database) (interface{}, error) + mockResponses []bson.D + validators []validator + }{ + { + title: "delete", + operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { + return db.Collection("test-collection").DeleteOne(ctx, bson.D{{Key: "test-item"}}) + }, + mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, + validators: []validator{ + func(s sdktrace.ReadOnlySpan) bool { + return assert.Equal(t, "test-collection.delete", s.Name()) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Contains(t, s.Attributes(), attribute.String("db.operation", "delete")) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Contains(t, s.Attributes(), attribute.String("db.mongodb.collection", "test-collection")) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Equal(t, codes.Unset, s.Status().Code) + }, + }, + }, + { + title: "listCollectionNames", + operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { + return db.ListCollectionNames(ctx, bson.D{}) + }, + mockResponses: []bson.D{ + { + {Key: "ok", Value: 1}, + {Key: "cursor", Value: bson.D{{Key: "firstBatch", Value: bson.A{}}}}, + }, + }, + validators: []validator{ + func(s sdktrace.ReadOnlySpan) bool { + return assert.Equal(t, "listCollections", s.Name()) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Contains(t, s.Attributes(), attribute.String("db.operation", "listCollections")) + }, + func(s sdktrace.ReadOnlySpan) bool { + return assert.Equal(t, codes.Unset, s.Status().Code) + }, + }, + }, + } + for _, tc := range tt { + tc := tc + + t.Run(tc.title, func(t *testing.T) { + md := drivertest.NewMockDeployment() + + sr := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") + + addr := "mongodb://localhost:27017/?connect=direct" + opts := options.Client() + opts.Deployment = md + opts.Monitor = otelmongo.NewMonitor( + otelmongo.WithTracerProvider(provider), + otelmongo.WithCommandAttributeDisabled(true), + ) + opts.ApplyURI(addr) + + md.AddResponses(tc.mockResponses...) + client, err := mongo.Connect(opts) + if err != nil { + t.FailNow() + } + + _, err = tc.operation(ctx, client.Database("test-database")) + if err != nil { + t.Error(err) + } + + span.End() + + spans := sr.Ended() + if !assert.Len(t, spans, 2, "expected 2 spans, received %d", len(spans)) { + t.FailNow() + } + assert.Len(t, spans, 2) + assert.Equal(t, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) + assert.Equal(t, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) + assert.Equal(t, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) + + s := spans[0] + assert.Equal(t, trace.SpanKindClient, s.SpanKind()) + attrs := s.Attributes() + assert.Contains(t, attrs, attribute.String("db.system", "mongodb")) + assert.Contains(t, attrs, attribute.String("net.peer.name", "")) + assert.Contains(t, attrs, attribute.Int64("net.peer.port", int64(27017))) + assert.Contains(t, attrs, attribute.String("net.transport", "ip_tcp")) + assert.Contains(t, attrs, attribute.String("db.name", "test-database")) + for _, v := range tc.validators { + assert.True(t, v(s)) + } + }) + } +} diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version.go new file mode 100644 index 00000000000..e6c1d5c800e --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package test // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test" + +// Version is the current release version of the mongo-driver instrumentation test module. +func Version() string { + return "0.58.0" + // This string is updated by the pre_release.sh script during release +} + +// SemVersion is the semantic version to be supplied to tracer/meter creation. +// +// Deprecated: Use [Version] instead. +func SemVersion() string { + return Version() +} diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version_test.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version_test.go new file mode 100644 index 00000000000..851ec95a599 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test/version_test.go @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package test_test + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test" +) + +// regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string +var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) + +func TestVersionSemver(t *testing.T) { + v := test.Version() + assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) +} diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version.go new file mode 100644 index 00000000000..93caa863b79 --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" + +// Version is the current release version of the mongo-driver instrumentation. +func Version() string { + return "0.58.0" + // This string is updated by the pre_release.sh script during release +} + +// SemVersion is the semantic version to be supplied to tracer/meter creation. +// +// Deprecated: Use [Version] instead. +func SemVersion() string { + return Version() +} diff --git a/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version_test.go b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version_test.go new file mode 100644 index 00000000000..491537d1f0d --- /dev/null +++ b/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/version_test.go @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelmongo_test + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" +) + +// regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string +var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) + +func TestVersionSemver(t *testing.T) { + v := otelmongo.Version() + assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) +} diff --git a/versions.yaml b/versions.yaml index 771487a0d6b..5d794a5029c 100644 --- a/versions.yaml +++ b/versions.yaml @@ -36,6 +36,8 @@ module-sets: - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test + - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo + - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/test - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/example - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/test