Skip to content
Last9
Book demo

Scala

Use OpenTelemetry to instrument your Scala application and send telemetry data to Last9

Use OpenTelemetry to instrument your Scala application and send traces, metrics, and logs to Last9. Scala runs on the JVM, so the OpenTelemetry Java agent works out of the box — no Scala-specific library required.

For a complete working example, see the scala/akka-http example in the Last9 OTel examples repository.

Prerequisites

  • Scala 2.13 or 3.x
  • Java 11 or later
  • sbt (or Maven/Gradle)
  • Last9 account with OTLP endpoint configured

Step 1: Add OpenTelemetry API dependency

Add only the OTel API to your build — the SDK is provided by the Java agent at runtime.

// build.sbt
libraryDependencies ++= Seq(
"io.opentelemetry" % "opentelemetry-api" % "1.44.0",
// Logback appender — ships log records to the collector via OTLP
"io.opentelemetry.instrumentation" % "opentelemetry-logback-appender-1.0" % "2.10.0-alpha" % Runtime,
"ch.qos.logback" % "logback-classic" % "1.5.12",
)

If building a fat JAR with sbt-assembly, add the merge strategy to avoid losing OTel SPI registrations:

assembly / assemblyMergeStrategy := {
case PathList("META-INF", "services", _*) => MergeStrategy.concat
case PathList("META-INF", _*) => MergeStrategy.discard
case PathList("reference.conf") => MergeStrategy.concat
case _ => MergeStrategy.first
}

Step 2: Download the Java agent

curl -L https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.10.0/opentelemetry-javaagent.jar \
-o opentelemetry-javaagent.jar

Step 3: Configure log correlation (optional)

Add the OpenTelemetryAppender to logback.xml to ship log records via OTLP and correlate them with traces:

<!-- src/main/resources/logback.xml -->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} trace_id=%X{trace_id} span_id=%X{span_id} - %msg%n</pattern>
</encoder>
</appender>
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<captureExperimentalAttributes>true</captureExperimentalAttributes>
<captureMdcAttributes>*</captureMdcAttributes>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="OTEL"/>
</root>
</configuration>

Step 4: Add manual spans (optional)

For business logic not covered by auto-instrumentation, create spans using the OTel API:

import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.api.trace.{SpanKind, StatusCode}
val tracer = GlobalOpenTelemetry.getTracer("my-service", "1.0.0")
// Wrap a block in a span. makeCurrent() ensures child spans (JDBC, Kafka, etc.)
// created inside the block are linked as children of this span.
val span = tracer.spanBuilder("process-order").setSpanKind(SpanKind.INTERNAL).startSpan()
val scope = span.makeCurrent()
try {
span.setAttribute("order.id", orderId)
// ... your code ...
} catch {
case e: Exception =>
span.recordException(e)
span.setStatus(StatusCode.ERROR, e.getMessage)
throw e
} finally {
scope.close()
span.end()
}

Step 5: Set environment variables

export OTEL_SERVICE_NAME="my-scala-service"
export OTEL_EXPORTER_OTLP_ENDPOINT="$last9_otlp_endpoint"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=$last9_otlp_auth_header"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_TRACES_SAMPLER="parentbased_always_on"

Find your OTLP endpoint and auth header in the Last9 console under Integrations → OpenTelemetry.

Step 6: Start the application with the agent

java -javaagent:opentelemetry-javaagent.jar -jar myapp.jar

Or set JAVA_TOOL_OPTIONS so the agent is added automatically by your runtime/container:

export JAVA_TOOL_OPTIONS="-javaagent:/path/to/opentelemetry-javaagent.jar"

What gets instrumented automatically

LibrarySignal
Akka HTTP / Pekko HTTP (server + client)Traces
JDBC (PostgreSQL, MySQL, etc.) + HikariCPTraces
Redis (Lettuce, Jedis)Traces
Kafka producer / consumerTraces + W3C header propagation
gRPC (ScalaPB, Armeria)Traces
JVM (GC, heap, threads, CPU)Metrics
SLF4J / LogbackLogs (via OpenTelemetryAppender)

Cloud resource detection

# AWS (EC2, ECS, EKS, Lambda)
export OTEL_RESOURCE_PROVIDERS_AWS_ENABLED=true
# GCP (GCE, GKE, Cloud Run, App Engine)
export OTEL_RESOURCE_PROVIDERS_GCP_ENABLED=true

Sampling for high-traffic services

export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of new traces

Kubernetes: OTel Operator

On Kubernetes, the OpenTelemetry Operator can inject the Java agent automatically — no -javaagent flag in your Dockerfile required.

Add the annotation to your Deployment pod template:

annotations:
instrumentation.opentelemetry.io/inject-java: "true"

And create an Instrumentation CRD pointing at your Last9 collector endpoint. The operator sets all OTEL_* environment variables on the pod before your container starts.

See the Akka HTTP guide for a full example.

Verify the integration

After starting your application and sending a few requests, you should see:

  1. Traces in Last9 with spans for HTTP requests, DB queries, and Kafka messages
  2. Logs with trace_id and span_id fields linking to traces
  3. JVM metrics (memory, GC, CPU) under your service name

For troubleshooting, set OTEL_LOG_LEVEL=debug to see SDK initialization details.