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.sbtlibraryDependencies ++= 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}<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-api</artifactId> <version>1.44.0</version></dependency><dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-logback-appender-1.0</artifactId> <version>2.10.0-alpha</version> <scope>runtime</scope></dependency>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.jarStep 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.GlobalOpenTelemetryimport 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.jarOr 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
| Library | Signal |
|---|---|
| Akka HTTP / Pekko HTTP (server + client) | Traces |
| JDBC (PostgreSQL, MySQL, etc.) + HikariCP | Traces |
| Redis (Lettuce, Jedis) | Traces |
| Kafka producer / consumer | Traces + W3C header propagation |
| gRPC (ScalaPB, Armeria) | Traces |
| JVM (GC, heap, threads, CPU) | Metrics |
| SLF4J / Logback | Logs (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=trueSampling for high-traffic services
export OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of new tracesKubernetes: 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:
- Traces in Last9 with spans for HTTP requests, DB queries, and Kafka messages
- Logs with
trace_idandspan_idfields linking to traces - JVM metrics (memory, GC, CPU) under your service name
For troubleshooting, set OTEL_LOG_LEVEL=debug to see SDK initialization details.