Test logs emitted by your code
Use LogsTestkit to run a logging bridge against an in-memory logs backend and assert the exported log records.
1. Add the testkit dependency
Add the testkit to the test scope in build.sbt:
libraryDependencies +=
"org.typelevel" %% "otel4s-oteljava-testkit" % "1.1.0" % Test
Add the test dependency to the test source file:
//> using test.dep "org.typelevel::otel4s-oteljava-testkit:1.1.0"
2. Allocate the logger once
Create one logger for the bridge's instrumentation scope and pass it directly to the bridge that emits OpenTelemetry log records.
import cats.effect.IO
import org.typelevel.otel4s.{AnyValue, Attribute}
import org.typelevel.otel4s.logs.{Logger, Severity}
final class ServiceLogBridge(logger: Logger[IO, _]) {
def requestFailed: IO[Unit] =
logger.logRecordBuilder
.withSeverity(Severity.error)
.withSeverityText("ERROR")
.withBody(AnyValue.string("request failed"))
.addAttributes(
Attribute("http.route", "/users"),
Attribute("error.type", "timeout")
)
.emit
}
Logger is intended for adapters that bridge an existing logging framework into OpenTelemetry. It does not replace
the logging API used by application code.
3. Define an assertion helper
The expectation API returns structured mismatches. Convert them into the failure type used by your test framework.
import io.opentelemetry.sdk.logs.data.LogRecordData
import org.typelevel.otel4s.oteljava.testkit.logs.{LogRecordExpectation, LogRecordExpectations}
def assertLogs(records: List[LogRecordData], expected: LogRecordExpectation*): IO[Unit] =
LogRecordExpectations.checkAll(records, expected: _*) match {
case Right(_) =>
IO.unit
case Left(mismatches) =>
IO.raiseError(new AssertionError(LogRecordExpectations.format(mismatches)))
}
This helper uses IO.raiseError so it remains independent of a specific test framework. Replace it with the framework's
failure mechanism when appropriate.
4. Run the bridge with LogsTestkit
Create the testkit as a Resource, get a logger from its loggerProvider, and pass the logger directly to the bridge.
Collect the finished log records before the resource closes.
import org.typelevel.otel4s.oteljava.testkit.logs.LogsTestkit
def test: IO[Unit] =
LogsTestkit.inMemory[IO]().use { testkit =>
for {
logger <- testkit.loggerProvider.logger("service").withVersion("1.0.0").get
bridge = new ServiceLogBridge(logger)
_ <- bridge.requestFailed
records <- testkit.finishedLogs
_ <- assertLogs(
records,
LogRecordExpectation
.message("request failed")
.severity(Severity.error)
.severityText("ERROR")
.attributesSubset(
Attribute("http.route", "/users"),
Attribute("error.type", "timeout")
)
)
} yield ()
}
LogRecordExpectation ignores unspecified fields, such as timestamps, trace correlation, and resource attributes.
What's next
For structured bodies, trace correlation, scope and resource matching, timestamps, and custom predicates, see Logs testkit reference.