Skip to content

Commit 452454e

Browse files
authored
Merge pull request #723 from iRevive/sdk-common/os-resource-detector
sdk-common: add `OSDetector`
2 parents 151242b + ab3b68f commit 452454e

File tree

12 files changed

+199
-8
lines changed

12 files changed

+199
-8
lines changed

docs/sdk/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ If not specified, SDK defaults the service name to `unknown_service:scala`.
5050
| otel.resource.attributes | OTEL\\_RESOURCE\\_ATTRIBUTES | Specify resource attributes in the following format: `key1=val1,key2=val2,key3=val3`. |
5151
| otel.service.name | OTEL\\_SERVICE\\_NAME | Specify logical service name. Takes precedence over `service.name` defined with `otel.resource.attributes`. |
5252
| otel.experimental.resource.disabled-keys | OTEL\\_EXPERIMENTAL\\_RESOURCE\\_DISABLED\\_KEYS | Specify resource attribute keys that are filtered. |
53-
| otel.otel4s.resource.detectors | OTEL\\_OTEL4S\\_RESOURCE\\_DETECTORS | Specify resource detectors to use. Defaults to `host`. |
53+
| otel.otel4s.resource.detectors | OTEL\\_OTEL4S\\_RESOURCE\\_DETECTORS | Specify resource detectors to use. Defaults to `host,os`. |
5454

5555
## Metrics
5656

sdk/all/src/main/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdk.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ object OpenTelemetrySdk {
197197
*
198198
* By default, the following detectors are enabled:
199199
* - host: `host.arch`, `host.name`
200+
* - os: `os.type`, `os.description`
200201
*
201202
* @param detector
202203
* the detector to add

sdk/common/js/src/main/scala/org/typelevel/otel4s/sdk/resource/OS.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,12 @@ private object OS {
2929
@JSImport("os", "hostname")
3030
def hostname(): String = js.native
3131

32+
@js.native
33+
@JSImport("os", "platform")
34+
def platform(): String = js.native
35+
36+
@js.native
37+
@JSImport("os", "release")
38+
def release(): String = js.native
39+
3240
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2023 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.typelevel.otel4s.sdk.resource
18+
19+
import cats.effect.Sync
20+
import org.typelevel.otel4s.Attributes
21+
import org.typelevel.otel4s.sdk.TelemetryResource
22+
import org.typelevel.otel4s.semconv.SchemaUrls
23+
24+
private[resource] trait OSDetectorPlatform { self: OSDetector.type =>
25+
26+
def apply[F[_]: Sync]: TelemetryResourceDetector[F] =
27+
new Detector[F]
28+
29+
private class Detector[F[_]: Sync] extends TelemetryResourceDetector[F] {
30+
def name: String = Const.Name
31+
32+
def detect: F[Option[TelemetryResource]] = Sync[F].delay {
33+
val tpe = Keys.Type(normalizeType(OS.platform()))
34+
val release = Keys.Description(OS.release())
35+
36+
Some(
37+
TelemetryResource(Attributes(tpe, release), Some(SchemaUrls.Current))
38+
)
39+
}
40+
}
41+
42+
// transforms https://nodejs.org/api/os.html#osplatform values to match the spec:
43+
// https://opentelemetry.io/docs/specs/semconv/resource/os/
44+
private def normalizeType(platform: String): String =
45+
platform match {
46+
case "sunos" => "solaris"
47+
case "win32" => "windows"
48+
case other => other
49+
}
50+
51+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2023 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.typelevel.otel4s.sdk.resource
18+
19+
import cats.effect.Sync
20+
import cats.syntax.flatMap._
21+
import cats.syntax.functor._
22+
import org.typelevel.otel4s.Attributes
23+
import org.typelevel.otel4s.sdk.TelemetryResource
24+
import org.typelevel.otel4s.semconv.SchemaUrls
25+
26+
import java.util.Locale
27+
28+
private[resource] trait OSDetectorPlatform { self: OSDetector.type =>
29+
30+
def apply[F[_]: Sync]: TelemetryResourceDetector[F] =
31+
new Detector[F]
32+
33+
private class Detector[F[_]: Sync] extends TelemetryResourceDetector[F] {
34+
def name: String = Const.Name
35+
36+
def detect: F[Option[TelemetryResource]] =
37+
for {
38+
nameOpt <- Sync[F].delay(sys.props.get("os.name"))
39+
versionOpt <- Sync[F].delay(sys.props.get("os.version"))
40+
} yield {
41+
val tpe = nameOpt.flatMap(nameToType).map(Keys.Type(_))
42+
43+
val description = versionOpt
44+
.zip(nameOpt)
45+
.map { case (version, name) =>
46+
Keys.Description(name + " " + version)
47+
}
48+
.orElse(nameOpt.map(Keys.Description(_)))
49+
50+
val attributes = tpe.to(Attributes) ++ description.to(Attributes)
51+
Option.when(attributes.nonEmpty)(
52+
TelemetryResource(attributes, Some(SchemaUrls.Current))
53+
)
54+
}
55+
}
56+
57+
// transforms OS names to match the spec:
58+
// https://opentelemetry.io/docs/specs/semconv/resource/os/
59+
private def nameToType(name: String): Option[String] =
60+
name.toLowerCase(Locale.ROOT) match {
61+
case os if os.startsWith("windows") => Some("windows")
62+
case os if os.startsWith("linux") => Some("linux")
63+
case os if os.startsWith("mac") => Some("darwin")
64+
case os if os.startsWith("freebsd") => Some("freebsd")
65+
case os if os.startsWith("netbsd") => Some("netbsd")
66+
case os if os.startsWith("openbsd") => Some("openbsd")
67+
case os if os.startsWith("dragonflybsd") => Some("dragonflybsd")
68+
case os if os.startsWith("hp-ux") => Some("hpux")
69+
case os if os.startsWith("aix") => Some("aix")
70+
case os if os.startsWith("solaris") => Some("solaris")
71+
case os if os.startsWith("z/os") => Some("z_os")
72+
case _ => None
73+
}
74+
75+
}

sdk/common/shared/src/main/scala/org/typelevel/otel4s/sdk/autoconfigure/TelemetryResourceAutoConfigure.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import java.nio.charset.StandardCharsets
4040
* | otel.resource.attributes | OTEL_RESOURCE_ATTRIBUTES | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 |
4141
* | otel.service.name | OTEL_SERVICE_NAME | Specify logical service name. Takes precedence over `service.name` defined with `otel.resource.attributes` |
4242
* | otel.experimental.resource.disabled-keys | OTEL_EXPERIMENTAL_RESOURCE_DISABLED_KEYS | Specify resource attribute keys that are filtered. |
43-
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host` |
43+
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host,os`. |
4444
* }}}
4545
*
4646
* @see
@@ -204,7 +204,8 @@ private[sdk] object TelemetryResourceAutoConfigure {
204204

205205
private object Defaults {
206206
val Detectors: Set[String] = Set(
207-
HostDetector.Const.Name
207+
HostDetector.Const.Name,
208+
OSDetector.Const.Name
208209
)
209210
}
210211

@@ -217,7 +218,7 @@ private[sdk] object TelemetryResourceAutoConfigure {
217218
* | otel.resource.attributes | OTEL_RESOURCE_ATTRIBUTES | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 |
218219
* | otel.service.name | OTEL_SERVICE_NAME | Specify logical service name. Takes precedence over `service.name` defined with `otel.resource.attributes` |
219220
* | otel.experimental.resource.disabled-keys | OTEL_EXPERIMENTAL_RESOURCE_DISABLED_KEYS | Specify resource attribute keys that are filtered. |
220-
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host` |
221+
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host,os`. |
221222
* }}}
222223
*
223224
* @see
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2023 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.typelevel.otel4s.sdk.resource
18+
19+
import org.typelevel.otel4s.AttributeKey
20+
21+
/** Detects OS type and description.
22+
*
23+
* @see
24+
* https://opentelemetry.io/docs/specs/semconv/resource/os/
25+
*/
26+
object OSDetector extends OSDetectorPlatform {
27+
28+
private[sdk] object Const {
29+
val Name = "os"
30+
}
31+
32+
private[resource] object Keys {
33+
val Type: AttributeKey[String] =
34+
AttributeKey[String]("os.type")
35+
36+
val Description: AttributeKey[String] =
37+
AttributeKey[String]("os.description")
38+
}
39+
40+
}

sdk/common/shared/src/main/scala/org/typelevel/otel4s/sdk/resource/TelemetryResourceDetector.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@ object TelemetryResourceDetector {
5353
*
5454
* Includes:
5555
* - host detector
56+
* - os detector
5657
*
5758
* @tparam F
5859
* the higher-kinded type of a polymorphic effect
5960
*/
6061
def default[F[_]: Sync]: Set[TelemetryResourceDetector[F]] =
61-
Set(HostDetector[F])
62+
Set(HostDetector[F], OSDetector[F])
6263

6364
}

sdk/common/shared/src/test/scala/org/typelevel/otel4s/sdk/autoconfigure/TelemetryResourceAutoConfigureSuite.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class TelemetryResourceAutoConfigureSuite extends CatsEffectSuite {
6868
.use { resource =>
6969
val service = Set("service.name")
7070
val host = Set("host.arch", "host.name")
71+
val os = Set("os.type", "os.description")
7172

7273
val telemetry = Set(
7374
"telemetry.sdk.language",
@@ -76,7 +77,7 @@ class TelemetryResourceAutoConfigureSuite extends CatsEffectSuite {
7677
)
7778

7879
val all =
79-
host ++ service ++ telemetry
80+
host ++ os ++ service ++ telemetry
8081

8182
IO(assertEquals(resource.attributes.map(_.key.name).toSet, all))
8283
}

sdk/common/shared/src/test/scala/org/typelevel/otel4s/sdk/resource/TelemetryResourceDetectorSuite.scala

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,20 @@ class TelemetryResourceDetectorSuite extends CatsEffectSuite {
3333
}
3434
}
3535

36-
test("default - contain host detector") {
36+
test("OSDetector - detect OS type and description") {
37+
val keys = Set("os.type", "os.description")
38+
39+
for {
40+
resource <- OSDetector[IO].detect
41+
} yield {
42+
assertEquals(resource.map(_.attributes.map(_.key.name).toSet), Some(keys))
43+
assertEquals(resource.flatMap(_.schemaUrl), Some(SchemaUrls.Current))
44+
}
45+
}
46+
47+
test("default - contain host, os detectors") {
3748
val detectors = TelemetryResourceDetector.default[IO].map(_.name)
38-
val expected = Set("host")
49+
val expected = Set("host", "os")
3950

4051
assertEquals(detectors.map(_.name), expected)
4152
}

0 commit comments

Comments
 (0)