Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@ lazy val docs = (project in file("docs"))
"SCALA_VERSION" -> scalaVersion.value,
"MYSQL_VERSION" -> "9.6.0"
),
laikaTheme := LaikaSettings.helium.value,
laikaTheme := LaikaSettings.helium.value,
laikaConfig := LaikaConfig.defaults.withRawContent,
// Modify tlSite task to run the LLM docs script after the site is generated
tlSite := {
tlSite.value
Expand Down
235 changes: 124 additions & 111 deletions docs/src/main/mdoc/en/examples/Otel.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,33 @@ laika.metadata.language = en

# Observability with OpenTelemetry

This page explains how to achieve observability through database query tracing, metrics collection, and logging by integrating ldbc with OpenTelemetry.
This page explains how to achieve observability through database query tracing and metrics collection by integrating ldbc with OpenTelemetry.

## What is OpenTelemetry?

[OpenTelemetry](https://opentelemetry.io/) is an open-source framework for implementing application observability. It provides features such as distributed tracing, metrics collection, and logging, enabling an integrated approach to monitoring and analyzing application performance and behavior.

By integrating with OpenTelemetry, ldbc can benefit from:

- Tracking database query execution time and processing
- Measuring query performance
- Error detection and diagnosis
- System-wide visualization and monitoring
For detailed API references (span attribute list, TelemetryConfig, metrics specification, etc.), see the [Telemetry Reference](../reference/Telemetry.md).

## Required Dependencies

To use OpenTelemetry with ldbc, you need to add the following dependencies to your project:
Add the following dependencies to your project to use OpenTelemetry with ldbc:

```scala
libraryDependencies ++= Seq(
// ldbc connector (includes otel4s-core)
"@ORGANIZATION@" %% "ldbc-connector" % "@VERSION@",

// otel4s library (Scala wrapper for OpenTelemetry)
"org.typelevel" %% "otel4s-oteljava" % "0.11.2",
"org.typelevel" %% "otel4s-oteljava" % "0.15.1",

// OpenTelemetry exporter (for data transmission)
"io.opentelemetry" % "opentelemetry-exporter-otlp" % "1.48.0" % Runtime,
// Auto-configuration functionality (configuration via environment variables and system properties)
"io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "1.48.0" % Runtime,
"io.opentelemetry" % "opentelemetry-exporter-otlp" % "1.59.0" % Runtime,

// Auto-configuration (configuration via environment variables and system properties)
"io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "1.59.0" % Runtime,
)
```

## OpenTelemetry Configuration in ldbc

ldbc provides a `setTracer` method in ConnectionProvider where you can configure the OpenTelemetry Tracer.
## Basic Setup

Basic configuration steps are as follows:

1. Initialize OpenTelemetry
2. Obtain a TracerProvider
3. Configure it in the ConnectionProvider

Here's a basic configuration example:
Configure both `setTracer` and `setMeter` on `MySQLDataSource` to enable tracing and metrics collection.

```scala
import cats.effect.*
Expand All @@ -54,66 +40,99 @@ import org.typelevel.otel4s.oteljava.OtelJava
import ldbc.connector.*
import ldbc.dsl.*

// Service name (trace identifier)
val serviceName = "my-ldbc-app"

// Resource configuration
val resource: Resource[IO, Connector[IO]] =
for
// Create otel4s instance from GlobalOpenTelemetry
otel <- Resource
.eval(IO.delay(GlobalOpenTelemetry.get))
.evalMap(OtelJava.forAsync[IO])

// Get TracerProvider with service name
otel <- Resource
.eval(IO.delay(GlobalOpenTelemetry.get))
.evalMap(OtelJava.forAsync[IO])
tracer <- Resource.eval(otel.tracerProvider.get(serviceName))

// Configure database connection and set TracerProvider
meter <- Resource.eval(otel.meterProvider.get(serviceName))
datasource = MySQLDataSource
.build[IO]("localhost", 3306, "user")
.setPassword("password")
.setDatabase("database")
.setSSL(SSL.Trusted)
.setTracer(tracer) // Set OpenTelemetry Tracer
.build[IO]("localhost", 3306, "user")
.setPassword("password")
.setDatabase("mydb")
.setSSL(SSL.Trusted)
.setTracer(tracer)
.setMeter(meter)
yield Connector.fromDataSource(datasource)

// Execute query using the resource
val program = resource.use { connector =>
sql"SELECT * FROM users".query[String].to[List].readOnly(connector)
}
```

## Configuration via Environment Variables
You can also use tracing-only or metrics-only:

OpenTelemetry can be configured using system properties or environment variables. You can customize OpenTelemetry behavior by setting the following system properties when starting your application:
```scala
// Tracing only
val tracingOnly = MySQLDataSource.build[IO]("localhost", 3306, "user")
.setTracer(tracer)

```shell
java -Dotel.java.global-autoconfigure.enabled=true \
-Dotel.service.name=ldbc-app \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=none \
-Dotel.exporter.otlp.endpoint=http://localhost:4317 \
-jar your-application.jar
// Metrics only
val metricsOnly = MySQLDataSource.build[IO]("localhost", 3306, "user")
.setMeter(meter)
```

For SBT execution, you can configure it as follows:
## Connection Pool with Metrics

When using connection pooling, pass `meter` to the `pooling` method to automatically collect pool metrics (connection wait time, use time, timeout count, etc.).

```scala
javaOptions ++= Seq(
"-Dotel.java.global-autoconfigure.enabled=true",
"-Dotel.service.name=ldbc-app",
"-Dotel.traces.exporter=otlp",
"-Dotel.metrics.exporter=none"
import ldbc.connector.*

val pool = MySQLDataSource.pooling[IO](
config = MySQLConfig.default
.setHost("127.0.0.1")
.setPort(3306)
.setUser("user")
.setPassword("password")
.setDatabase("mydb"),
meter = Some(meter),
tracer = Some(tracer)
)
```

## Practical Example: ldbc Application with OpenTelemetry
For the full list of pool metrics, see [Telemetry Reference - Connection Pool Metrics](../reference/Telemetry.md).

Here's a complete example of setting up an observability environment using Docker Compose with Jaeger and Prometheus, and sending trace data from an ldbc application.
## Customization with TelemetryConfig

### Project Structure
Use `TelemetryConfig` to control telemetry behavior.

```scala
import ldbc.connector.telemetry.TelemetryConfig

Assume the following project structure:
// Default (spec-compliant, all features enabled)
val config = TelemetryConfig.default

// Use fixed span names (no query text parsing)
val fixedSpanName = TelemetryConfig.withoutQueryTextExtraction

// Custom configuration
val custom = TelemetryConfig.default
.withoutQueryTextExtraction // Use fixed span names like "Execute Statement"
.withoutSanitization // Disable query sanitization (caution: may expose sensitive data)
.withoutInClauseCollapsing // Do not collapse IN (?, ?, ?) to IN (?)
```

Apply `TelemetryConfig` to the data source:

```scala
val datasource = MySQLDataSource
.build[IO]("localhost", 3306, "user")
.setPassword("password")
.setDatabase("mydb")
.setTracer(tracer)
.setMeter(meter)
.setTelemetryConfig(TelemetryConfig.withoutQueryTextExtraction)
```

## Practical Example: Jaeger + Prometheus + Grafana

A complete example of building an observability environment with Docker Compose (Jaeger, Prometheus, Grafana) and sending traces and metrics from an ldbc application.

### Project Structure

```
otel-example/
Expand All @@ -122,7 +141,7 @@ otel-example/
│ └── scala/
│ └── Main.scala
├── database/
│ └── xxx.sql
│ └── init.sql
├── dependencies/
│ ├── jaeger/
│ │ └── jaeger-ui.json
Expand All @@ -135,8 +154,6 @@ otel-example/

### Main.scala

Here's the main application code:

```scala
import cats.effect.*
import io.opentelemetry.api.GlobalOpenTelemetry
Expand All @@ -148,23 +165,24 @@ object Main extends IOApp.Simple:

private val serviceName = "ldbc-otel-example"

private val base = MySQLDataSource
.build[IO]("127.0.0.1", 13307, "ldbc")
.setPassword("password")
.setDatabase("world")
.setSSL(SSL.Trusted)

private def setupTracing: Resource[IO, Connector[IO]] =
private def setupObservability: Resource[IO, Connector[IO]] =
for
otel <- Resource
.eval(IO.delay(GlobalOpenTelemetry.get))
.evalMap(OtelJava.forAsync[IO])
otel <- Resource
.eval(IO.delay(GlobalOpenTelemetry.get))
.evalMap(OtelJava.forAsync[IO])
tracer <- Resource.eval(otel.tracerProvider.get(serviceName))
datasource <- Resource.eval(IO.delay(base.setTracer(tracer)))
meter <- Resource.eval(otel.meterProvider.get(serviceName))
datasource = MySQLDataSource
.build[IO]("127.0.0.1", 13306, "ldbc")
.setPassword("password")
.setDatabase("world")
.setSSL(SSL.Trusted)
.setTracer(tracer)
.setMeter(meter)
yield Connector.fromDataSource(datasource)

override def run: IO[Unit] =
setupTracing.use { connector =>
setupObservability.use { connector =>
sql"SELECT name FROM city".query[String].to[List].readOnly(connector).flatMap { cities =>
IO.println(cities)
}
Expand All @@ -173,20 +191,17 @@ object Main extends IOApp.Simple:

### docker-compose.yaml

Use the following Docker Compose configuration for the observability environment:

```yaml
services:
database:
image: mysql:8.0.41
image: mysql:9.6.0
container_name: ldbc-otel-example
ports:
- 13307:3306
- 13306:3306
networks:
- static-network
volumes:
- ./database:/docker-entrypoint-initdb.d
- ./database/my.cnf:/etc/database/conf.d/my.cn
environment:
MYSQL_USER: 'ldbc'
MYSQL_PASSWORD: 'password'
Expand Down Expand Up @@ -218,8 +233,8 @@ services:
ports:
- "16685:16685" # GRPC
- "16686:16686" # UI
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP http receiver
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
networks:
- static-network

Expand All @@ -243,52 +258,50 @@ networks:

### Execution Steps

1. First, start the observability environment with Docker Compose:
1. Start the observability environment with Docker Compose:

```bash
cd otel-example
docker-compose up -d
```

2. Then, run the application with the following Java options:
2. Run the application with the following Java options:

```bash
sbt -Dotel.java.global-autoconfigure.enabled=true \
-Dotel.service.name=ldbc-otel-example \
-Dotel.metrics.exporter=none \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://localhost:4317 \
run
```

For `build.sbt` configuration:

```scala
javaOptions ++= Seq(
"-Dotel.java.global-autoconfigure.enabled=true",
"-Dotel.service.name=ldbc-otel-example",
"-Dotel.traces.exporter=otlp",
"-Dotel.metrics.exporter=otlp",
"-Dotel.exporter.otlp.endpoint=http://localhost:4317"
)
```

3. After running the application, access the Jaeger UI ([http://localhost:16686](http://localhost:16686)) to view traces:

![Jaeger UI](../../../img/jaeger_ui.png)

You can also check traces in the Grafana UI ([http://localhost:3000](http://localhost:3000)):
You can also check traces and metrics in the Grafana UI ([http://localhost:3000](http://localhost:3000)):

![Grafana UI](../../../img/grafana_ui.png)

## Trace Details

ldbc's OpenTelemetry integration records the following information in traces:

- **Query execution time**: Time taken for database queries
- **SQL statements**: Content of executed SQL queries
- **Query parameters**: Parameter values passed to queries (safely recorded)
- **Connection information**: Server and database name connection details
- **Error information**: Detailed information when errors occur

## Trace Customization Options

ldbc's OpenTelemetry integration allows for additional customization such as:

- **Sampling rate configuration**: Trace only a portion of queries instead of all
- **Attribute filtering**: Exclude sensitive information from traces
- **Custom trace exporters**: Send to tracing systems other than Jaeger

These settings can be configured through OpenTelemetry configuration files or system properties.

## Summary

Combining ldbc with OpenTelemetry enables detailed visualization and monitoring of database operations. Using the methods introduced in this guide makes it easier to identify application performance issues, discover bottlenecks, and perform troubleshooting.
Combining ldbc with OpenTelemetry enables detailed visualization and monitoring of database operations.

- **Tracing**: Record query execution time, error details, and span attributes via `setTracer`
- **Metrics**: Record query duration, returned row counts, and pool state via `setMeter`
- **TelemetryConfig**: Customize query sanitization and span name generation behavior

In production environments, you may need to adjust settings according to security requirements and environment specifics. We recommend referring to the official OpenTelemetry documentation to find the optimal configuration.
For detailed span attributes and metrics specifications, see the [Telemetry Reference](../reference/Telemetry.md).
Loading
Loading