diff --git a/core/common/src/main/scala/org/typelevel/otel4s/Attributes.scala b/core/common/src/main/scala/org/typelevel/otel4s/Attributes.scala index 1cc71958f..97b5074af 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/Attributes.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/Attributes.scala @@ -53,12 +53,28 @@ sealed trait Attributes final def added[T: KeySelect](name: String, value: T): Attributes = added(Attribute(name, value)) + /** Adds an [[`Attribute`]] with the given name and value to these `Attributes`, replacing any `Attribute` with the + * same name and type if one exists. + * + * If the `value` is None, the attributes remain intact. + */ + final def added[T: KeySelect](name: String, value: Option[T]): Attributes = + value.fold(this)(added(name, _)) + /** Adds an [[`Attribute`]] with the given key and value to these `Attributes`, replacing any `Attribute` with the * same key if one exists. */ final def added[T](key: AttributeKey[T], value: T): Attributes = added(Attribute(key, value)) + /** Adds an [[`Attribute`]] with the given key and value to these `Attributes`, replacing any `Attribute` with the + * same key if one exists. + * + * If the `value` is None, the attributes remain intact. + */ + final def added[T](key: AttributeKey[T], value: Option[T]): Attributes = + value.fold(this)(added(key, _)) + /** Adds the given [[`Attribute`]] to these `Attributes`, replacing any `Attribute` with the same key if one exists. */ def added(attribute: Attribute[_]): Attributes @@ -210,6 +226,44 @@ object Attributes extends SpecificIterableFactory[Attribute[_], Attributes] { this } + /** Adds the attribute with the given `key` and `value` (if any) to the builder. + * + * @note + * no attribute will be added or overwritten if the `value` is None. + * + * @note + * if the given `key` is already present in the builder, the value will be overwritten with the given `value`. + * + * @param key + * the key of the attribute. Denotes the types of the `value` + * + * @param value + * the value of the attribute + */ + def addOne[A](key: AttributeKey[A], value: Option[A]): this.type = { + value.foreach(v => addOne(key, v)) + this + } + + /** Adds the attribute with the given `key` (created from `name`) and `value` (if any) to the builder. + * + * @note + * no attribute will be added or overwritten if the `value` is None. + * + * @note + * if the given `key` is already present in the builder, the value will be overwritten with the given `value`. + * + * @param name + * the name of the attribute's key + * + * @param value + * the value of the attribute + */ + def addOne[A: KeySelect](name: String, value: Option[A]): this.type = { + value.foreach(v => addOne(name, v)) + this + } + /** Adds the given `attribute` to the builder. * * @note diff --git a/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSBeanstalkDetector.scala b/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSBeanstalkDetector.scala index 04e439542..33d2ec7db 100644 --- a/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSBeanstalkDetector.scala +++ b/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSBeanstalkDetector.scala @@ -67,9 +67,9 @@ private class AWSBeanstalkDetector[F[_]: Concurrent: Files: Console] private ( builder.addOne(Keys.CloudProvider, Const.CloudProvider) builder.addOne(Keys.CloudPlatform, Const.CloudPlatform) - metadata.deploymentId.foreach(id => builder.addOne(Keys.ServiceInstanceId, id.toString)) - metadata.versionLabel.foreach(v => builder.addOne(Keys.ServiceVersion, v)) - metadata.environmentName.foreach(n => builder.addOne(Keys.ServiceNamespace, n)) + builder.addOne(Keys.ServiceInstanceId, metadata.deploymentId.map(_.toString)) + builder.addOne(Keys.ServiceVersion, metadata.versionLabel) + builder.addOne(Keys.ServiceNamespace, metadata.environmentName) TelemetryResource(builder.result(), Some(SchemaUrls.Current)) } diff --git a/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSECSDetector.scala b/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSECSDetector.scala index d9230b90a..f966923d4 100644 --- a/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSECSDetector.scala +++ b/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSECSDetector.scala @@ -86,14 +86,14 @@ private class AWSECSDetector[F[_]: Async: Network: Env: Console] private ( builder.addOne(Keys.CloudPlatform, Const.CloudPlatform) builder.addOne(Keys.CloudResourceId, container.containerArn) builder.addOne(Keys.CloudAvailabilityZones, task.availabilityZone) - regionOpt.foreach(region => builder.addOne(Keys.CloudRegion, region)) - accountIdOpt.foreach(accountId => builder.addOne(Keys.CloudAccountId, accountId)) + builder.addOne(Keys.CloudRegion, regionOpt) + builder.addOne(Keys.CloudAccountId, accountIdOpt) // container builder.addOne(Keys.ContainerId, container.dockerId) builder.addOne(Keys.ContainerName, container.dockerName) - imageNameOpt.foreach(name => builder.addOne(Keys.ContainerImageName, name)) - imageTagOpt.foreach(tag => builder.addOne(Keys.ContainerImageTags, Seq(tag))) + builder.addOne(Keys.ContainerImageName, imageNameOpt) + builder.addOne(Keys.ContainerImageTags, imageTagOpt.map(Seq(_))) // aws builder.addOne(Keys.AwsLogGroupNames, Seq(container.logOptions.group)) diff --git a/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSLambdaDetector.scala b/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSLambdaDetector.scala index 91a9d6758..1fb6cd112 100644 --- a/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSLambdaDetector.scala +++ b/sdk-contrib/aws/resource/src/main/scala/org/typelevel/otel4s/sdk/contrib/aws/resource/AWSLambdaDetector.scala @@ -51,9 +51,9 @@ private class AWSLambdaDetector[F[_]: Env: Monad] extends TelemetryResourceDetec builder.addOne(Keys.CloudProvider, Const.CloudProvider) builder.addOne(Keys.CloudPlatform, Const.CloudPlatform) - region.foreach(r => builder.addOne(Keys.CloudRegion, r)) - functionName.foreach(name => builder.addOne(Keys.FaasName, name)) - functionVersion.foreach(v => builder.addOne(Keys.FaasVersion, v)) + builder.addOne(Keys.CloudRegion, region) + builder.addOne(Keys.FaasName, functionName) + builder.addOne(Keys.FaasVersion, functionVersion) TelemetryResource(builder.result(), Some(SchemaUrls.Current)) } diff --git a/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala b/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala index c40859fe7..4771db29c 100644 --- a/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala +++ b/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala @@ -36,15 +36,16 @@ private[resource] trait HostDetectorPlatform { self: HostDetector.type => def detect: F[Option[TelemetryResource]] = for { - hostOpt <- Sync[F] - .blocking(InetAddress.getLocalHost.getHostName) - .redeem(_ => None, Some(_)) - - archOpt <- Sync[F].delay(sys.props.get("os.arch")) + host <- Sync[F].blocking(InetAddress.getLocalHost.getHostName).redeem(_ => None, Some(_)) + arch <- Sync[F].delay(sys.props.get("os.arch")) } yield { - val host = hostOpt.map(Keys.Host(_)) - val arch = archOpt.map(Keys.Arch(_)) - val attributes = host.to(Attributes) ++ arch.to(Attributes) + val builder = Attributes.newBuilder + + builder.addOne(Keys.Host, host) + builder.addOne(Keys.Arch, arch) + + val attributes = builder.result() + Option.when(attributes.nonEmpty)( TelemetryResource(attributes, Some(SchemaUrls.Current)) ) diff --git a/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessDetectorPlatform.scala b/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessDetectorPlatform.scala index d8fdba78e..dd9cad16b 100644 --- a/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessDetectorPlatform.scala +++ b/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessDetectorPlatform.scala @@ -49,7 +49,7 @@ private[resource] trait ProcessDetectorPlatform { self: ProcessDetector.type => } yield { val builder = Attributes.newBuilder - getPid(runtime).foreach(pid => builder.addOne(Keys.Pid(pid))) + builder.addOne(Keys.Pid, getPid(runtime)) javaHomeOpt.foreach { javaHome => val exePath = executablePath(javaHome, osNameOpt) diff --git a/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessRuntimeDetectorPlatform.scala b/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessRuntimeDetectorPlatform.scala index 384feeaba..cf50c404d 100644 --- a/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessRuntimeDetectorPlatform.scala +++ b/sdk/common/jvm/src/main/scala/org/typelevel/otel4s/sdk/resource/ProcessRuntimeDetectorPlatform.scala @@ -43,11 +43,14 @@ private[resource] trait ProcessRuntimeDetectorPlatform { } yield { val attributes = Attributes.newBuilder - runtimeName.foreach(name => attributes.addOne(Keys.Name(name))) - runtimeVersion.foreach(name => attributes.addOne(Keys.Version(name))) - (vmVendor, vmName, vmVersion).mapN { (vendor, name, version) => - attributes.addOne(Keys.Description(s"$vendor $name $version")) - } + val description = + (vmVendor, vmName, vmVersion).mapN { (vendor, name, version) => + s"$vendor $name $version" + } + + attributes.addOne(Keys.Name, runtimeName) + attributes.addOne(Keys.Version, runtimeVersion) + attributes.addOne(Keys.Description, description) Some(TelemetryResource(attributes.result(), Some(SchemaUrls.Current))) } diff --git a/sdk/common/native/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala b/sdk/common/native/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala index 3b33c1c61..54d44614e 100644 --- a/sdk/common/native/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala +++ b/sdk/common/native/src/main/scala/org/typelevel/otel4s/sdk/resource/HostDetectorPlatform.scala @@ -38,12 +38,16 @@ private[resource] trait HostDetectorPlatform { self: HostDetector.type => def detect: F[Option[TelemetryResource]] = for { - hostOpt <- Sync[F].delay(detectHost).handleError(_ => None) - archOpt <- Sync[F].delay(sys.props.get("os.arch")) + host <- Sync[F].delay(detectHost).handleError(_ => None) + arch <- Sync[F].delay(sys.props.get("os.arch")) } yield { - val host = hostOpt.map(Keys.Host(_)) - val arch = archOpt.map(Keys.Arch(_)) - val attributes = host.to(Attributes) ++ arch.to(Attributes) + val builder = Attributes.newBuilder + + builder.addOne(Keys.Host, host) + builder.addOne(Keys.Arch, arch) + + val attributes = builder.result() + Option.when(attributes.nonEmpty)( TelemetryResource(attributes, Some(SchemaUrls.Current)) )