diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d24030..6c738c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#1](https://github.com/ericdallo/metrepl/pull/1) Enable exporting metrics from `otlp` exporter. + ## 0.4.2 - Bump nrepl to 1.5.0 diff --git a/README.md b/README.md index 0619eb6..19156e8 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Available exporters: - `stdout`: Export the metric to current nREPL process stdout, useful for debugging. - `file`: Export the metric to a file, appending each metric in a new line. -- `otlp`: Export the metric via [OpenTelemetry](https://opentelemetry.io/) to what user configured. +- `otlp`: Export metrics via [OpenTelemetry Protocol](https://opentelemetry.io/docs/specs/otlp/). Supports exporting as logs and/or metrics. By default no exporter is enabled, you need to manually configure which one(s) you want to enable, example: `{:exporters {:stdout {:enabled? true}}}`. diff --git a/deps.edn b/deps.edn index c41ebe7..aefee4d 100644 --- a/deps.edn +++ b/deps.edn @@ -9,6 +9,9 @@ :extra-deps {nubank/matcher-combinators {:mvn/version "3.9.1"} lambdaisland/kaocha {:mvn/version "1.91.1392"}} :main-opts ["-m" "kaocha.runner"]} + :repl {:extra-paths ["dev" "docs" "test"] + :extra-deps {nubank/matcher-combinators {:mvn/version "3.9.1"} + lambdaisland/kaocha {:mvn/version "1.91.1392"}}} :build {:deps {io.github.clojure/tools.build {:tag "v0.10.7" :sha "573711e"} slipset/deps-deploy {:mvn/version "0.2.2"}} :extra-paths ["resources"] diff --git a/src/metrepl/exporters/otlp.clj b/src/metrepl/exporters/otlp.clj index afbc3e0..189db1e 100644 --- a/src/metrepl/exporters/otlp.clj +++ b/src/metrepl/exporters/otlp.clj @@ -3,24 +3,22 @@ [clojure.string :as string] [metrepl.format :as format]) (:import - [io.opentelemetry.api.common AttributeKey] + [io.opentelemetry.api.common AttributeKey Attributes] [io.opentelemetry.api.logs Severity] + [io.opentelemetry.sdk OpenTelemetrySdk] [io.opentelemetry.sdk.autoconfigure AutoConfiguredOpenTelemetrySdk] - [io.opentelemetry.sdk.logs SdkLoggerProvider] [java.util.function Function])) (set! *warn-on-reflection* true) -(defonce otlp-logger-provider* (atom nil)) +(defonce ^:private otlp-provider* (atom nil)) -(defn ^:private setup-logger [otlp-config] - (reset! otlp-logger-provider* +(defn ^:private setup-sdk [otel-config] + (reset! otlp-provider* (-> (AutoConfiguredOpenTelemetrySdk/builder) - (.addPropertiesCustomizer (reify Function (apply [_ _] - otlp-config))) + (.addPropertiesCustomizer ^Function (constantly otel-config)) (.build) - .getOpenTelemetrySdk - .getSdkLoggerProvider))) + .getOpenTelemetrySdk))) (defn ^:private ->severity [level] (case level @@ -30,21 +28,38 @@ :error Severity/ERROR Severity/INFO)) -(defn ^:private ->raw-value [value] - (cond - (keyword? value) (string/join "" (drop 1 (str value))) - (number? value) value - (boolean? value) value - :else (str value))) +(defn ^:private ->attributes + [m] + (let [builder (Attributes/builder)] + (doseq [[k v] m + :let [value (cond + (keyword? v) (string/join "" (drop 1 (str v))) + (number? v) v + (boolean? v) v + :else (str v))]] + (.put builder (AttributeKey/stringKey (name k)) value)) + (.build builder))) -(defn export! [data _metric-cfg {:keys [config]}] - (when-not @otlp-logger-provider* - (setup-logger config)) - (let [log-record-builder (-> (.get ^SdkLoggerProvider @otlp-logger-provider* (str *ns*)) +(defn export! + [{:keys [payload level timestamp] :as data {:keys [op]} :payload} _metric-cfg {:keys [config]}] + (when-not @otlp-provider* + (setup-sdk config)) + (let [^OpenTelemetrySdk sdk @otlp-provider* + base-atrributes (dissoc data :timestamp :level :payload) + log-attributes (->attributes base-atrributes) + log-record-builder (-> (.getSdkLoggerProvider sdk) + (.get (str *ns*)) (.logRecordBuilder) - (.setBody (format/parse-data (:payload data) :json)) - (.setSeverity (->severity (:level data))) - (.setTimestamp (:timestamp data)))] - (doseq [[field value] (dissoc data :timestamp :level :payload)] - (.setAttribute log-record-builder (AttributeKey/stringKey (name field)) (->raw-value value))) - (.emit log-record-builder))) + (.setBody (format/parse-data payload :json)) + (.setSeverity (->severity level)) + (.setTimestamp timestamp) + (.setAllAttributes log-attributes)) + long-counter (-> (.getSdkMeterProvider sdk) + (.get (str *ns*)) + (.counterBuilder "metrepl.events.total") + (.setUnit "events") + (.setDescription "Total number of REPL events") + (.build)) + metric-attributes (->attributes (cond-> base-atrributes op (assoc :operation op)))] + (.emit log-record-builder) + (.add long-counter 1 metric-attributes)))