Skip to content

Commit 7669a9a

Browse files
authored
Merge branch 'main' into feature/aws-proxy-sdk-v2-migration
2 parents 88403eb + 4158922 commit 7669a9a

21 files changed

Lines changed: 223 additions & 31 deletions

File tree

.chloggen/add-tracing-support.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type:
5+
enhancement
6+
7+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
8+
component:
9+
receiver/mysql
10+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
11+
note:
12+
Propagates W3C TraceContext from MySQL session variables to query sample log records. When a MySQL session sets `@traceparent`, the receiver extracts the TraceID and SpanID and stamps them onto the corresponding `db.server.query_sample` log record, enabling correlation between application traces and query samples.
13+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
14+
issues: [46631]
15+
16+
# (Optional) One or more lines of additional information to render under the primary note.
17+
# These lines will be padded with 2 spaces and then inserted directly into the document.
18+
# Use pipe (|) for multiline entries.
19+
subtext:
20+
Only samples from sessions where `@traceparent` is set will have non-zero `traceId` and `spanId` fields on the log record.
21+
# If your change doesn't affect end users or the exported elements of any package,
22+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
23+
# Optional: The change log or logs in which this entry should be included.
24+
# e.g. '[user]' or '[user, api]'
25+
# Include 'user' if the change is relevant to end users.
26+
# Include 'api' if there is a change to a library API.
27+
# Default: '[user]'
28+
change_logs: [user]

internal/docker/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
go.opentelemetry.io/collector/confmap v1.54.1-0.20260330144813-4d17eb8959de
1414
go.uber.org/goleak v1.3.0
1515
go.uber.org/zap v1.27.1
16-
golang.org/x/sync v0.19.0
16+
golang.org/x/sync v0.20.0
1717
)
1818

1919
require (

internal/docker/go.sum

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

receiver/dockerstatsreceiver/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ require (
108108
go.opentelemetry.io/otel/trace v1.42.0 // indirect
109109
go.yaml.in/yaml/v3 v3.0.4 // indirect
110110
golang.org/x/crypto v0.48.0 // indirect
111-
golang.org/x/sync v0.19.0 // indirect
111+
golang.org/x/sync v0.20.0 // indirect
112112
golang.org/x/sys v0.41.0 // indirect
113113
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
114114
google.golang.org/grpc v1.79.3 // indirect

receiver/dockerstatsreceiver/go.sum

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

receiver/mysqlreceiver/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ Details about the metrics produced by this receiver can be found in [metadata.ya
9797
## Logs
9898
Details about the logs produced by this receiver can be found in [documentation.md](./documentation.md)
9999
100+
**Trace propagation:** Query sample log records carry a TraceID and SpanID only when
101+
a MySQL session sets the `@traceparent` user variable (W3C TraceContext format,
102+
lowercase name). The collector extracts the TraceID and SpanID from that value and
103+
stamps the application's trace context onto the corresponding log record. Log records
104+
without a `@traceparent` will have empty TraceID and SpanID fields. This allows
105+
application transactions to be correlated with query samples collected by this receiver.
106+
107+
> **Note:** MySQL stores user variable names in lowercase in
108+
> `performance_schema.user_variables_by_thread` regardless of how the client
109+
> spelled them. The JOIN condition `VARIABLE_NAME = 'traceparent'` therefore
110+
> matches any case variation the client used (e.g. `SET @TraceParent = '...'`
111+
> works identically to `SET @traceparent = '...'`).
100112
### MySQL Requirements to enable log collection
101113

102114
| Parameter | Value | Description |

receiver/mysqlreceiver/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ type querySample struct {
211211
sessionStatus string
212212
waitEvent string
213213
waitTime float64
214+
traceparent string
214215
}
215216

216217
type topQuery struct {
@@ -780,6 +781,7 @@ func (c *mySQLClient) getQuerySamples(limit uint64) ([]querySample, error) {
780781
&s.sessionStatus,
781782
&s.waitEvent,
782783
&s.waitTime,
784+
&s.traceparent,
783785
)
784786
if err != nil {
785787
return nil, err

receiver/mysqlreceiver/factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func createDefaultConfig() component.Config {
4040
Transport: confignet.TransportTypeTCP,
4141
},
4242
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
43+
LogsBuilderConfig: metadata.DefaultLogsBuilderConfig(),
4344
StatementEvents: StatementEventsConfig{
4445
DigestTextLimit: defaultStatementEventsDigestTextLimit,
4546
Limit: defaultStatementEventsLimit,

receiver/mysqlreceiver/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ require (
113113
go.opentelemetry.io/collector/pipeline v1.54.1-0.20260330144813-4d17eb8959de // indirect
114114
go.opentelemetry.io/collector/scraper v0.148.1-0.20260330144813-4d17eb8959de
115115
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
116-
go.opentelemetry.io/otel v1.42.0 // indirect
116+
go.opentelemetry.io/otel v1.42.0
117117
go.opentelemetry.io/otel/metric v1.42.0 // indirect
118118
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
119119
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect

receiver/mysqlreceiver/scraper.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"go.opentelemetry.io/collector/pdata/pmetric"
2121
"go.opentelemetry.io/collector/receiver"
2222
"go.opentelemetry.io/collector/scraper/scrapererror"
23+
"go.opentelemetry.io/otel/propagation"
24+
"go.opentelemetry.io/otel/trace"
2325
"go.uber.org/zap"
2426

2527
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/priorityqueue"
@@ -132,7 +134,7 @@ func (m *mySQLScraper) scrape(context.Context) (pmetric.Metrics, error) {
132134
return m.mb.Emit(), errs.Combine()
133135
}
134136

135-
func (m *mySQLScraper) scrapeTopQueryFunc(ctx context.Context) (plog.Logs, error) {
137+
func (m *mySQLScraper) scrapeTopQueryFunc(_ context.Context) (plog.Logs, error) {
136138
if m.sqlclient == nil {
137139
return plog.NewLogs(), errors.New("failed to connect to MySQL client")
138140
}
@@ -144,7 +146,7 @@ func (m *mySQLScraper) scrapeTopQueryFunc(ctx context.Context) (plog.Logs, error
144146
if m.lastExecutionTimestamp.Add(m.config.TopQueryCollection.CollectionInterval).After(now.AsTime()) {
145147
m.logger.Debug("Skipping top queries scrape, not enough time has passed since last execution")
146148
} else {
147-
m.scrapeTopQueries(ctx, now, errs)
149+
m.scrapeTopQueries(now, errs)
148150
}
149151
rb := m.lb.NewResourceBuilder()
150152
rb.SetMysqlInstanceEndpoint(m.config.Endpoint)
@@ -651,7 +653,7 @@ func (m *mySQLScraper) scrapeReplicaStatusStats(now pcommon.Timestamp) {
651653
}
652654
}
653655

654-
func (m *mySQLScraper) scrapeTopQueries(ctx context.Context, now pcommon.Timestamp, errs *scrapererror.ScrapeErrors) {
656+
func (m *mySQLScraper) scrapeTopQueries(now pcommon.Timestamp, errs *scrapererror.ScrapeErrors) {
655657
queries, err := m.sqlclient.getTopQueries(m.config.TopQueryCollection.MaxQuerySampleCount, m.config.TopQueryCollection.LookbackTime)
656658
if err != nil {
657659
m.logger.Error("Failed to fetch top queries", zap.Error(err))
@@ -713,7 +715,7 @@ func (m *mySQLScraper) scrapeTopQueries(ctx context.Context, now pcommon.Timesta
713715
}
714716

715717
m.lb.RecordDbServerTopQueryEvent(
716-
ctx,
718+
context.Background(),
717719
now,
718720
metadata.AttributeDbSystemNameMysql,
719721
obfuscatedQuery,
@@ -726,7 +728,7 @@ func (m *mySQLScraper) scrapeTopQueries(ctx context.Context, now pcommon.Timesta
726728
}
727729
}
728730

729-
func (m *mySQLScraper) scrapeQuerySamples(ctx context.Context, now pcommon.Timestamp, errs *scrapererror.ScrapeErrors) {
731+
func (m *mySQLScraper) scrapeQuerySamples(_ context.Context, now pcommon.Timestamp, errs *scrapererror.ScrapeErrors) {
730732
samples, err := m.sqlclient.getQuerySamples(m.config.QuerySampleCollection.MaxRowsPerQuery)
731733
if err != nil {
732734
m.logger.Error("Failed to fetch query samples", zap.Error(err))
@@ -754,13 +756,27 @@ func (m *mySQLScraper) scrapeQuerySamples(ctx context.Context, now pcommon.Times
754756
}
755757
}
756758

757-
obfuscatedQuery, err := m.obfuscator.obfuscateSQLString(sample.sqlText)
758-
if err != nil {
759-
m.logger.Error("Failed to obfuscate query", zap.Error(err))
759+
obfuscatedQuery, obfErr := m.obfuscator.obfuscateSQLString(sample.sqlText)
760+
if obfErr != nil {
761+
m.logger.Error("Failed to obfuscate query", zap.Error(obfErr))
762+
}
763+
764+
// Use context.Background() as the default (not the scraper ctx) so that log
765+
// records carry empty trace/span IDs when no application traceparent is present.
766+
// This prevents the collector's own internal scrape span from being stamped onto
767+
// query-sample records. If the sample carries a W3C traceparent, extract the
768+
// application's trace context from it.
769+
recordCtx := context.Background()
770+
if sample.traceparent != "" {
771+
var tpErr error
772+
recordCtx, tpErr = contextWithTraceparent(sample.traceparent)
773+
if tpErr != nil {
774+
m.logger.Warn("Invalid traceparent; omitting trace context", zap.String("presented-traceparent", sample.traceparent), zap.String("db.query.digest", sample.digest), zap.Error(tpErr))
775+
}
760776
}
761777

762778
m.lb.RecordDbServerQuerySampleEvent(
763-
ctx,
779+
recordCtx,
764780
now,
765781
metadata.AttributeDbSystemNameMysql,
766782
sample.threadID,
@@ -784,6 +800,20 @@ func (m *mySQLScraper) scrapeQuerySamples(ctx context.Context, now pcommon.Times
784800
}
785801
}
786802

803+
// contextWithTraceparent extracts a W3C TraceContext traceparent from the given
804+
// string and returns a new context.Background()-based context carrying the
805+
// resulting span context. On failure (invalid or absent traceparent), returns
806+
// an undecorated context.Background() and a non-nil error.
807+
func contextWithTraceparent(traceparent string) (context.Context, error) {
808+
newCtx := propagation.TraceContext{}.Extract(context.Background(), propagation.MapCarrier{
809+
"traceparent": traceparent,
810+
})
811+
if trace.SpanContextFromContext(newCtx).IsValid() {
812+
return newCtx, nil
813+
}
814+
return context.Background(), errors.New("no valid span context extracted from traceparent")
815+
}
816+
787817
func addPartialIfError(errors *scrapererror.ScrapeErrors, err error) {
788818
if err != nil {
789819
errors.AddPartial(1, err)

0 commit comments

Comments
 (0)