Skip to content

Commit

Permalink
Merge pull request #737 from iRevive/sdk-common/process-detector
Browse files Browse the repository at this point in the history
sdk-common: add `ProcessDetector`
  • Loading branch information
iRevive committed Aug 30, 2024
2 parents eaf58ae + 8314836 commit 75f0fa1
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ object OpenTelemetrySdk {
* By default, the following detectors are enabled:
* - host: `host.arch`, `host.name`
* - os: `os.type`, `os.description`
* - process: `process.command`, `process.command_args`,
* `process.command_line`, `process.executable.name`,
* `process.executable.path`, `process.pid`, `process.owner`
* - process_runtime: `process.runtime.name`,
* `process.runtime.version`, `process.runtime.description`
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,12 @@ private object OS {
@JSImport("os", "release")
def release(): String = js.native

@js.native
@JSImport("os", "userInfo")
def userInfo(): UserInfo = js.native

@js.native
trait UserInfo extends js.Object {
def username: String = js.native
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ private object Process {
@JSImport("process", "versions")
def versions: Versions = js.native

@js.native
@JSImport("process", "pid")
def pid: Int = js.native

@js.native
@JSImport("process", "title")
def title: String = js.native

@js.native
@JSImport("process", "execPath")
def execPath: String = js.native

@js.native
@JSImport("process", "argv")
def argv: js.Array[String] = js.native

@js.native
@JSImport("process", "execArgv")
def execArgv: js.Array[String] = js.native

@js.native
trait Versions extends js.Object {
def node: String = js.native
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2023 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.otel4s.sdk.resource

import cats.effect.Sync
import org.typelevel.otel4s.Attributes
import org.typelevel.otel4s.sdk.TelemetryResource
import org.typelevel.otel4s.semconv.SchemaUrls

import scala.util.Try

private[resource] trait ProcessDetectorPlatform { self: ProcessDetector.type =>

def apply[F[_]: Sync]: TelemetryResourceDetector[F] =
new Detector[F]

private class Detector[F[_]: Sync] extends TelemetryResourceDetector[F] {
def name: String = Const.Name

def detect: F[Option[TelemetryResource]] = Sync[F].delay {
val argv = Process.argv.toList

val command =
if (argv.length > 1) Attributes(Keys.Command(Process.argv(1)))
else Attributes.empty

val owner =
Try(
Attributes(Keys.Owner(OS.userInfo().username))
).getOrElse(Attributes.empty)

val args =
argv.headOption.toSeq ++ Process.execArgv ++ argv.drop(1)

val attributes = Attributes(
Keys.Pid(Process.pid.toLong),
Keys.ExecutableName(Process.title),
Keys.ExecutablePath(Process.execPath),
Keys.CommandArgs(args)
) ++ command ++ owner

Some(TelemetryResource(attributes, Some(SchemaUrls.Current)))
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright 2023 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.otel4s.sdk.resource

import cats.effect.Sync
import cats.syntax.flatMap._
import cats.syntax.functor._
import org.typelevel.otel4s.Attributes
import org.typelevel.otel4s.sdk.TelemetryResource
import org.typelevel.otel4s.semconv.SchemaUrls

import java.io.File
import java.lang.management.ManagementFactory
import java.lang.management.RuntimeMXBean
import java.util.Locale
import java.util.regex.Pattern

private[resource] trait ProcessDetectorPlatform { self: ProcessDetector.type =>

private val JarFilePattern =
Pattern.compile("^\\S+\\.(jar|war)", Pattern.CASE_INSENSITIVE)

def apply[F[_]: Sync]: TelemetryResourceDetector[F] =
new Detector[F]

private class Detector[F[_]: Sync] extends TelemetryResourceDetector[F] {
def name: String = Const.Name

def detect: F[Option[TelemetryResource]] =
for {
runtime <- Sync[F].delay(ManagementFactory.getRuntimeMXBean)
javaHomeOpt <- Sync[F].delay(sys.props.get("java.home"))
osNameOpt <- Sync[F].delay(sys.props.get("os.name"))
javaCommandOpt <- Sync[F].delay(sys.props.get("sun.java.command"))
} yield {
val builder = Attributes.newBuilder

getPid(runtime).foreach(pid => builder.addOne(Keys.Pid(pid)))

javaHomeOpt.foreach { javaHome =>
val exePath = executablePath(javaHome, osNameOpt)
val cmdLine = exePath + commandLineArgs(runtime, javaCommandOpt)

builder.addOne(Keys.ExecutablePath(exePath))
builder.addOne(Keys.CommandLine(cmdLine))
}

Some(TelemetryResource(builder.result(), Some(SchemaUrls.Current)))
}

private def getPid(runtime: RuntimeMXBean): Option[Long] =
runtime.getName.split("@").headOption.flatMap(_.toLongOption)

private def executablePath(
javaHome: String,
osName: Option[String]
): String = {
val executablePath = new StringBuilder(javaHome)
executablePath
.append(File.separatorChar)
.append("bin")
.append(File.separatorChar)
.append("java")

if (osName.exists(_.toLowerCase(Locale.ROOT).startsWith("windows")))
executablePath.append(".exe")

executablePath.result()
}

private def commandLineArgs(
runtime: RuntimeMXBean,
javaCommandOpt: Option[String]
): String = {
val commandLine = new StringBuilder()

// VM args: -Dfile.encoding=UTF-8, -Xms2000m, -Xmx2000m, etc
runtime.getInputArguments.forEach { arg =>
commandLine.append(' ').append(arg)
()
}

// general args, e.g.: org.ClassName param1 param2
javaCommandOpt.foreach { javaCommand =>
// '-jar' is missing when launching a jar directly, add it if needed
if (JarFilePattern.matcher(javaCommand).matches())
commandLine.append(" -jar")

commandLine.append(' ').append(javaCommand)
()
}

commandLine.result()
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.otel4s.sdk.resource

import cats.effect.Sync
import org.typelevel.otel4s.Attributes
import org.typelevel.otel4s.sdk.TelemetryResource
import org.typelevel.otel4s.semconv.SchemaUrls

import scala.scalanative.posix.unistd._

private[resource] trait ProcessDetectorPlatform { self: ProcessDetector.type =>

def apply[F[_]: Sync]: TelemetryResourceDetector[F] =
new Detector[F]

private class Detector[F[_]: Sync] extends TelemetryResourceDetector[F] {
def name: String = Const.Name

def detect: F[Option[TelemetryResource]] = Sync[F].delay {
val attributes = Attributes(
Keys.Pid(getpid().longValue)
)

Some(TelemetryResource(attributes, Some(SchemaUrls.Current)))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import java.nio.charset.StandardCharsets
* | otel.resource.attributes | OTEL_RESOURCE_ATTRIBUTES | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 |
* | otel.service.name | OTEL_SERVICE_NAME | Specify logical service name. Takes precedence over `service.name` defined with `otel.resource.attributes` |
* | otel.experimental.resource.disabled-keys | OTEL_EXPERIMENTAL_RESOURCE_DISABLED_KEYS | Specify resource attribute keys that are filtered. |
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host,os,process_runtime`. |
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host,os,process,process_runtime`. |
* }}}
*
* @see
Expand Down Expand Up @@ -206,6 +206,7 @@ private[sdk] object TelemetryResourceAutoConfigure {
val Detectors: Set[String] = Set(
HostDetector.Const.Name,
OSDetector.Const.Name,
ProcessDetector.Const.Name,
ProcessRuntimeDetector.Const.Name
)
}
Expand All @@ -219,7 +220,7 @@ private[sdk] object TelemetryResourceAutoConfigure {
* | otel.resource.attributes | OTEL_RESOURCE_ATTRIBUTES | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 |
* | otel.service.name | OTEL_SERVICE_NAME | Specify logical service name. Takes precedence over `service.name` defined with `otel.resource.attributes` |
* | otel.experimental.resource.disabled-keys | OTEL_EXPERIMENTAL_RESOURCE_DISABLED_KEYS | Specify resource attribute keys that are filtered. |
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host,os,process_runtime`. |
* | otel.otel4s.resource.detectors | OTEL_OTEL4S_RESOURCE_DETECTORS | Specify resource detectors to use. Defaults to `host,os,process,process_runtime`. |
* }}}
*
* @see
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2023 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.otel4s.sdk.resource

import org.typelevel.otel4s.AttributeKey

/** Detects process-specific parameters such as executable name, executable
* path, PID, and so on.
*
* @see
* [[https://opentelemetry.io/docs/specs/semconv/resource/process/]]
*/
object ProcessDetector extends ProcessDetectorPlatform {

private[sdk] object Const {
val Name = "process"
}

private[resource] object Keys {
val Command: AttributeKey[String] =
AttributeKey("process.command")

val CommandArgs: AttributeKey[Seq[String]] =
AttributeKey("process.command_args")

val CommandLine: AttributeKey[String] =
AttributeKey("process.command_line")

val ExecutableName: AttributeKey[String] =
AttributeKey("process.executable.name")

val ExecutablePath: AttributeKey[String] =
AttributeKey("process.executable.path")

val Pid: AttributeKey[Long] =
AttributeKey("process.pid")

val Owner: AttributeKey[String] =
AttributeKey("process.owner")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,18 @@ object TelemetryResourceDetector {
* Includes:
* - host detector
* - os detector
* - process detector
* - process runtime detector
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*/
def default[F[_]: Sync]: Set[TelemetryResourceDetector[F]] =
Set(HostDetector[F], OSDetector[F], ProcessRuntimeDetector[F])
Set(
HostDetector[F],
OSDetector[F],
ProcessDetector[F],
ProcessRuntimeDetector[F]
)

}
Loading

0 comments on commit 75f0fa1

Please sign in to comment.