Skip to content

refactor(ddtrace/tracer): internalize {Get,Set}GlobalTracer API#3482

Merged
darccio merged 12 commits into
mainfrom
dario.castane/internalize-globaltracer-api
May 7, 2025
Merged

refactor(ddtrace/tracer): internalize {Get,Set}GlobalTracer API#3482
darccio merged 12 commits into
mainfrom
dario.castane/internalize-globaltracer-api

Conversation

@darccio

@darccio darccio commented May 2, 2025

Copy link
Copy Markdown
Member

What does this PR do?

Brings back {Get,Set}GlobalTracer by using generics and leverage the compiler so the API remains functionally identical and stores tracer.Tracer values without creating an import cycle between ddtrace/tracer and ddtrace/internal.

In v1.x we don't have this issue, neither the API exported, because both packages use interfaces defined in ddtrace. As part of our v2 work, we removed some interfaces and migrated others in #2408.

Motivation

Reduce API surface to the essentially required.

Reviewer's Checklist

  • Changed code has unit tests for its functionality at or near 100% coverage.
  • System-Tests covering this feature have been added and enabled with the va.b.c-dev version tag.
  • There is a benchmark for any new code, or changes to existing code.
  • If this interacts with the agent in a new way, a system test has been added.
  • New code is free of linting errors. You can check this by running golangci-lint run locally.
  • Add an appropriate team label so this PR gets put in the right place for the release notes.
  • Non-trivial go.mod changes, e.g. adding new modules, are reviewed by @DataDog/dd-trace-go-guild.

Unsure? Have a question? Request a review!

@darccio
darccio requested a review from a team as a code owner May 2, 2025 15:14

// Stop deactivates the mock tracer and sets the active tracer to a no-op.
func (t *mocktracer) Stop() {
tracer.StopTestTracer()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tracer.StopTestTracer functionality is covered by tracer.Stop.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal.SetGlobalTracer swaps and stops the previously set tracer, so it is functionally equal.

}

func getGlobalTracer() tracer.Tracer {
return internal.GetGlobalTracer[tracer.Tracer]()

@darccio darccio May 2, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented May 2, 2025

Copy link
Copy Markdown

Datadog Report

Branch report: dario.castane/internalize-globaltracer-api
Commit report: 7446f6a
Test service: dd-trace-go

✅ 0 Failed, 4525 Passed, 65 Skipped, 3m 41.05s Total Time

@darccio
darccio requested a review from a team as a code owner May 2, 2025 15:44
@pr-commenter

pr-commenter Bot commented May 2, 2025

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2025-05-07 10:54:21

Comparing candidate commit 02063e7 in PR branch dario.castane/internalize-globaltracer-api with baseline commit 332f786 in branch main.

Found 0 performance improvements and 1 performance regressions! Performance is the same for 55 metrics, 0 unstable metrics.

scenario:BenchmarkSetTagMetric-24

  • 🟥 execution_time [+2.621ns; +4.419ns] or [+2.233%; +3.766%]

@darccio
darccio force-pushed the dario.castane/internalize-globaltracer-api branch from f40bde6 to 5772968 Compare May 2, 2025 17:07
Comment thread ddtrace/internal/globaltracer.go Outdated

// 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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need an additional StoreGlobalTracer method? Can't we just use the SetGlobalTracer? What's the additional benefit?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread ddtrace/internal/globaltracer.go Outdated
// 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()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a nil-check here, in case the old value was nil.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9f8e598.

@darccio
darccio force-pushed the dario.castane/internalize-globaltracer-api branch from 5b79c46 to 9f8e598 Compare May 5, 2025 15:01
// 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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also be checking for a nil Load() return here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@darccio I'd go for the log, that's less "radical" than a panic IMO

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@darccio darccio May 7, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Panic route taken in 02063e7

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@darccio
darccio enabled auto-merge (squash) May 7, 2025 12:58
@darccio

darccio commented May 7, 2025

Copy link
Copy Markdown
Member Author

Failing smoke tests fixed here: #3504

@darccio
darccio merged commit 281ec7d into main May 7, 2025
@darccio
darccio deleted the dario.castane/internalize-globaltracer-api branch May 7, 2025 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants