refactor(ddtrace/tracer): internalize {Get,Set}GlobalTracer API#3482
Conversation
|
|
||
| // Stop deactivates the mock tracer and sets the active tracer to a no-op. | ||
| func (t *mocktracer) Stop() { | ||
| tracer.StopTestTracer() |
There was a problem hiding this comment.
tracer.StopTestTracer functionality is covered by tracer.Stop.
There was a problem hiding this comment.
This change uncovered some tests that were doing weird stuff with the mocktracer, like stacking multiple instances through mocktracer.Start and defer mt.Stop.
| tracer.SetGlobalTracer(t) | ||
| return t | ||
| var t tracer.Tracer = newMockTracer() | ||
| internal.SetGlobalTracer(t) |
There was a problem hiding this comment.
internal.SetGlobalTracer swaps and stops the previously set tracer, so it is functionally equal.
| } | ||
|
|
||
| func getGlobalTracer() tracer.Tracer { | ||
| return internal.GetGlobalTracer[tracer.Tracer]() |
There was a problem hiding this comment.
API users may find this a bit uncomfortable but it's better to have a bit of duplication than perfect ergonomics but leaky abstractions.
Datadog ReportBranch report: ✅ 0 Failed, 4525 Passed, 65 Skipped, 3m 41.05s Total Time |
BenchmarksBenchmark execution time: 2025-05-07 10:54:21 Comparing candidate commit 02063e7 in PR branch Found 0 performance improvements and 1 performance regressions! Performance is the same for 55 metrics, 0 unstable metrics. scenario:BenchmarkSetTagMetric-24
|
…e globaltracer with generics
…ce to globaltracer.go
…ings to reset instead of stop
…csSettings to reset instead of stop
…o reset instead of stop
f40bde6 to
5772968
Compare
|
|
||
| // StoreGlobalTracer stores a tracer in the global tracer. | ||
| // It is the responsibility of the caller to ensure that the value is `tracer.Tracer`. | ||
| func StoreGlobalTracer[T tracerLike](t T) { |
There was a problem hiding this comment.
Do we actually need an additional StoreGlobalTracer method? Can't we just use the SetGlobalTracer? What's the additional benefit?
There was a problem hiding this comment.
This replaces the original Store operation at the init function. I agree it could be replaced by the SetGlobalTracer, but I'm not sure why it was decided to go this way.
| // It is the responsibility of the caller to ensure that the value is `tracer.Tracer`. | ||
| func SetGlobalTracer[T tracerLike](t T) { | ||
| old := *globalTracer.Swap(&t).(*T) | ||
| old.Stop() |
There was a problem hiding this comment.
We should have a nil-check here, in case the old value was nil.
…es; drop internal.StoreGlobalTracer
5b79c46 to
9f8e598
Compare
| // It is the responsability of the caller to ensure that calling code uses `tracer.Tracer` | ||
| // as generic type. | ||
| func GetGlobalTracer[T tracerLike]() T { | ||
| return *globalTracer.Load().(*T) |
There was a problem hiding this comment.
Should we also be checking for a nil Load() return here?
There was a problem hiding this comment.
In the context of our tracer, this shouldn't happen. The init function ensures globalTracer is set. If somebody misuses the API and sets a nil value, which is possible, the check would make sense.
The problem with the check in this PR is that... What should we return? Returning nil would lead to panics when the tracer API is used. Another way would be to set a NoopTracer, but that would create an import cycle between internal and tracer.
I think the best way would be to panic if SetGlobalTracer is called with a nil value. A nil tracer could be considered a bad state that is not recoverable. WDYT? cc @kakkoyun
There was a problem hiding this comment.
@darccio I have no strong opinion about panicking in the SetGlobalTracer when a nil value is passed but we could also do nothing when called with a nil instead of panicking.
There was a problem hiding this comment.
@genesor My fear by doing nothing is that it might go unnoticed. Another way would be to log that a nil tracer has been passed and we avoided setting it. WDYT?
There was a problem hiding this comment.
@darccio I'd go for the log, that's less "radical" than a panic IMO
There was a problem hiding this comment.
@genesor Now I realized that this being an internal package, it doesn't have access to our logger. If we use log as provided by the stdlib, it would be a deviation from the rest of the tracer.
I'm reconsidering going the panic route.
There was a problem hiding this comment.
IMO an early panic is acceptable in this path. If user miss uses the API, we can fail fast.
But using the NoopTracer and having a warning would be ideal. How was the previous behaviour?
There was a problem hiding this comment.
The explanation: in v1 it was also possible to set a nil tracer. It causes the code to panic once a span is started. I feel it's better to fail on initialization than during the execution.
|
Failing smoke tests fixed here: #3504 |
What does this PR do?
Brings back
{Get,Set}GlobalTracerby using generics and leverage the compiler so the API remains functionally identical and storestracer.Tracervalues without creating an import cycle betweenddtrace/tracerandddtrace/internal.In
v1.xwe don't have this issue, neither the API exported, because both packages use interfaces defined inddtrace. As part of ourv2work, we removed some interfaces and migrated others in #2408.Motivation
Reduce API surface to the essentially required.
Reviewer's Checklist
golangci-lint runlocally.Unsure? Have a question? Request a review!