Skip to content

Commit 757add3

Browse files
authored
Migrate db.statement to db.query.text (XSAM#478)
Part of XSAM#388
1 parent ef0bd98 commit 757add3

19 files changed

+528
-32
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ Thumbs.db
99
coverage.*
1010
bin/
1111
vendor/
12+
example/otel-collector/otel-collector
13+
example/stdout/stdout

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- Support to emit query related attributes for the v1.24.0 and v1.30.0 semantic conventions based on the value of the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. (#478)
14+
15+
- `database/dup`: Emit both `db.statement` and `db.query.text` attributes.
16+
- `database`: Emit `db.query.text` attribute.
17+
- by default: Emit `db.statement` attribute.
18+
1119
## [0.38.0] - 2025-03-26
1220

1321
### Added

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ build: generate
6060
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
6161
echo "$(GO) build $${dir}/..."; \
6262
(cd "$${dir}" && \
63-
$(GO) build -o ./bin/main ./... && \
63+
$(GO) build ./... && \
6464
$(GO) list ./... \
6565
| grep -v third_party \
6666
| xargs $(GO) test -vet=off -run xxxxxMatchNothingxxxxx >/dev/null); \

config.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"go.opentelemetry.io/otel/attribute"
2323
"go.opentelemetry.io/otel/metric"
2424
"go.opentelemetry.io/otel/trace"
25+
26+
internalsemconv "github.com/XSAM/otelsql/internal/semconv"
2527
)
2628

2729
const (
@@ -90,6 +92,14 @@ type config struct {
9092
// The measurement will be recorded as status=ok.
9193
// Default is false
9294
DisableSkipErrMeasurement bool
95+
96+
// SemConvStabilityOptIn controls which database semantic convention are emitted.
97+
// It follows the value of environment variable `OTEL_SEMCONV_STABILITY_OPT_IN`.
98+
SemConvStabilityOptIn internalsemconv.OTelSemConvStabilityOptInType
99+
100+
// DBQueryTextAttributes will be called to produce related attributes on `db.query.text`.
101+
// It follows the value of environment variable `OTEL_SEMCONV_STABILITY_OPT_IN`.
102+
DBQueryTextAttributes func(query string) []attribute.KeyValue
93103
}
94104

95105
// SpanOptions holds configuration of tracing span to decide
@@ -148,6 +158,9 @@ func newConfig(options ...Option) config {
148158
TracerProvider: otel.GetTracerProvider(),
149159
MeterProvider: otel.GetMeterProvider(),
150160
SpanNameFormatter: defaultSpanNameFormatter,
161+
// Uses the stable behavior
162+
SemConvStabilityOptIn: internalsemconv.OTelSemConvStabilityOptInStable,
163+
DBQueryTextAttributes: internalsemconv.NewDBQueryTextAttributes(internalsemconv.OTelSemConvStabilityOptInStable),
151164
}
152165
for _, opt := range options {
153166
opt.Apply(&cfg)
@@ -169,5 +182,11 @@ func newConfig(options ...Option) config {
169182
otel.Handle(err)
170183
}
171184

185+
// Initialize SemConvStabilityOptIn from environment
186+
cfg.SemConvStabilityOptIn = internalsemconv.ParseOTelSemConvStabilityOptIn()
187+
188+
// Initialize DBQueryTextAttributes based on SemConvStabilityOptIn
189+
cfg.DBQueryTextAttributes = internalsemconv.NewDBQueryTextAttributes(cfg.SemConvStabilityOptIn)
190+
172191
return cfg
173192
}

config_test.go

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,31 @@ import (
2222
"go.opentelemetry.io/otel"
2323
"go.opentelemetry.io/otel/attribute"
2424
"go.opentelemetry.io/otel/metric"
25-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
25+
semconvlegacy "go.opentelemetry.io/otel/semconv/v1.24.0"
26+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
2627
"go.opentelemetry.io/otel/trace"
28+
29+
internalsemconv "github.com/XSAM/otelsql/internal/semconv"
2730
)
2831

2932
func TestNewConfig(t *testing.T) {
30-
cfg := newConfig(WithSpanOptions(SpanOptions{Ping: true}), WithAttributes(semconv.DBSystemMySQL))
33+
// Set a clean environment for the test
34+
t.Setenv(internalsemconv.OTelSemConvStabilityOptIn, "")
35+
36+
cfg := newConfig(WithSpanOptions(SpanOptions{Ping: true}), WithAttributes(semconv.DBSystemNameMySQL))
3137

3238
// Compare function result
3339
assert.Equal(t, defaultSpanNameFormatter(context.Background(), "foo", "bar"), cfg.SpanNameFormatter(context.Background(), "foo", "bar"))
34-
// Ignore function compare
40+
41+
// Verify DBQueryTextAttributes exists and returns expected format
42+
assert.NotNil(t, cfg.DBQueryTextAttributes)
43+
attrs := cfg.DBQueryTextAttributes("SELECT 1")
44+
assert.Len(t, attrs, 1)
45+
assert.Contains(t, attrs[0].Key, string(semconvlegacy.DBStatementKey))
46+
47+
// Ignore function compares for test equality check
3548
cfg.SpanNameFormatter = nil
49+
cfg.DBQueryTextAttributes = nil
3650

3751
assert.EqualValues(t, config{
3852
TracerProvider: otel.GetTracerProvider(),
@@ -49,9 +63,71 @@ func TestNewConfig(t *testing.T) {
4963
Instruments: cfg.Instruments,
5064
SpanOptions: SpanOptions{Ping: true},
5165
Attributes: []attribute.KeyValue{
52-
semconv.DBSystemMySQL,
66+
semconv.DBSystemNameMySQL,
5367
},
54-
SQLCommenter: newCommenter(false),
68+
SQLCommenter: newCommenter(false),
69+
SemConvStabilityOptIn: internalsemconv.OTelSemConvStabilityOptInNone,
5570
}, cfg)
5671
assert.NotNil(t, cfg.Instruments)
5772
}
73+
74+
func TestConfigSemConvStabilityOptIn(t *testing.T) {
75+
testCases := []struct {
76+
name string
77+
envValue string
78+
expectedOptIn internalsemconv.OTelSemConvStabilityOptInType
79+
}{
80+
{
81+
name: "none",
82+
envValue: "",
83+
expectedOptIn: internalsemconv.OTelSemConvStabilityOptInNone,
84+
},
85+
{
86+
name: "database/dup",
87+
envValue: "database/dup",
88+
expectedOptIn: internalsemconv.OTelSemConvStabilityOptInDup,
89+
},
90+
{
91+
name: "database",
92+
envValue: "database",
93+
expectedOptIn: internalsemconv.OTelSemConvStabilityOptInStable,
94+
},
95+
}
96+
97+
for _, tc := range testCases {
98+
t.Run(tc.name, func(t *testing.T) {
99+
// Use t.Setenv which automatically cleans up after the test
100+
t.Setenv(internalsemconv.OTelSemConvStabilityOptIn, tc.envValue)
101+
102+
// Create new config
103+
cfg := newConfig()
104+
105+
// Check that SemConvStabilityOptIn is correctly set
106+
assert.Equal(t, tc.expectedOptIn, cfg.SemConvStabilityOptIn)
107+
108+
// Check that DBQueryTextAttributes is initialized
109+
assert.NotNil(t, cfg.DBQueryTextAttributes)
110+
111+
// Test with a sample query to verify it returns the expected attributes format
112+
const query = "SELECT * FROM test"
113+
attrs := cfg.DBQueryTextAttributes(query)
114+
115+
// Verify format of returned attributes based on opt-in type
116+
switch tc.expectedOptIn {
117+
case internalsemconv.OTelSemConvStabilityOptInNone:
118+
assert.Equal(t, attrs, []attribute.KeyValue{
119+
semconvlegacy.DBStatementKey.String(query),
120+
})
121+
case internalsemconv.OTelSemConvStabilityOptInDup:
122+
assert.Equal(t, attrs, []attribute.KeyValue{
123+
semconvlegacy.DBStatementKey.String(query),
124+
semconv.DBQueryTextKey.String(query),
125+
})
126+
case internalsemconv.OTelSemConvStabilityOptInStable:
127+
assert.Equal(t, attrs, []attribute.KeyValue{
128+
semconv.DBQueryTextKey.String(query),
129+
})
130+
}
131+
})
132+
}
133+
}

conn_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"github.com/stretchr/testify/assert"
2424
"github.com/stretchr/testify/require"
2525
"go.opentelemetry.io/otel/attribute"
26-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
26+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
2727
"go.opentelemetry.io/otel/trace"
2828
)
2929

@@ -268,7 +268,7 @@ func TestOtConn_Ping(t *testing.T) {
268268
func TestOtConn_ExecContext(t *testing.T) {
269269
query := "query"
270270
args := []driver.NamedValue{{Value: "foo"}}
271-
expectedAttrs := []attribute.KeyValue{semconv.DBStatementKey.String(query)}
271+
expectedAttrs := []attribute.KeyValue{semconv.DBQueryTextKey.String(query)}
272272

273273
testCases := []struct {
274274
name string
@@ -362,7 +362,7 @@ func TestOtConn_ExecContext(t *testing.T) {
362362
func TestOtConn_QueryContext(t *testing.T) {
363363
query := "query"
364364
args := []driver.NamedValue{{Value: "foo"}}
365-
expectedAttrs := []attribute.KeyValue{semconv.DBStatementKey.String(query)}
365+
expectedAttrs := []attribute.KeyValue{semconv.DBQueryTextKey.String(query)}
366366

367367
for _, omitConnQuery := range []bool{true, false} {
368368
var testname string
@@ -488,7 +488,7 @@ func TestOtConn_QueryContext(t *testing.T) {
488488

489489
func TestOtConn_PrepareContext(t *testing.T) {
490490
query := "query"
491-
expectedAttrs := []attribute.KeyValue{semconv.DBStatementKey.String(query)}
491+
expectedAttrs := []attribute.KeyValue{semconv.DBQueryTextKey.String(query)}
492492

493493
for _, legacy := range []bool{true, false} {
494494
var testname string

driver_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"github.com/stretchr/testify/assert"
2323
"github.com/stretchr/testify/require"
2424
"go.opentelemetry.io/otel/attribute"
25-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
25+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
2626
)
2727

2828
type mockDriver struct {
@@ -65,12 +65,12 @@ var (
6565
)
6666

6767
func TestNewDriver(t *testing.T) {
68-
d := newDriver(newMockDriver(false), config{Attributes: []attribute.KeyValue{semconv.DBSystemMySQL}})
68+
d := newDriver(newMockDriver(false), config{Attributes: []attribute.KeyValue{semconv.DBSystemNameMySQL}})
6969

7070
otelDriver, ok := d.(*otDriver)
7171
require.True(t, ok)
7272
assert.Equal(t, newMockDriver(false), otelDriver.driver)
73-
assert.Equal(t, config{Attributes: []attribute.KeyValue{semconv.DBSystemMySQL}}, otelDriver.cfg)
73+
assert.Equal(t, config{Attributes: []attribute.KeyValue{semconv.DBSystemNameMySQL}}, otelDriver.cfg)
7474
}
7575

7676
func TestOtDriver_Open(t *testing.T) {

example/otel-collector/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
3232
"go.opentelemetry.io/otel/sdk/resource"
3333
sdktrace "go.opentelemetry.io/otel/sdk/trace"
34-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
34+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
3535
"google.golang.org/grpc"
3636
"google.golang.org/grpc/credentials/insecure"
3737

@@ -162,7 +162,7 @@ func main() {
162162
}
163163

164164
func connectDB() *sql.DB {
165-
attrs := append(otelsql.AttributesFromDSN(mysqlDSN), semconv.DBSystemMySQL)
165+
attrs := append(otelsql.AttributesFromDSN(mysqlDSN), semconv.DBSystemNameMySQL)
166166

167167
// Connect to database
168168
db, err := otelsql.Open("mysql", mysqlDSN, otelsql.WithAttributes(

example/stdout/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
"go.opentelemetry.io/otel/sdk/metric"
3131
"go.opentelemetry.io/otel/sdk/resource"
3232
sdktrace "go.opentelemetry.io/otel/sdk/trace"
33-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
33+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
3434

3535
"github.com/XSAM/otelsql"
3636
)
@@ -86,7 +86,7 @@ func main() {
8686
initTracer()
8787
initMeter()
8888

89-
attrs := append(otelsql.AttributesFromDSN(mysqlDSN), semconv.DBSystemMySQL)
89+
attrs := append(otelsql.AttributesFromDSN(mysqlDSN), semconv.DBSystemNameMySQL)
9090

9191
// Connect to database
9292
db, err := otelsql.Open("mysql", mysqlDSN, otelsql.WithAttributes(

example_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"database/sql"
1919
"database/sql/driver"
2020

21-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
21+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
2222

2323
"github.com/XSAM/otelsql"
2424
)
@@ -80,7 +80,7 @@ func ExampleRegister() {
8080
}
8181

8282
func ExampleAttributesFromDSN() {
83-
attrs := append(otelsql.AttributesFromDSN(mysqlDSN), semconv.DBSystemMySQL)
83+
attrs := append(otelsql.AttributesFromDSN(mysqlDSN), semconv.DBSystemNameMySQL)
8484

8585
// Connect to database
8686
db, err := otelsql.Open("mysql", mysqlDSN, otelsql.WithAttributes(

helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"strings"
2121

2222
"go.opentelemetry.io/otel/attribute"
23-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
23+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
2424
)
2525

2626
// AttributesFromDSN returns attributes extracted from a DSN string.

helpers_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919

2020
"github.com/stretchr/testify/assert"
2121
"go.opentelemetry.io/otel/attribute"
22-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
22+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
2323
)
2424

2525
func TestAttributesFromDSN(t *testing.T) {

internal/semconv/attributes.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright Sam Xie
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package semconv
16+
17+
import (
18+
"go.opentelemetry.io/otel/attribute"
19+
semconvlegacy "go.opentelemetry.io/otel/semconv/v1.24.0"
20+
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
21+
)
22+
23+
// NewDBQueryTextAttributes returns a function that generates appropriate database query attributes
24+
// based on the provided OTelSemConvStabilityOptInType.
25+
//
26+
// - OTelSemConvStabilityOptInNone: Only legacy db.statement attribute
27+
// - OTelSemConvStabilityOptInDup: Both legacy db.statement and stable db.query.text attributes
28+
// - OTelSemConvStabilityOptInStable: Only stable db.query.text attribute
29+
func NewDBQueryTextAttributes(optInType OTelSemConvStabilityOptInType) func(query string) []attribute.KeyValue {
30+
switch optInType {
31+
case OTelSemConvStabilityOptInDup:
32+
// Emit both legacy and stable attributes
33+
return func(query string) []attribute.KeyValue {
34+
return []attribute.KeyValue{
35+
semconvlegacy.DBStatementKey.String(query),
36+
semconv.DBQueryTextKey.String(query),
37+
}
38+
}
39+
case OTelSemConvStabilityOptInStable:
40+
// Only emit stable attribute
41+
return func(query string) []attribute.KeyValue {
42+
return []attribute.KeyValue{
43+
semconv.DBQueryTextKey.String(query),
44+
}
45+
}
46+
default:
47+
// OTelSemConvStabilityOptInNone or any unknown types
48+
// Only emit legacy attribute
49+
return func(query string) []attribute.KeyValue {
50+
return []attribute.KeyValue{
51+
semconvlegacy.DBStatementKey.String(query),
52+
}
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)