diff --git a/attributes_propagator.go b/attributes_propagator.go new file mode 100644 index 00000000..72bc3b90 --- /dev/null +++ b/attributes_propagator.go @@ -0,0 +1,36 @@ +package otelsql + +import ( + "context" + + "go.opentelemetry.io/otel/propagation" +) + +type TextAttributesPropagator struct { + Attributes map[string]string +} + +var _ propagation.TextMapPropagator = TextAttributesPropagator{} + +func (p TextAttributesPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { + for k, v := range p.Attributes { + carrier.Set(k, v) + } +} + +func (p TextAttributesPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { + for _, k := range carrier.Keys() { + if _, ok := p.Attributes[k]; ok { + p.Attributes[k] = carrier.Get(k) + } + } + return ctx +} + +func (p TextAttributesPropagator) Fields() []string { + var keys []string + for k := range p.Attributes { + keys = append(keys, k) + } + return keys +} diff --git a/commenter.go b/commenter.go index 96f19d54..21ef4482 100644 --- a/commenter.go +++ b/commenter.go @@ -45,10 +45,14 @@ type commenter struct { propagator propagation.TextMapPropagator } -func newCommenter(enabled bool) *commenter { +func newCommenter(enabled bool, propagator propagation.TextMapPropagator) *commenter { + if propagator == nil { + propagator = otel.GetTextMapPropagator() + } + return &commenter{ enabled: enabled, - propagator: otel.GetTextMapPropagator(), + propagator: propagator, } } diff --git a/commenter_test.go b/commenter_test.go index 8baf1142..b1324191 100644 --- a/commenter_test.go +++ b/commenter_test.go @@ -75,7 +75,7 @@ func TestCommenter_WithComment(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - c := newCommenter(tc.enabled) + c := newCommenter(tc.enabled, nil) c.propagator = propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) result := c.withComment(tc.ctx, query) diff --git a/config.go b/config.go index ada9ab79..126b0234 100644 --- a/config.go +++ b/config.go @@ -21,6 +21,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" internalsemconv "github.com/XSAM/otelsql/internal/semconv" @@ -77,8 +78,9 @@ type config struct { // // Notice: This config is EXPERIMENTAL and may be changed or removed in a // later release. - SQLCommenterEnabled bool - SQLCommenter *commenter + SQLCommenterEnabled bool + SQLCommenterPropagator propagation.TextMapPropagator + SQLCommenter *commenter // AttributesGetter will be called to produce additional attributes while creating spans. // Default returns nil @@ -179,7 +181,7 @@ func newConfig(options ...Option) config { metric.WithInstrumentationVersion(Version()), ) - cfg.SQLCommenter = newCommenter(cfg.SQLCommenterEnabled) + cfg.SQLCommenter = newCommenter(cfg.SQLCommenterEnabled, cfg.SQLCommenterPropagator) var err error if cfg.Instruments, err = newInstruments(cfg.Meter); err != nil { diff --git a/config_test.go b/config_test.go index 1830dbd3..1327381f 100644 --- a/config_test.go +++ b/config_test.go @@ -69,7 +69,7 @@ func TestNewConfig(t *testing.T) { Attributes: []attribute.KeyValue{ semconv.DBSystemNameMySQL, }, - SQLCommenter: newCommenter(false), + SQLCommenter: newCommenter(false, nil), SemConvStabilityOptIn: internalsemconv.OTelSemConvStabilityOptInNone, }, cfg) assert.NotNil(t, cfg.Instruments) diff --git a/example/sql-commenter/Dockerfile b/example/sql-commenter/Dockerfile new file mode 100644 index 00000000..17d1b140 --- /dev/null +++ b/example/sql-commenter/Dockerfile @@ -0,0 +1,20 @@ +# Copyright Sam Xie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +FROM golang:1.24-alpine@sha256:ef18ee7117463ac1055f5a370ed18b8750f01589f13ea0b48642f5792b234044 AS base +COPY .. /src/otelsql +WORKDIR /src/otelsql/example/sql-commenter + +FROM base +RUN go install main.go +CMD ["/go/bin/main"] \ No newline at end of file diff --git a/example/sql-commenter/docker-compose.yaml b/example/sql-commenter/docker-compose.yaml new file mode 100644 index 00000000..bbbfbc96 --- /dev/null +++ b/example/sql-commenter/docker-compose.yaml @@ -0,0 +1,36 @@ +# Copyright Sam Xie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +services: + mssql: + image: mcr.microsoft.com/mssql/server:2022-latest + platform: linux/amd64 + environment: + ACCEPT_EULA: 'Y' + MSSQL_SA_PASSWORD: 'Passw0rd' + MSSQL_DATA_DIR: /var/opt/mssql/data + MSSQL_PID: 'Developer' + MSSQL_TCP_PORT: 1433 + ports: + - "1433:1433" + + client: + build: + dockerfile: $PWD/Dockerfile + context: ../.. + + otel-collector: + image: otel/opentelemetry-collector-contrib:0.121.0@sha256:789689988e379c58ac12b07718dbcf4b23c2214bd804173c1c77af346d381c15 + command: ["--config=/etc/otel-collector.yaml"] + volumes: + - ./otel-collector.yaml:/etc/otel-collector.yaml \ No newline at end of file diff --git a/example/sql-commenter/go.mod b/example/sql-commenter/go.mod new file mode 100644 index 00000000..8dce8bfe --- /dev/null +++ b/example/sql-commenter/go.mod @@ -0,0 +1,38 @@ +module github.com/XSAM/otelsql/example/sql-commenter + +go 1.23.0 + +godebug x509negativeserial=1 + +replace github.com/XSAM/otelsql => ../../ + +require ( + github.com/XSAM/otelsql v0.39.0 + github.com/microsoft/go-mssqldb v1.7.2 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 + go.opentelemetry.io/otel/sdk v1.37.0 + google.golang.org/grpc v1.73.0 +) + +require ( + github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/example/sql-commenter/go.sum b/example/sql-commenter/go.sum new file mode 100644 index 00000000..75c0d86f --- /dev/null +++ b/example/sql-commenter/go.sum @@ -0,0 +1,83 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +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/example/sql-commenter/main.go b/example/sql-commenter/main.go new file mode 100644 index 00000000..1c48ed02 --- /dev/null +++ b/example/sql-commenter/main.go @@ -0,0 +1,172 @@ +// Copyright Sam Xie +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package main implements an example application that demonstrates how to use otelsql +// with OpenTelemetry Collector for tracing and metrics collection. +package main + +import ( + "context" + "database/sql" + "fmt" + "log" + "log/slog" + "os" + "os/signal" + + _ "github.com/microsoft/go-mssqldb" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.30.0" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/XSAM/otelsql" +) + +const instrumentationName = "github.com/XSAM/otelsql/example/sqlserver" + +var serviceName = semconv.ServiceNameKey.String("otelsql-example") + +var DSN = "sqlserver://sa:Passw0rd@mssql:1433/instance" + +// Initialize a gRPC connection to be used by both the tracer and meter +// providers. +func initConn() (*grpc.ClientConn, error) { + // Make a gRPC connection with otel collector. + conn, err := grpc.NewClient("otel-collector:4317", + // Note the use of insecure transport here. TLS is recommended in production. + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err) + } + + return conn, nil +} + +// Initializes an OTLP exporter, and configures the corresponding trace providers. +func initTracerProvider(ctx context.Context, conn *grpc.ClientConn) (func(context.Context) error, error) { + res, err := resource.New(ctx, + resource.WithAttributes( + // the service name used to display traces in backends + serviceName, + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create resource: %w", err) + } + + // Set up a trace exporter + traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) + if err != nil { + return nil, fmt.Errorf("failed to create trace exporter: %w", err) + } + + // Register the trace exporter with a TracerProvider, using a batch + // span processor to aggregate spans before export. + bsp := sdktrace.NewBatchSpanProcessor(traceExporter) + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(res), + sdktrace.WithSpanProcessor(bsp), + ) + otel.SetTracerProvider(tracerProvider) + + // set global propagator to tracecontext (the default is no-op). + otel.SetTextMapPropagator(propagation.TraceContext{}) + + // Shutdown will flush any remaining spans and shut down the exporter. + return tracerProvider.Shutdown, nil +} + +func run() error { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + conn, err := initConn() + if err != nil { + return err + } + + shutdownTracerProvider, err := initTracerProvider(ctx, conn) + if err != nil { + return err + } + defer func() { + if err := shutdownTracerProvider(ctx); err != nil { + slog.Error("failed to shutdown TracerProvider", "error", err) + } + }() + + db := connectDB() + defer func() { _ = db.Close() }() + + err = runSQLQuery(ctx, db) + if err != nil { + return err + } + + slog.Info("Example finished") + + return nil +} + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } +} + +func connectDB() *sql.DB { + // Connect to database + db, err := otelsql.Open("sqlserver", DSN, otelsql.WithSQLCommenter(true, + propagation.NewCompositeTextMapPropagator( + otelsql.TextAttributesPropagator{Attributes: map[string]string{string(semconv.ServiceNameKey): "otelsql-example"}}, + propagation.TraceContext{}, // Optional, if you want to propagate trace context + ), + )) + if err != nil { + log.Fatal(err) + } + + return db +} + +func runSQLQuery(ctx context.Context, db *sql.DB) error { + // Create a parent span (Optional) + tracer := otel.GetTracerProvider() + ctx, span := tracer.Tracer(instrumentationName).Start(ctx, "example") + defer span.End() + + err := query(ctx, db) + if err != nil { + span.RecordError(err) + return err + } + return nil +} + +func query(ctx context.Context, db *sql.DB) error { + // Add WAITFOR DELAY to simulate a long-running query that could take 5 seconds, so we can easily catch it by querying query samples. + _, err := db.ExecContext(ctx, `SELECT * FROM sys.dm_exec_connections; WAITFOR DELAY '00:00:05'`) + if err != nil { + return err + } + + return nil +} diff --git a/example/sql-commenter/otel-collector.yaml b/example/sql-commenter/otel-collector.yaml new file mode 100644 index 00000000..2ef7f944 --- /dev/null +++ b/example/sql-commenter/otel-collector.yaml @@ -0,0 +1,32 @@ +receivers: + # Make sure to add the otlp receiver. + # This will open up the receiver on port 4317 + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" +processors: + batch: + # https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor/memorylimiterprocessor + memory_limiter: + # 75% of total memory + limit_percentage: 75 + check_interval: 1s +extensions: + health_check: {} +exporters: + debug: + verbosity: detailed + +service: + extensions: [health_check] + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [debug] + + metrics: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [debug] \ No newline at end of file diff --git a/option.go b/option.go index f8beb854..7255857f 100644 --- a/option.go +++ b/option.go @@ -17,6 +17,7 @@ package otelsql import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) @@ -86,12 +87,14 @@ func WithMeterProvider(provider metric.MeterProvider) Option { // SELECT * from FOO /*traceparent='00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',tracestate='congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7'*/ // // This option defaults to disable. +// If propagator is not nil, it will use the global propagator. // // Notice: This option is EXPERIMENTAL and may be changed or removed in a // later release. -func WithSQLCommenter(enabled bool) Option { +func WithSQLCommenter(enabled bool, propagator propagation.TextMapPropagator) Option { return OptionFunc(func(cfg *config) { cfg.SQLCommenterEnabled = enabled + cfg.SQLCommenterPropagator = propagator }) } diff --git a/option_test.go b/option_test.go index 8889efca..7c1b6c9a 100644 --- a/option_test.go +++ b/option_test.go @@ -75,7 +75,7 @@ func TestOptions(t *testing.T) { }, { name: "WithSQLCommenter", - option: WithSQLCommenter(true), + option: WithSQLCommenter(true, nil), expectedConfig: config{SQLCommenterEnabled: true}, }, {