From d9fa6db2f7dff893cc2489419e73a3fbe0acb915 Mon Sep 17 00:00:00 2001 From: Dennis Schridde Date: Mon, 19 Jun 2023 13:57:08 +0200 Subject: [PATCH] OpenTelemetry Protocol (OTLP) appender Requires com.github.steffan-westcott/clj-otel-api. With Java Agent: Activate an appender configured by the OpenTelemetry Java Agent: ```clj (let [logger-provider (.getLogsBridge (GlobalOpenTelemetry/get)) appender (taoensso.timbre.appenders.community.otlp/otlp-appender logger-provider)] (taoensso.timbre/merge-config! {:appenders {:otlp appender}})) ``` Note: When relying on the OpenTelemetry Java Agent 1.x, you need to explicitly enable the logs exporter with `OTEL_LOGS_EXPORTER=otlp`. This will become the default with the release of Java Agent 2.0, cf. * https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md#version-1270-2023-06-14 * https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8647 Without Java Agent: If you want autoconfiguration without the Java Agent, you also need io.opentelemetry/opentelemetry-sdk-extension-autoconfigure and io.opentelemetry/opentelemetry-exporter-otlp on the classpath. Create an autoconfigured appender and activate it: ```clj (let [logger-provider (.getSdkLoggerProvider (.getOpenTelemetrySdk (.build (AutoConfiguredOpenTelemetrySdk/builder)))) appender (taoensso.timbre.appenders.community.otlp/otlp-appender logger-provider)] (taoensso.timbre/merge-config! {:appenders {:otlp appender}})) ``` If you already have an instance of `GlobalOpenTelemetry`, e.g. created by the OpenTelemetry Java Agent, you need to prevent setting the newly created SDK as the global default: ```clj (.build (doto (AutoConfiguredOpenTelemetrySdk/builder) (.setResultAsGlobal false))) ``` I took inspiration from `taoensso.timbre.appenders.community.sentry`. Once https://github.com/steffan-westcott/clj-otel/issues/8 is implemented, the actual log emission should be replaced with using clj-otel's API. --- project.clj | 2 +- .../timbre/appenders/community/otlp.clj | 105 ++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/taoensso/timbre/appenders/community/otlp.clj diff --git a/project.clj b/project.clj index 4342c036..56f31be7 100644 --- a/project.clj +++ b/project.clj @@ -52,7 +52,7 @@ [server-socket "1.0.0"] [org.zeromq/cljzmq "0.1.4"] [cljs-node-io "1.1.2"] ; Node spit appender - ]} + [com.github.steffan-westcott/clj-otel-api "0.2.3"]]} :extra {:source-paths [ "src" "extra/src"] diff --git a/src/taoensso/timbre/appenders/community/otlp.clj b/src/taoensso/timbre/appenders/community/otlp.clj new file mode 100644 index 00000000..e54ae417 --- /dev/null +++ b/src/taoensso/timbre/appenders/community/otlp.clj @@ -0,0 +1,105 @@ +(ns taoensso.timbre.appenders.community.otlp + "OpenTelemetry Protocol (OTLP) appender. + Requires com.github.steffan-westcott/clj-otel-api. + + # With Java Agent + + Activate an appender configured by the OpenTelemetry Java Agent: + ```clj + (let [logger-provider (.getLogsBridge (GlobalOpenTelemetry/get)) + appender (otlp/appender logger-provider)] + (timbre/merge-config! {:appenders {:otlp appender}})) + ``` + + Note: When relying on the OpenTelemetry Java Agent 1.x, you need + to explicitly enable the logs exporter with `OTEL_LOGS_EXPORTER=otlp`. + This will become the default with the release of Java Agent 2.0, cf. + * https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md#version-1270-2023-06-14 + * https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8647 + + # Without Java Agent + + If you want autoconfiguration without the Java Agent, you also need + io.opentelemetry/opentelemetry-sdk-extension-autoconfigure and + io.opentelemetry/opentelemetry-exporter-otlp on the classpath. + + Create an autoconfigured appender and activate it: + ```clj + (let [logger-provider (.getSdkLoggerProvider + (.getOpenTelemetrySdk + (.build + (AutoConfiguredOpenTelemetrySdk/builder)))) + appender (otlp/appender logger-provider)] + (timbre/merge-config! {:appenders {:otlp appender}})) + ``` + + If you already have an instance of `GlobalOpenTelemetry`, e.g. created + by the OpenTelemetry Java Agent, you need to prevent setting the newly + created SDK as the global default: + ```clj + (.build + (doto (AutoConfiguredOpenTelemetrySdk/builder) + (.setResultAsGlobal false))) + ```" + {:author "Dennis Schridde (@devurandom)"} + (:require + [steffan-westcott.clj-otel.api.attributes :as attr]) + (:import + (io.opentelemetry.api.logs LoggerProvider Severity) + (java.util Date))) + +(set! *warn-on-reflection* true) + +(def ^:private default-severity + Severity/INFO) + +(def ^:private timbre->otlp-levels + {:trace Severity/TRACE + :debug Severity/DEBUG + :info Severity/INFO + :warn Severity/WARN + :error Severity/ERROR + :fatal Severity/FATAL + :report default-severity}) + +(defn- single-map [xs] + (let [[x & r] xs] + (when (and (map? x) (not r)) + x))) + +(defn appender + [^LoggerProvider logger-provider] + {:enabled? true + :async? true + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [{:keys [^Date instant level ^String ?ns-str ?file ?line ?err vargs msg_ context]}] + (let [actual-instant (.toInstant instant) + severity (get timbre->otlp-levels level default-severity) + arg (single-map vargs) + message (if-let [msg (:msg arg)] + msg + (force msg_)) + ?ex-data (ex-data ?err) + extra (cond-> context + (and ?file (not (contains? context :file))) + (assoc :file ?file) + (and ?line (not (contains? context :line))) + (assoc :line ?line) + (and ?ex-data (not (contains? context :ex-data))) + (assoc :ex-data ?ex-data)) + event (merge (dissoc arg :msg) + extra) + attributes (attr/->attributes event) + ; TODO: Use clj-otel once it supports the logs API. + ; cf. https://github.com/steffan-westcott/clj-otel/issues/8 + logger (.get logger-provider ?ns-str)] + (.emit + (doto (.logRecordBuilder logger) + (.setTimestamp actual-instant) + (.setSeverity severity) + (.setSeverityText (.toString severity)) + (.setAllAttributes attributes) + (.setBody message)))))})