Resource detectors can add environment-specific attributes to the telemetry resource. AWS detectors are implemented as a third-party library, and you need to enable them manually.
The detector relies on the AWS_REGION
, AWS_LAMBDA_FUNCTION_NAME
, and AWS_LAMBDA_FUNCTION_VERSION
environment variables
to configure the telemetry resource.
Either AWS_LAMBDA_FUNCTION_NAME
or AWS_LAMBDA_FUNCTION_VERSION
must be present.
import cats.effect.IO
import cats.effect.std.Env
import cats.effect.unsafe.implicits.global
import org.typelevel.otel4s.sdk.contrib.aws.resource._
import scala.collection.immutable
val envEntries = Map(
"AWS_REGION" -> "eu-west-1",
"AWS_LAMBDA_FUNCTION_NAME" -> "function",
"AWS_LAMBDA_FUNCTION_VERSION" -> "0.0.1"
)
implicit val env: Env[IO] =
new Env[IO] {
def get(name: String): IO[Option[String]] = IO.pure(envEntries.get(name))
def entries: IO[immutable.Iterable[(String, String)]] = IO.pure(envEntries)
}
println("Environment: ")
println("```")
envEntries.foreach { case (k, v) => println(s"${k.replace("_", "_")}=$v") }
println("```")
println("Detected resource: ")
println("```yaml")
AwsLambdaDetector[IO].detect.unsafeRunSync().foreach { resource =>
resource.attributes.toList.sortBy(_.key.name).foreach { attribute =>
println(attribute.key.name + ": " + attribute.value)
}
}
println("```")
The detector fetches instance metadata from the http://169.254.169.254
endpoint.
See AWS documentation for more
details.
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import io.circe.Json
import io.circe.syntax._
import org.http4s._
import org.http4s.circe.jsonEncoder
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.syntax.literals._
import org.typelevel.otel4s.sdk.contrib.aws.resource._
val hostname = "ip-10-0-0-1.eu-west-1.compute.internal"
val metadata = Json.obj(
"accountId" := "1234567890",
"architecture" := "x86_64",
"availabilityZone" := "eu-west-1a",
"imageId" := "ami-abc123de",
"instanceId" := "i-abc321de",
"instanceType" := "t3.small",
"privateIp" := "10.0.0.1",
"region" := "eu-west-1",
"version" := "2017-09-30"
)
val client = Client.fromHttpApp[IO](
HttpRoutes
.of[IO] {
case GET -> Root / "latest" / "meta-data" / "hostname" => Ok(hostname)
case GET -> Root / "latest" / "dynamic" / "instance-identity" / "document" => Ok(metadata)
case PUT -> Root / "latest" / "api" / "token" => Ok("token")
}
.orNotFound
)
println("The `http://169.254.169.254/latest/dynamic/instance-identity/document` response: ")
println("```json")
println(metadata)
println("```")
println("The `http://169.254.169.254/latest/meta-data/hostname` response:")
println("```")
println(hostname)
println("```")
println("Detected resource: ")
println("```yaml")
AwsEc2Detector[IO](uri"", client).detect.unsafeRunSync().foreach { resource =>
resource.attributes.toList.sortBy(_.key.name).foreach { attribute =>
println(attribute.key.name + ": " + attribute.value)
}
}
println("```")
The detector fetches ECS container and task metadata.
The base URI is obtained from ECS_CONTAINER_METADATA_URI_V4
or ECS_CONTAINER_METADATA_URI
env variable.
See AWS documentation for more details.
import cats.effect.IO
import cats.effect.std.Env
import cats.effect.unsafe.implicits.global
import io.circe.Json
import io.circe.syntax._
import org.http4s._
import org.http4s.circe.jsonEncoder
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.typelevel.otel4s.sdk.contrib.aws.resource._
import scala.collection.immutable
val envEntries = Map(
"ECS_CONTAINER_METADATA_URI_V4" -> "http://169.254.170.2/v4/5fb8fcdd-29f2-490f-8229-c1269d11a9d9"
)
implicit val env: Env[IO] = new Env[IO] {
def get(name: String): IO[Option[String]] = IO.pure(envEntries.get(name))
def entries: IO[immutable.Iterable[(String, String)]] = IO.pure(envEntries)
}
val accountId = "1234567890"
val region = "eu-west-1"
val taskId = "5e1b...86980"
val family = "service-production"
val cluster = "production"
val revision = "11"
val taskArn = s"arn:aws:ecs:$region:$accountId:task/$cluster/$taskId"
val container = Json.obj(
"DockerId" := "83b2af5973dc...ee1e1",
"Name" := "server",
"DockerName" := s"ecs-$family-$revision-server-e4e7efbceda7b7c68601",
"Image" := s"$accountId.dkr.ecr.$region.amazonaws.com/internal/repository:8abab2a5",
"ImageID" := "sha256:7382b7779e6038...11f2d7d522d",
"DesiredStatus" := "RUNNING",
"CreatedAt" := "2024-09-12T18:08:55.593944224Z",
"StartedAt" := "2024-09-12T18:08:56.524454503Z",
"Type" := "NORMAL",
"Health" := Json.obj("status" := "HEALTHY"),
"LogDriver" := "awslogs",
"LogOptions" := Json.obj(
"awslogs-group" := s"/ecs/$cluster/service",
"awslogs-region" := region,
"awslogs-stream" := s"ecs/server/$taskId"
),
"ContainerARN" := s"$taskArn/1a1c23fe-1718-4eed-9833-c3dc2dad712c"
)
val task = Json.obj(
"Cluster" := cluster,
"TaskARN" := taskArn,
"Family" := family,
"Revision" := revision,
"DesiredStatus" := "RUNNING",
"KnownStatus" := "RUNNING",
"PullStartedAt" := "2024-09-12T18:08:55.307387715Z",
"PullStoppedAt" := "2024-09-12T18:08:55.564707417Z",
"AvailabilityZone" := "eu-west-1a",
"LaunchType" := "EC2",
"VPCID" := "vpc-123",
"ServiceName" := "service"
)
val client = Client.fromHttpApp[IO](
HttpRoutes
.of[IO] {
case GET -> Root / "v4" / "5fb8fcdd-29f2-490f-8229-c1269d11a9d9" => Ok(container)
case GET -> Root / "v4" / "5fb8fcdd-29f2-490f-8229-c1269d11a9d9" / "task" => Ok(task)
}
.orNotFound
)
println("The `http://169.254.170.2/v4/5fb8fcdd-29f2-490f-8229-c1269d11a9d9` response: ")
println("```json")
println(container)
println("```")
println("The `http://169.254.170.2/v4/5fb8fcdd-29f2-490f-8229-c1269d11a9d9/task` response:")
println("```json")
println(task)
println("```")
println("Detected resource: ")
println("```yaml")
AwsEcsDetector[IO](client).detect.unsafeRunSync().foreach { resource =>
resource.attributes.toList.sortBy(_.key.name).foreach { attribute =>
println(attribute.key.name + ": " + attribute.value)
}
}
println("```")
The detector parses environment details from the /var/elasticbeanstalk/xray/environment.conf
file to configure the telemetry resource.
Expected configuration attributes:
deployment_id
version_label
environment_name
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import fs2.io.file.Files
import io.circe.Json
import io.circe.syntax._
import org.typelevel.otel4s.sdk.contrib.aws.resource._
val content = Json.obj(
"deployment_id" := 2,
"version_label" := "1.1",
"environment_name" := "production-eu-west"
).noSpaces
println("The content of the `/var/elasticbeanstalk/xray/environment.conf` file: ")
println("```json")
println(content)
println("```")
println("Detected resource: ")
println("```yaml")
val detected = Files[IO].tempFile.use { path =>
for {
_ <- fs2.Stream(content).through(fs2.text.utf8.encode).through(Files[IO].writeAll(path)).compile.drain
r <- AwsBeanstalkDetector[IO](path).detect
} yield r
}.unsafeRunSync()
detected.foreach { resource =>
resource.attributes.toList.sortBy(_.key.name).foreach { attribute =>
println(attribute.key.name + ": " + attribute.value)
}
}
println("```")
@:select(build-tool)
@:choice(sbt)
Add settings to the build.sbt
:
libraryDependencies ++= Seq(
"org.typelevel" %%% "otel4s-sdk" % "@VERSION@", // <1>
"org.typelevel" %%% "otel4s-sdk-exporter" % "@VERSION@", // <2>
"org.typelevel" %%% "otel4s-sdk-contrib-aws-resource" % "@VERSION@" // <3>
)
@:choice(scala-cli)
Add directives to the *.scala
file:
//> using lib "org.typelevel::otel4s-sdk::@VERSION@" // <1>
//> using lib "org.typelevel::otel4s-sdk-exporter::@VERSION@" // <2>
//> using lib "org.typelevel::otel4s-sdk-contrib-aws-resource::@VERSION@" // <3>
@:@
- Add the
otel4s-sdk
library - Add the
otel4s-sdk-exporter
library. Without the exporter, the application will crash - Add the
otel4s-sdk-contrib-aws-resource
library
Then autoconfigure the SDK:
@:select(sdk-entry-point)
@:choice(sdk)
OpenTelemetrySdk.autoConfigured
configures both MeterProvider
and TracerProvider
:
import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.sdk.OpenTelemetrySdk
import org.typelevel.otel4s.sdk.contrib.aws.resource._
import org.typelevel.otel4s.sdk.exporter.otlp.autoconfigure.OtlpExportersAutoConfigure
import org.typelevel.otel4s.trace.TracerProvider
object TelemetryApp extends IOApp.Simple {
def run: IO[Unit] =
OpenTelemetrySdk
.autoConfigured[IO](
// register OTLP exporters configurer
_.addExportersConfigurer(OtlpExportersAutoConfigure[IO])
// register AWS Lambda detector
.addResourceDetector(AwsLambdaDetector[IO])
// register AWS EC2 detector
.addResourceDetector(AwsEc2Detector[IO])
// register AWS ECS detector
.addResourceDetector(AwsEcsDetector[IO])
// register AWS Beanstalk detector
.addResourceDetector(AwsBeanstalkDetector[IO])
)
.use { autoConfigured =>
val sdk = autoConfigured.sdk
program(sdk.meterProvider, sdk.tracerProvider)
}
def program(
meterProvider: MeterProvider[IO],
tracerProvider: TracerProvider[IO]
): IO[Unit] =
???
}
@:choice(traces)
SdkTraces
configures only TracerProvider
:
import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.sdk.contrib.aws.resource._
import org.typelevel.otel4s.sdk.exporter.otlp.trace.autoconfigure.OtlpSpanExporterAutoConfigure
import org.typelevel.otel4s.sdk.trace.SdkTraces
import org.typelevel.otel4s.trace.TracerProvider
object TelemetryApp extends IOApp.Simple {
def run: IO[Unit] =
SdkTraces
.autoConfigured[IO](
// register OTLP exporters configurer
_.addExporterConfigurer(OtlpSpanExporterAutoConfigure[IO])
// register AWS Lambda detector
.addResourceDetector(AwsLambdaDetector[IO])
// register AWS EC2 detector
.addResourceDetector(AwsEc2Detector[IO])
// register AWS ECS detector
.addResourceDetector(AwsEcsDetector[IO])
// register AWS Beanstalk detector
.addResourceDetector(AwsBeanstalkDetector[IO])
)
.use { autoConfigured =>
program(autoConfigured.tracerProvider)
}
def program(
tracerProvider: TracerProvider[IO]
): IO[Unit] =
???
}
@:@
The OpenTelemetrySdk.autoConfigured(...)
and SdkTraces.autoConfigured(...)
rely on the environment variables and system properties to configure the SDK.
Check out the configuration details.
There are several ways to configure the options:
@:select(sdk-options-source)
@:choice(sbt)
Add settings to the build.sbt
:
javaOptions += "-Dotel.otel4s.resource.detectors.enabled=aws-lambda,aws-ec2,aws-ecs,aws-beanstalk"
envVars ++= Map("OTEL_OTEL4S_RESOURCE_DETECTORS_ENABLE" -> "aws-lambda,aws-ec2,aws-ecs,aws-beanstalk")
@:choice(scala-cli)
Add directives to the *.scala
file:
//> using javaOpt -Dotel.otel4s.resource.detectors.enabled=aws-lambda,aws-ec2,aws-ecs,aws-beanstalk
@:choice(shell)
$ export OTEL_OTEL4S_RESOURCE_DETECTORS_ENABLED=aws-lambda,aws-ec2,aws-ecs,aws-beanstalk
@:@