Compare commits

..

11 commits

Author SHA1 Message Date
Simon Cruanes
aa86fc455d
wip changelog
Some checks failed
format / format (push) Has been cancelled
build / build (4.08.x, ubuntu-latest) (push) Has been cancelled
build / build (4.13.x, ubuntu-latest) (push) Has been cancelled
build / build (5.0.x, ubuntu-latest) (push) Has been cancelled
build / build (5.3.x, ubuntu-latest) (push) Has been cancelled
2026-03-06 14:47:31 -05:00
Simon Cruanes
d57c182daa
add config parameter for self_metrics 2026-03-06 14:44:01 -05:00
Simon Cruanes
00d41b5354
format 2026-03-06 12:55:35 -05:00
Simon Cruanes
aa9e3f98ff
try weirder tricks for version generation 2026-03-06 12:32:16 -05:00
Simon Cruanes
d974213376
fix tests 2026-03-06 12:07:50 -05:00
Simon Cruanes
6faf23899f
format 2026-03-06 12:02:01 -05:00
Simon Cruanes
068baca4c9
follow otel semconv 2026-03-06 11:37:35 -05:00
Simon Cruanes
d77dbacfb5
fix warning by explicitly including unix 2026-03-06 11:34:04 -05:00
Simon Cruanes
6e07d48d5d
small refactor 2026-03-06 11:32:35 -05:00
Simon Cruanes
e0560ac730
globals: add sdk name+vesion to resource attributes 2026-03-06 11:32:26 -05:00
Simon Cruanes
ba264c7094
add auto-generated Version module to main lib 2026-03-06 11:32:08 -05:00
19 changed files with 157 additions and 19 deletions

3
.gitignore vendored
View file

@ -7,3 +7,6 @@ _opam
*.install
*.exe
*.tmp
src/lib/version.ml
src/lib/.git_index_path
src/lib/.git_index.lnk

View file

@ -1,4 +1,36 @@
## 0.20
- major refactor: split library into `opentelemetry.core`, `opentelemetry`,
`opentelemetry.util`, `opentelemetry.emitter`, `opentelemetry.atomic`, revamp internals
- per-signal providers: separate trace, meter, and logger providers replace
the single monolithic exporter
- `opentelemetry.ambient-context` is now a standalone library, once again
- new `opentelemetry-client-ocurl-lwt` package
- client: split `opentelemetry-client-sync` off of the main client library
- client: add support for `http/json` protocol alongside `http/protobuf`
- client: add HTTP retry with exponential backoff
- client: overhaul bounded queue; introduce generic consumer framework
- client: add `Exporter_add_batching`, `Emitter_add_batching`, `Emitter_sample`,
`Emitter_limit_interval` combinators. Batching is factored out of individual
client libraries.
- client: add sampler as an emitter transformer
- client: add `exporter_stdout` and `debug_exporter`
- client: add `self_metrics` and `self_debug` to exporters
- client: add `after_shutdown` callback in ocurl/ocurl-lwt clients
- `Span.dummy`: inert span that is never modified
- `Span.record_exception` now also sets the span status to error
- `Span.set_span_status` added in `opentelemetry.trace`
- `Span`: carry flags to `span_link`
- `Span`: now mutable thanks to ocaml-protoc 4.0, replaces old `Scope.t` entirely
- `Meter.emit` and `Meter_provider.emit_l` added
- emitter: add `flat_map`, `tap`, `to_list`, `enabled` combinators
- clock abstraction added; `ptime` used by default in logger and metrics
- interval limiter used for `metrics_callbacks`
- update to OTEL spec 1.8.0
- update semantic conventions
- various bug fixes and performance improvements
## 0.12
- breaking: change `Collector.cleanup` so it takes a callback

View file

@ -186,7 +186,8 @@ let setup_ ~sw ~config env : unit =
Opentelemetry.Self_debug.log Opentelemetry.Self_debug.Info (fun () ->
"opentelemetry: cohttp-eio exporter installed");
Opentelemetry_client.Self_trace.set_enabled config.self_trace
Opentelemetry_client.Self_trace.set_enabled config.self_trace;
if config.self_metrics then Opentelemetry.Sdk.setup_self_metrics ()
let setup ?(config = Config.make ()) ?(enable = true) ~sw env =
if enable && not config.sdk_disabled then setup_ ~sw ~config env

View file

@ -120,6 +120,7 @@ let setup_ ~config () : unit =
Opentelemetry.Self_debug.log Opentelemetry.Self_debug.Info (fun () ->
"opentelemetry: cohttp-lwt exporter installed");
Opentelemetry_client.Self_trace.set_enabled config.self_trace;
if config.self_metrics then Opentelemetry.Sdk.setup_self_metrics ();
()
let setup ?(config = Config.make ()) ?(enable = true) () =

View file

@ -95,6 +95,7 @@ let setup_ ~config () : Exporter.t =
Opentelemetry.Self_debug.log Opentelemetry.Self_debug.Info (fun () ->
"opentelemetry: ocurl-lwt exporter installed");
Opentelemetry_client.Self_trace.set_enabled config.self_trace;
if config.self_metrics then Opentelemetry.Sdk.setup_self_metrics ();
exp

View file

@ -100,6 +100,7 @@ let setup_ ~config () : OTEL.Exporter.t =
"opentelemetry: ocurl exporter installed");
OTELC.Self_trace.set_enabled config.common.self_trace;
if config.common.self_metrics then Opentelemetry.Sdk.setup_self_metrics ();
exporter
let remove_exporter () : unit =

View file

@ -26,6 +26,7 @@ type t = {
metrics: Opentelemetry.Provider_config.t;
logs: Opentelemetry.Provider_config.t;
self_trace: bool;
self_metrics: bool;
http_concurrency_level: int option;
retry_max_attempts: int;
retry_initial_delay_ms: float;
@ -65,6 +66,7 @@ let pp out (self : t) : unit =
log_level;
sdk_disabled;
self_trace;
self_metrics;
url_traces;
url_metrics;
url_logs;
@ -91,7 +93,8 @@ let pp out (self : t) : unit =
in
Format.fprintf out
"{@[ debug=%B;@ log_level=%a;@ sdk_disabled=%B;@ self_trace=%B;@ \
url_traces=%S;@ url_metrics=%S;@ url_logs=%S;@ @[<2>headers=@,\
self_metrics=%B;@ url_traces=%S;@ url_metrics=%S;@ url_logs=%S;@ \
@[<2>headers=@,\
%a@];@ @[<2>headers_traces=@,\
%a@];@ @[<2>headers_metrics=@,\
%a@];@ @[<2>headers_logs=@,\
@ -100,8 +103,8 @@ let pp out (self : t) : unit =
logs=%a;@ http_concurrency_level=%a;@ retry_max_attempts=%d;@ \
retry_initial_delay_ms=%.0f;@ retry_max_delay_ms=%.0f;@ \
retry_backoff_multiplier=%.1f @]}"
debug pp_log_level log_level sdk_disabled self_trace url_traces url_metrics
url_logs ppheaders headers ppheaders headers_traces ppheaders
debug pp_log_level log_level sdk_disabled self_trace self_metrics url_traces
url_metrics url_logs ppheaders headers ppheaders headers_traces ppheaders
headers_metrics ppheaders headers_logs pp_protocol protocol timeout_ms
timeout_traces_ms timeout_metrics_ms timeout_logs_ms pp_provider_config
traces pp_provider_config metrics pp_provider_config logs ppiopt
@ -135,6 +138,7 @@ type 'k make =
?timeout_metrics_ms:int ->
?timeout_logs_ms:int ->
?self_trace:bool ->
?self_metrics:bool ->
?http_concurrency_level:int ->
?retry_max_attempts:int ->
?retry_initial_delay_ms:float ->
@ -245,9 +249,9 @@ module Env () : ENV = struct
?(protocol = get_protocol_from_env "OTEL_EXPORTER_OTLP_PROTOCOL")
?(timeout_ms = get_timeout_from_env "OTEL_EXPORTER_OTLP_TIMEOUT" 10_000)
?timeout_traces_ms ?timeout_metrics_ms ?timeout_logs_ms
?(self_trace = false) ?http_concurrency_level ?(retry_max_attempts = 3)
?(retry_initial_delay_ms = 100.) ?(retry_max_delay_ms = 5000.)
?(retry_backoff_multiplier = 2.0) =
?(self_trace = false) ?(self_metrics = false) ?http_concurrency_level
?(retry_max_attempts = 3) ?(retry_initial_delay_ms = 100.)
?(retry_max_delay_ms = 5000.) ?(retry_backoff_multiplier = 2.0) =
let batch_timeout_ = Mtime.Span.(batch_timeout_ms * ms) in
let traces =
match traces with
@ -375,6 +379,7 @@ module Env () : ENV = struct
metrics;
logs;
self_trace;
self_metrics;
http_concurrency_level;
retry_max_attempts;
retry_initial_delay_ms;

View file

@ -72,6 +72,10 @@ type t = {
(** If true, the OTEL library will perform some self-instrumentation.
Default [false].
@since 0.7 *)
self_metrics: bool;
(** If true, the OTEL library will regularly emit metrics about itself.
Default [false].
@since NEXT_RELEASE *)
http_concurrency_level: int option;
(** How many HTTP requests can be done simultaneously (at most)? This can
be used to represent the size of a pool of workers where each worker
@ -123,6 +127,7 @@ type 'k make =
?timeout_metrics_ms:int ->
?timeout_logs_ms:int ->
?self_trace:bool ->
?self_metrics:bool ->
?http_concurrency_level:int ->
?retry_max_attempts:int ->
?retry_initial_delay_ms:float ->

View file

@ -2,14 +2,19 @@
open Common_
open struct
let mk_resources ?service_name ?attrs () =
let attributes = OTEL.Globals.mk_attributes ?service_name ?attrs () in
Proto.Resource.make_resource ~attributes ()
end
let make_resource_logs ?service_name ?attrs (logs : Proto.Logs.log_record list)
: Proto.Logs.resource_logs =
let attributes = OTEL.Globals.mk_attributes ?service_name ?attrs () in
let resource = Proto.Resource.make_resource ~attributes () in
let ll =
Proto.Logs.make_scope_logs ~scope:OTEL.Globals.instrumentation_library
~log_records:logs ()
in
let resource = mk_resources ?service_name ?attrs () in
Proto.Logs.make_resource_logs ~resource ~scope_logs:[ ll ] ()
let make_resource_spans ?service_name ?attrs spans : Proto.Trace.resource_spans
@ -18,8 +23,7 @@ let make_resource_spans ?service_name ?attrs spans : Proto.Trace.resource_spans
Proto.Trace.make_scope_spans ~scope:OTEL.Globals.instrumentation_library
~spans ()
in
let attributes = OTEL.Globals.mk_attributes ?service_name ?attrs () in
let resource = Proto.Resource.make_resource ~attributes () in
let resource = mk_resources ?service_name ?attrs () in
Proto.Trace.make_resource_spans ~resource ~scope_spans:[ ils ] ()
(** Aggregate metrics into a {!Proto.Metrics.resource_metrics} *)
@ -29,6 +33,5 @@ let make_resource_metrics ?service_name ?attrs (l : OTEL.Metrics.t list) :
let lm =
make_scope_metrics ~scope:OTEL.Globals.instrumentation_library ~metrics:l ()
in
let attributes = OTEL.Globals.mk_attributes ?service_name ?attrs () in
let resource = Proto.Resource.make_resource ~attributes () in
let resource = mk_resources ?service_name ?attrs () in
Proto.Metrics.make_resource_metrics ~scope_metrics:[ lm ] ~resource ()

View file

@ -1,3 +1,26 @@
(rule
(target .git_index_path)
(deps (universe) gen_git_index_path.sh)
(action
(with-stdout-to
.git_index_path
(run sh gen_git_index_path.sh))))
(rule
(target .git_index.lnk)
(action
(run ln -sf %{read-lines:.git_index_path} %{target})))
(rule
(target version.ml)
(deps
(file .git_index.lnk)
gen_version.sh)
(action
(with-stdout-to
version.ml
(run sh gen_version.sh))))
(library
(name opentelemetry)
(public_name opentelemetry)
@ -24,4 +47,5 @@
mtime
mtime.clock.os
pbrt
unix
threads))

View file

@ -0,0 +1,2 @@
#!/bin/sh
git rev-parse --git-path index 2>/dev/null || echo /dev/null

6
src/lib/gen_version.sh Normal file
View file

@ -0,0 +1,6 @@
#!/bin/sh
v=$(git describe --exact-match HEAD 2>/dev/null | sed 's/^v//')
[ -z "$v" ] && v=dev
h=$(git rev-parse HEAD 2>/dev/null)
[ -z "$h" ] && h=unknown
printf 'let version = "%s"\nlet git_hash = "%s"\n' "$v" "$h"

View file

@ -20,8 +20,11 @@ let service_instance_id = ref None
@since 0.12 *)
let service_version = ref None
(** @since NEXT_RELEASE *)
let sdk_version : string = Version.(spf "%s at %s" version git_hash)
let instrumentation_library =
make_instrumentation_scope ~version:"%%VERSION_NUM%%" ~name:"ocaml-otel" ()
make_instrumentation_scope ~version:sdk_version ~name:"opentelemetry" ()
(** Global attributes, initially set via OTEL_RESOURCE_ATTRIBUTES and modifiable
by the user code. They will be attached to each outgoing metrics/traces. *)
@ -58,6 +61,15 @@ open struct
]
let runtime_attributes_converted = List.map Key_value.conv runtime_attributes
let sdk_attributes =
[
"telemetry.sdk.language", `String "ocaml";
"telemetry.sdk.name", `String "opentelemetry";
"telemetry.sdk.version", `String sdk_version;
]
let sdk_attributes_converted = List.map Key_value.conv sdk_attributes
end
(** Attributes about the OCaml runtime. See
@ -65,9 +77,12 @@ end
*)
let[@inline] get_runtime_attributes () = runtime_attributes
(** Build main global attributes, to be used in resource attributes (shared by
all spans/metrics/logs in each outgoing batch) *)
let mk_attributes ?(service_name = !service_name) ?(attrs = []) () : _ list =
let l = List.rev_map Key_value.conv attrs in
let l = List.rev_append runtime_attributes_converted l in
let l = List.rev_append sdk_attributes_converted l in
let l =
make_key_value ~key:Conventions.Attributes.Service.name
~value:(String_value service_name) ()

View file

@ -100,6 +100,7 @@ type key_value = Key_value.t
(** {2 Global settings} *)
module Globals = Globals
module Version = Version
(** {2 Traces and Spans} *)

View file

@ -90,9 +90,35 @@ let set ?(traces = Provider_config.default) ?(metrics = Provider_config.default)
Log_provider.set logger
let self_metrics () : Metrics.t list =
let now = Clock.now_main () in
let emitter_metrics =
Emitter.self_metrics (Trace_provider.get ()).emit ~now
@ Emitter.self_metrics (Meter_provider.get ()).emit ~now
@ Emitter.self_metrics (Log_provider.get ()).emit ~now
in
match get () with
| None -> []
| Some exp -> exp.Exporter.self_metrics ()
| None -> emitter_metrics
| Some exp -> exp.Exporter.self_metrics () @ emitter_metrics
open struct
let self_metrics_enabled = Atomic.make false
end
(** Regularly emit metrics about the OTEL SDK. Idempotent. *)
let setup_self_metrics () =
if not (Atomic.exchange self_metrics_enabled true) then (
Self_debug.log Info (fun () -> "enabling self metrics");
let interval_limiter =
Interval_limiter.create ~min_interval:Mtime.Span.(10 * s) ()
in
let on_tick () =
if Interval_limiter.make_attempt interval_limiter then (
let ms = self_metrics () in
Meter_provider.emit_l ms
)
in
Globals.add_on_tick_callback on_tick
)
(* Permanent tick callback to drive batch timeouts on provider emitters *)
let () =

7
src/lib/version.mli Normal file
View file

@ -0,0 +1,7 @@
val version : string
(** Version of the library, e.g. ["0.12"]. ["dev"] if not built from a release
tag. *)
val git_hash : string
(** Full git commit hash at build time, e.g. ["b92159c1..."]. ["unknown"] if git
was unavailable. *)

View file

@ -8,7 +8,7 @@ let test_config_printing () =
in
let expected =
"{ debug=false; log_level=info; sdk_disabled=false; self_trace=false;\n\
\ url_traces=\"http://localhost:4318/v1/traces\";\n\
\ self_metrics=false; url_traces=\"http://localhost:4318/v1/traces\";\n\
\ url_metrics=\"http://localhost:4318/v1/metrics\";\n\
\ url_logs=\"http://localhost:4318/v1/logs\"; headers=[]; headers_traces=[];\n\
\ headers_metrics=[]; headers_logs=[]; protocol=http/protobuf;\n\

View file

@ -7,8 +7,8 @@
scope_logs =
[{ scope =
Some(
{ name = "ocaml-otel";
version = "%%VERSION_NUM%%";
{ name = "opentelemetry";
version = "";
attributes = [];
dropped_attributes_count = 0 (* absent *);
});

View file

@ -42,6 +42,11 @@ let tests (signal_batches : Client.Resource_signal.t list) =
lr)
sl.log_records
in
Option.iter
(fun sc ->
Opentelemetry_proto.Common
.instrumentation_scope_set_version sc "")
sl.scope;
let sl = L.copy_scope_logs sl in
L.scope_logs_set_log_records sl masked_log_records;
sl)