Skip to content

Commit 6b4d5d2

Browse files
authored
Add OTLP trace export and bump spannerotel to v0.2.0 (#67)
Add --experimental-trace-otlp for local OpenTelemetry collectors, bump spannerotel to v0.2.0, and expand tracing documentation.
1 parent 0156f4c commit 6b4d5d2

6 files changed

Lines changed: 100 additions & 15 deletions

File tree

README.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ Application Options:
3939
--param= [name]=[Cloud Spanner type(PLAN only) or literal]; legacy name:value OK
4040
--param-file= YAML or JSON file of query parameters
4141
--log-grpc Show gRPC logs
42-
--experimental-trace-project=
43-
--experimental-trace-stdout Export traces to stderr (local debugging)
42+
--experimental-trace-project= Export traces to Cloud Trace
43+
--experimental-trace-stdout Export spans to stderr as pretty JSON
44+
--experimental-trace-otlp Export spans via OTLP/gRPC (local collector)
45+
--experimental-trace-otlp-endpoint= OTLP/gRPC endpoint (default: localhost:4317)
4446
--enable-partitioned-dml Execute DML statement using Partitioned DML
4547
--timeout= Maximum time to wait for the SQL query to complete (default: 10m)
4648
--try-partition-query (Experimental) Check whether the query can be executed as partition query or not
@@ -240,23 +242,43 @@ $ execspansql ${DATABASE_ID} --query-mode=NORMAL \
240242
--param='songinfo=STRUCT<SongName STRING, ArtistNames ARRAY<STRUCT<FirstName STRING, LastName STRING>>>("Imagination", [("Elena", "Campbell"), ("Hannah", "Harris")])'
241243
```
242244

243-
### (Experimental) Cloud Trace integration
245+
### (Experimental) OpenTelemetry tracing
244246

245-
Export PROFILE query plans and Spanner client spans via OpenTelemetry (`spannerotel` + the Spanner client's native OTel instrumentation).
247+
Export Spanner client spans and PROFILE query plans via OpenTelemetry (`spannerotel` + the Spanner client's native OTel instrumentation).
246248

247-
Export to Cloud Trace:
249+
Plan node spans (`spannerotel/plantotrace`) appear only with **`--query-mode=PROFILE`** (or equivalent stats that include a query plan). NORMAL mode still records Spanner client spans, but not per-plan-node children.
250+
251+
Exactly one trace export flag may be set: `--experimental-trace-project`, `--experimental-trace-stdout`, or `--experimental-trace-otlp`.
252+
253+
#### Cloud Trace
248254

249255
```sh
250-
$ execspansql $DATABASE_ID --sql "SELECT * FROM Singers@{FORCE_INDEX=SingersByFirstLastName}" --query-mode=PROFILE --experimental-trace-project=$PROJECT_ID
256+
$ execspansql $DATABASE_ID --sql "SELECT * FROM Singers@{FORCE_INDEX=SingersByFirstLastName}" \
257+
--query-mode=PROFILE --experimental-trace-project=$PROJECT_ID
251258
```
252259

253-
For local debugging without Cloud Trace credentials, write spans to stderr:
260+
#### Local collector (OTLP/gRPC)
261+
262+
Send spans to a local OpenTelemetry Collector, Jaeger, Grafana Tempo, etc.:
263+
264+
```sh
265+
# Example: collector listening on localhost:4317
266+
$ execspansql $DATABASE_ID --query-mode=PROFILE --sql 'SELECT 1' --experimental-trace-otlp
267+
268+
# Custom endpoint
269+
$ execspansql $DATABASE_ID --query-mode=PROFILE --sql 'SELECT 1' \
270+
--experimental-trace-otlp --experimental-trace-otlp-endpoint=127.0.0.1:4317
271+
```
272+
273+
#### stderr JSON (no collector)
274+
275+
Pretty-printed span JSON to stderr:
254276

255277
```sh
256278
$ execspansql $DATABASE_ID --query-mode=PROFILE --sql 'SELECT 1' --experimental-trace-stdout
257279
```
258280

259-
`--experimental-trace-stdout` and `--experimental-trace-project` are mutually exclusive.
281+
Note: `--experimental-trace-stdout` writes to **stderr**, not stdout.
260282

261283
![trace.png](docs/trace.png)
262284

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/apstndb/memebridge v0.6.1
1010
github.com/apstndb/spanemuboost v0.4.6
1111
github.com/apstndb/spaniter v0.3.0
12-
github.com/apstndb/spannerotel v0.1.0
12+
github.com/apstndb/spannerotel v0.2.0
1313
github.com/apstndb/spanvalue v0.8.0
1414
github.com/cloudspannerecosystem/memefish v0.6.2
1515
github.com/goccy/go-yaml v1.19.2
@@ -43,6 +43,7 @@ require (
4343
github.com/Microsoft/go-winio v0.6.2 // indirect
4444
github.com/apstndb/spantype v0.3.11 // indirect
4545
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
46+
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
4647
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4748
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
4849
github.com/containerd/errdefs v1.0.0 // indirect
@@ -67,6 +68,7 @@ require (
6768
github.com/google/uuid v1.6.0 // indirect
6869
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
6970
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
71+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
7072
github.com/itchyny/timefmt-go v0.1.7 // indirect
7173
github.com/klauspost/compress v1.18.5 // indirect
7274
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
@@ -100,10 +102,13 @@ require (
100102
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 // indirect
101103
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
102104
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
105+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
106+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect
103107
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 // indirect
104108
go.opentelemetry.io/otel/metric v1.44.0 // indirect
105109
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
106110
go.opentelemetry.io/otel/trace v1.44.0 // indirect
111+
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
107112
go.uber.org/multierr v1.10.0 // indirect
108113
golang.org/x/crypto v0.51.0 // indirect
109114
golang.org/x/net v0.55.0 // indirect

go.sum

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,17 @@ github.com/apstndb/spanemuboost v0.4.6 h1:9f1qQDLNrPQJiswNLtiTaR0U//zToqxjcEGWGk
5656
github.com/apstndb/spanemuboost v0.4.6/go.mod h1:urUe85EvqomWV4vCj2pALluNhIksbu9RAQZufgq1b8E=
5757
github.com/apstndb/spaniter v0.3.0 h1:b0zXMONClGRfvWf7ciwsIYVso9qkhqFdjH0+PqOnQGI=
5858
github.com/apstndb/spaniter v0.3.0/go.mod h1:aBSHcHIqgAZXCxFdi734R/wAQUIuCQ6WZ+CjOCxARIM=
59-
github.com/apstndb/spannerotel v0.1.0 h1:gAEyMMhkKD2+OyUw6NrDCEhYVx7q7GkB75l7vWsamK4=
60-
github.com/apstndb/spannerotel v0.1.0/go.mod h1:WHD4+pRgOBckpBXnL7bd4lYrZebYjYLZ08KGCtQrrXg=
59+
github.com/apstndb/spannerotel v0.2.0 h1:EpGzxB9CfnRedlOlO/x4+c8+vOEbptGcd0PegEMsKYk=
60+
github.com/apstndb/spannerotel v0.2.0/go.mod h1:tD+JGppXRRgxPvlfc4OADUGgpHNSkoZ2qbHwbZ3rtJo=
6161
github.com/apstndb/spantype v0.3.11 h1:wKue4WLYGT82MH3B3TRSFn8tWIbk1Geczs+5BAEtnK4=
6262
github.com/apstndb/spantype v0.3.11/go.mod h1:9eHowE7LcJ155ukCYUyuNzVAw9Ne0GXPXpHmu+iaMyk=
6363
github.com/apstndb/spanvalue v0.8.0 h1:wLHl/0m5C6PvRwJOJoz1nRL4qSpS17eS1tW3Pjzcvo8=
6464
github.com/apstndb/spanvalue v0.8.0/go.mod h1:bqVJYydQf+D0Tux1LtyRlREPVwrVYNG+1usZJ489GTA=
6565
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
6666
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
6767
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
68+
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
69+
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
6870
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
6971
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
7072
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -167,6 +169,8 @@ github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU
167169
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
168170
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
169171
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
172+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
173+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
170174
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
171175
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
172176
github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA=
@@ -278,6 +282,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:Oyrsyzu
278282
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
279283
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
280284
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
285+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
286+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
287+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
288+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
281289
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 h1:bl2S7Ubua0Nms+D/gAmznQTd4dxxMA93aKbcpKqiTCs=
282290
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0=
283291
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
@@ -290,6 +298,8 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk
290298
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
291299
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
292300
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
301+
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
302+
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
293303
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
294304
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
295305
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=

main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ type opts struct {
7070
ParamFile string `name:"param-file" help:"YAML or JSON file of query parameters (name to type/literal string)"`
7171
LogGrpc bool `name:"log-grpc" help:"Show gRPC logs"`
7272
TraceProject string `name:"experimental-trace-project" xor:"trace" help:"Export traces to Cloud Trace in the given project."`
73-
TraceStdout bool `name:"experimental-trace-stdout" xor:"trace" help:"Export traces to stderr (local debugging; no Cloud Trace credentials required)."`
73+
TraceStdout bool `name:"experimental-trace-stdout" xor:"trace" help:"Export spans to stderr as pretty JSON (local debugging)."`
74+
TraceOTLP bool `name:"experimental-trace-otlp" xor:"trace" help:"Export spans via OTLP/gRPC to a local OpenTelemetry collector."`
75+
TraceOTLPEndpoint string `name:"experimental-trace-otlp-endpoint" default:"localhost:4317" help:"OTLP/gRPC endpoint used with --experimental-trace-otlp."`
7476
EnablePartitionedDML bool `name:"enable-partitioned-dml" help:"Execute DML statement using Partitioned DML"`
7577
Timeout time.Duration `name:"timeout" default:"10m" help:"Maximum time to wait for the SQL query to complete"`
7678
TryPartitionQuery bool `name:"try-partition-query" help:"(Experimental) Check whether the query can be executed as partition query or not"`

trace.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,45 @@ import (
1010
sdktrace "go.opentelemetry.io/otel/sdk/trace"
1111
)
1212

13+
const traceServiceName = "execspansql"
14+
1315
func tracingEnabled(o opts) bool {
14-
return o.TraceStdout || o.TraceProject != ""
16+
return o.TraceStdout || o.TraceProject != "" || o.TraceOTLP
1517
}
1618

1719
func traceConfig(o opts) (tracing.Config, error) {
20+
n := 0
21+
if o.TraceOTLP {
22+
n++
23+
}
24+
if o.TraceStdout {
25+
n++
26+
}
27+
if o.TraceProject != "" {
28+
n++
29+
}
30+
if n != 1 {
31+
return tracing.Config{}, fmt.Errorf("exactly one of --experimental-trace-otlp, --experimental-trace-stdout, or --experimental-trace-project must be set")
32+
}
1833
switch {
19-
case o.TraceStdout && o.TraceProject != "":
20-
return tracing.Config{}, fmt.Errorf("use either --experimental-trace-stdout or --experimental-trace-project, not both")
34+
case o.TraceOTLP:
35+
return tracing.Config{
36+
Exporter: tracing.ExporterOTLP,
37+
ServiceName: traceServiceName,
38+
OTLPEndpoint: o.TraceOTLPEndpoint,
39+
OTLPInsecure: true,
40+
}, nil
2141
case o.TraceStdout:
2242
return tracing.Config{
2343
Exporter: tracing.ExporterStdout,
44+
ServiceName: traceServiceName,
2445
StdoutWriter: os.Stderr,
2546
PrettyStdout: true,
2647
}, nil
2748
case o.TraceProject != "":
2849
return tracing.Config{
2950
Exporter: tracing.ExporterCloudTrace,
51+
ServiceName: traceServiceName,
3052
CloudTraceProject: o.TraceProject,
3153
}, nil
3254
default:

trace_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@ func TestTraceConfigMutuallyExclusive(t *testing.T) {
2929
}
3030
}
3131

32+
func TestTraceConfigOTLP(t *testing.T) {
33+
t.Parallel()
34+
35+
cfg, err := traceConfig(opts{TraceOTLP: true, TraceOTLPEndpoint: "127.0.0.1:4317"})
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
if cfg.Exporter != tracing.ExporterOTLP {
40+
t.Fatalf("exporter = %q, want %q", cfg.Exporter, tracing.ExporterOTLP)
41+
}
42+
if cfg.OTLPEndpoint != "127.0.0.1:4317" {
43+
t.Fatalf("endpoint = %q", cfg.OTLPEndpoint)
44+
}
45+
if cfg.ServiceName != "execspansql" {
46+
t.Fatalf("service name = %q", cfg.ServiceName)
47+
}
48+
if !cfg.OTLPInsecure {
49+
t.Fatal("expected insecure otlp for local collector")
50+
}
51+
}
52+
3253
func TestTraceFlagsMutuallyExclusiveViaKong(t *testing.T) {
3354
t.Parallel()
3455

@@ -82,6 +103,9 @@ func TestTracingEnabled(t *testing.T) {
82103
if !tracingEnabled(opts{TraceProject: "demo"}) {
83104
t.Fatal("project should enable tracing")
84105
}
106+
if !tracingEnabled(opts{TraceOTLP: true}) {
107+
t.Fatal("otlp should enable tracing")
108+
}
85109
if tracingEnabled(opts{}) {
86110
t.Fatal("expected tracing disabled by default")
87111
}

0 commit comments

Comments
 (0)