Skip to content

feat: OpenTelemetry tracing support#45

Open
MrMarvin wants to merge 4 commits intojensteichert:mainfrom
MrMarvin:mf/open_telemetry_tracing
Open

feat: OpenTelemetry tracing support#45
MrMarvin wants to merge 4 commits intojensteichert:mainfrom
MrMarvin:mf/open_telemetry_tracing

Conversation

@MrMarvin
Copy link

@MrMarvin MrMarvin commented Jan 7, 2026

Motivation and context

More and more of our services are otel-tracing enabled. Currently colt does not allow to provide context objects to DB queries, nor does it instrument the underlying MongoDB driver.

This PR

  • Adds go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo and uses its otelmongo.NewMonitor().
  • Adds two new WithContext() functions, that can be used by clients to provide custom context. However, calling those is optional, making this change backwards compatible.
  • Ensures that all default context (created internally if none given by caller) is properly cancelled. It does not attempt to cancel externally given context if it comes with a deadline set.
  • Additional example with full tracing enabled.
  • Updates Go to current 1.25.5 and includes some dependency bumps (primarily mongo-driver to v1.17.6).

Example

Output of running cmd/tracing/example.go into a local Jaeger collector:
image

Exposes two new `WithContext()` functions that can be used
to provide trancing context.
Also pulls in the otelmongo package.
@MrMarvin
Copy link
Author

@jensteichert If you'd rather prefer the more standard approach for Go libraries of expecting ctx in every function call, we could do that as well. However this comes with the downside of breaking the convenience of users not having to "worry about" context and deadlines this project employed until now.

@YannickAlex07
Copy link

YannickAlex07 commented Mar 7, 2026

@MrMarvin A more common way for adding context while keeping backwards compatibility would be to provide a second set of methods that accept the context and then call them internally from the old API i.e. the Insert function would look like this:

func (repo *Collection[T]) Insert(model T) (T, error) {
        return repo.InsertContext(defaultContext(), T)
}

func (repo *Collection[T]) InsertContext(ctx context.Context, model T) (T, error) {
        if model.GetID() == "" {
		model.SetID(model.NewID())
	}

	if hook, ok := any(model).(BeforeInsertHook); ok {
		if err := hook.BeforeInsert(); err != nil {
			return model, err
		}
	}

	res, err := repo.collection.InsertOne(ctx, model)
	if err != nil && res != nil {
		model.SetID(res.InsertedID.(string))
	}

	return model, err
}

This is also the pattern used by standard library packages like database/sql (https://pkg.go.dev/database/sql#DB.Exec and https://pkg.go.dev/database/sql#DB.ExecContext). Each caller can easily decide if they want to use the version with or without context.

Doing it like this also avoid having the same context on a collection for all operations - ofc depends on the exact usage but if the same collection would be used by something like an API, each operation should use the requests context and I am not sure if it would be so desirable to always create a "copy" of the object using the WithContext for this sake.

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.

2 participants