Skip to content

Add tracing support for PreparedStatementIO#2361

Merged
jatcwang merged 14 commits intotypelevel:mainfrom
iRevive:tracing-sketch
Jan 31, 2026
Merged

Add tracing support for PreparedStatementIO#2361
jatcwang merged 14 commits intotypelevel:mainfrom
iRevive:tracing-sketch

Conversation

@iRevive
Copy link
Copy Markdown
Contributor

@iRevive iRevive commented Jan 16, 2026

Hey everyone. I'm trying to implement a flexible (well, to some degree) instrumentation logic for doobie, to make it compatible with otel4s and be mostly compliant with OpenTelemetry DB Semantic Conventions.

I'm aware of otel4s-doobie and natchez-doobie. Yet, I wonder whether we can slightly extend the ADT to make it easier to write instrumentation.

OTel semantic conventions for DB operations require certain attributes. I expect to make them opt-in, i.e., users will need to explicitly enable features, such as query or query params capture.

We can break down mandatory (and semi-mandatory) attributes into a few categories.

Transactor initialization

  • db.system.name - mysql, postgresql, etc
  • db.namespace- the name of the database
  • server.address - the server address
  • server.port - the server port (can be empty if we use a default)

Prepared statement execution

  • db.operation.name- it can be SELECT / UPDATE / DELETE, but I would choose something easier, such as prepared statement op, e.g. executeBatch, executeUpdate, executeQuery
  • db.query.text - query text
  • db.query.parameter.{idx} - query params
  • error.type - FQN of the error, e.g. java.sql.SQLException
  • db.response.status_code - the db error code, java.sql.SQLException#getErrorCode

Query metadata

  • db.query.summary - it can be either auto-derived or provided by a user

This one is tricky. I was thinking of using a label to attach structured information to the query.

sql"delete from users where id = $id".queryWithContext(Map("db.query.summary" -> "delete user permanently"))

Where queryWithAttributes is an extension API that will serialize Attributes as JSON, and then we can deserialize a label into the map of key-values inside the trace override.

With this approach, users can attach any identifiable information to the span: source info (file name, enclosing, lines), MDC, etc.

The implementation details

The instrumentation can be described as a natural transformation F ~> F. It is suitable for both tracing and metrics.
Yet, we need some additional meta information to reason about the operation.

In this sketch, I introduce a new trait, such as TracingEvent. Each ADT (currently, there is only one) describes a specific instrumentation place.

Then, within the trace, the implementor can decide which calls to instrument and choose an appropriate naming strategy.

Usage example

Create a transactor:

private def createTransactor(using TracerProvider[IO]): IO[Transactor[IO]] = {
  val tx = Transactor.fromDriverManager[IO](
    driver = "org.postgresql.Driver",
    url = "jdbc:postgresql://localhost:5432/warehouse",
    user = "user",
    password = "password",
    logHandler = None
  )
  TracedTransactor.create(tx, TracedInterceptor.Config.default, None)
}

Run queries:

val variable = "some cool text"
val conn =
  for {
    _ <- sql"select 1".query[Int].unique
    _ <- insert.updateWithLabel("update user info").run
  } yield ()

conn.transact(transactor).void >> sql"select $variable".query[Int].unique .transact(transactor).void

As you can see, all attributes are captured correctly:

The outcome image

Please let me know what do you think and how feasible this idea is.

Comment thread modules/core/src/main/scala/doobie/hi/connection.scala Outdated
Comment thread modules/core/src/main/scala/doobie/TracedTransactor.scala Outdated
Comment thread modules/core/src/main/scala/doobie/TracedTransactor.scala Outdated
Comment thread build.sbt
)
)

lazy val otel4s = project
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This module is used to test the tracing integration. Because otel4s is still pre-1.0, we should probably avoid publishing this module yet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I disabled publishing for this module.

Comment thread modules/free/src/main/scala/doobie/util/trace.scala Outdated
@iRevive iRevive marked this pull request as ready for review January 22, 2026 19:49
@iRevive iRevive changed the title Sketch tracing support for PreparedStatementIO Add tracing support for PreparedStatementIO Jan 22, 2026
@iRevive
Copy link
Copy Markdown
Contributor Author

iRevive commented Jan 22, 2026

@jatcwang could you please take a look?

@jatcwang jatcwang merged commit c2f9fa4 into typelevel:main Jan 31, 2026
@jatcwang
Copy link
Copy Markdown
Collaborator

Thanks for the lovely PR :)

@iRevive iRevive deleted the tracing-sketch branch January 31, 2026 11:00
@iRevive
Copy link
Copy Markdown
Contributor Author

iRevive commented Jan 31, 2026

Perfect, thank you!

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