otel.trace: have a single collector that always use current *_provider

just use the current Trace_provider.() (resp Log, Metrics) to emit a
span (resp log, metric). Simpler, and we rely on a global exporter
anyway.
This commit is contained in:
Simon Cruanes 2026-03-03 15:15:24 -05:00
parent fa14ddf1f8
commit 478fe1da7b
No known key found for this signature in database
GPG key ID: EBFFF6F283F3A2B4
2 changed files with 58 additions and 86 deletions

View file

@ -21,15 +21,27 @@ end
open Extensions
open struct
type state = {
clock: Opentelemetry_core.Clock.t;
exporter: OTEL.Exporter.t;
}
module Ambient_span_provider_ = struct
let get_current_span () =
match OTEL.Ambient_span.get () with
| None -> None
| Some sp -> Some (Span_otel sp)
let create_state ~(exporter : OTEL.Exporter.t) () : state =
let clock = OTEL.Clock.ptime_clock in
{ clock; exporter }
let with_current_span_set_to () span f =
match span with
| Span_otel sp -> OTEL.Ambient_span.with_ambient sp (fun () -> f span)
| _ -> f span
let callbacks : unit Trace.Ambient_span_provider.Callbacks.t =
{ get_current_span; with_current_span_set_to }
let provider = Trace.Ambient_span_provider.ASP_some ((), callbacks)
end
let ambient_span_provider = Ambient_span_provider_.provider
open struct
type state = unit
(* sanity check: otrace meta-map must be the same as hmap *)
let () = ignore (fun (k : _ Hmap.key) : _ Ambient_context.Context.key -> k)
@ -38,9 +50,9 @@ open struct
let k_span_ctx : OTEL.Span_ctx.t Ambient_context.Context.key =
OTEL.Span_ctx.k_ambient
let enter_span (self : state) ~__FUNCTION__ ~__FILE__ ~__LINE__ ~level:_
~params:_ ~(data : (_ * Trace.user_data) list) ~parent name : Trace.span =
let start_time = OTEL.Clock.now self.clock in
let enter_span () ~__FUNCTION__ ~__FILE__ ~__LINE__ ~level:_ ~params:_
~(data : (_ * Trace.user_data) list) ~parent name : Trace.span =
let start_time = OTEL.Clock.now_main () in
let trace_id, parent_id =
match parent with
| Trace.P_some (Span_otel sp) ->
@ -93,13 +105,15 @@ open struct
Span_otel otel_sp
let exit_span (self : state) sp =
let exit_span () sp =
match sp with
| Span_otel span when OTEL.Span.is_not_dummy span ->
(* emit the span after setting the end timestamp *)
let end_time = OTEL.Clock.now self.clock in
let end_time = OTEL.Clock.now_main () in
OTEL.Proto.Trace.span_set_end_time_unix_nano span end_time;
self.exporter.OTEL.Exporter.export (OTEL.Any_signal_l.Spans [ span ])
(* use the current tracer *)
OTEL.Trace_provider.emit span
| _ -> ()
let add_data_to_span _self span (data : (_ * Trace.user_data) list) =
@ -116,9 +130,9 @@ open struct
| Info -> OTEL.Log_record.Severity_number_info
| Warning -> OTEL.Log_record.Severity_number_warn
let message (self : state) ~(level : Trace_core.Level.t) ~params:_ ~data ~span
msg : unit =
let observed_time_unix_nano = OTEL.Clock.now self.clock in
let message () ~(level : Trace_core.Level.t) ~params:_ ~data ~span msg : unit
=
let observed_time_unix_nano = OTEL.Clock.now_main () in
let trace_id, span_id =
match span with
| Some (Span_otel sp) ->
@ -135,10 +149,10 @@ open struct
OTEL.Log_record.make ~severity ?trace_id ?span_id ~attrs:data
~observed_time_unix_nano (`String msg)
in
self.exporter.OTEL.Exporter.export (OTEL.Any_signal_l.Logs [ log ])
OTEL.Log_provider.emit log
let metric (self : state) ~level:_ ~params:_ ~data:attrs name v : unit =
let now = OTEL.Clock.now self.clock in
let metric () ~level:_ ~params:_ ~data:attrs name v : unit =
let now = OTEL.Clock.now_main () in
let kind =
let open Trace_core.Core_ext in
match v with
@ -157,8 +171,7 @@ open struct
| `sum v -> [ OTEL.Metrics.sum ~name [ v ] ]
| `hist h -> [ OTEL.Metrics.histogram ~name [ h ] ]
in
if m <> [] then
self.exporter.OTEL.Exporter.export (OTEL.Any_signal_l.Metrics m)
if m <> [] then OTEL.Emitter.emit (OTEL.Meter_provider.get ()).emit m
let extension (_self : state) ~level:_ ev =
match ev with
@ -174,35 +187,17 @@ open struct
| Ev_record_exn _ -> ()
| _ -> ()
let shutdown self = OTEL.Exporter.shutdown self.exporter
let init () = Trace.set_ambient_context_provider ambient_span_provider
let shutdown () = ()
let callbacks : state Trace.Collector.Callbacks.t =
Trace.Collector.Callbacks.make ~enter_span ~exit_span ~add_data_to_span
~message ~metric ~extension ~shutdown ()
~message ~metric ~extension ~init ~shutdown ()
end
module Ambient_span_provider_ = struct
let get_current_span () =
match OTEL.Ambient_span.get () with
| None -> None
| Some sp -> Some (Span_otel sp)
let with_current_span_set_to () span f =
match span with
| Span_otel sp -> OTEL.Ambient_span.with_ambient sp (fun () -> f span)
| _ -> f span
let callbacks : unit Trace.Ambient_span_provider.Callbacks.t =
{ get_current_span; with_current_span_set_to }
let provider = Trace.Ambient_span_provider.ASP_some ((), callbacks)
end
let ambient_span_provider = Ambient_span_provider_.provider
let collector_of_exporter (exporter : OTEL.Exporter.t) : Trace_core.collector =
let st = create_state ~exporter () in
Trace_core.Collector.C_some (st, callbacks)
let collector : Trace_core.collector =
Trace_core.Collector.C_some ((), callbacks)
let with_ambient_span (sp : Trace.span) f =
match sp with
@ -235,37 +230,11 @@ let record_exception sp exn bt : unit =
if Trace.enabled () then
Trace.extension_event @@ Ev_record_exn { sp; exn; bt }
(** Collector that forwards to the {b currently installed} OTEL exporter. *)
let collector_main_otel_exporter () : Trace.collector =
(* Create a dynamic exporter that forwards to the currently installed main
exporter at call time. *)
let dynamic_exp : OTEL.Exporter.t =
{
OTEL.Exporter.export =
(fun sig_ ->
match OTEL.Sdk.get () with
| None -> ()
| Some exp -> exp.OTEL.Exporter.export sig_);
active = (fun () -> Aswitch.dummy);
shutdown = ignore;
self_metrics = (fun () -> OTEL.Sdk.self_metrics ());
}
in
collector_of_exporter dynamic_exp
let (collector
[@deprecated "use collector_of_exporter or collector_main_otel_exporter"])
=
collector_main_otel_exporter
let setup () =
Trace.set_ambient_context_provider Ambient_span_provider_.provider;
Trace.setup_collector @@ collector_main_otel_exporter ()
let setup () = Trace.setup_collector collector
let setup_with_otel_exporter exp : unit =
let coll = collector_of_exporter exp in
OTEL.Sdk.set exp;
Trace.setup_collector coll
Trace.setup_collector collector
let setup_with_otel_backend = setup_with_otel_exporter

View file

@ -5,10 +5,10 @@
that use [ocaml-trace], and they will automatically emit OpenTelemetry spans
and logs.
Ambient_context is used to track the current ambient span.
[Ambient_context] is used to propagate the current span to child spans.
We use [Trace_core.extension_event] to add more features on top of the
common tracing interface. For example to set the "span kind":
[Trace_core.extension_event] is used to expose OTEL-specific features on top
of the common tracing interface, e.g. to set the span kind:
{[
let@ span = Trace_core.with_span ~__FILE__ ~__LINE__ "my-span" in
@ -44,23 +44,26 @@ module Extensions : sig
end
val setup : unit -> unit
(** Install the OTEL backend as a Trace collector *)
(** Install the OTEL backend as a [Trace] collector. The trace collector will
use {!Trace_provider.get}, {!Log_provider.get}, and {!Meter_provider.get} to
get the current tracer, logger, meter and use that to emit signals.
This will not do much until a proper {!OTEL.Exporter.t} is installed via
{!OTEL.Sdk.set}. *)
val setup_with_otel_exporter : OTEL.Exporter.t -> unit
(** Same as {!setup}, but using the given exporter *)
(** Same as {!setup}, but also calls [OTEL.Sdk.set otel_exporter] *)
val setup_with_otel_backend : OTEL.Exporter.t -> unit
[@@deprecated "use setup_with_otel_exporter"]
val collector_of_exporter : OTEL.Exporter.t -> Trace_core.collector
val collector : unit -> Trace_core.collector
[@@deprecated "use collector_of_exporter, avoid global state"]
(** Make a Trace collector that uses the main OTEL backend to send spans and
logs *)
val collector : Trace_core.collector
(** Make a Trace collector that uses the main OTEL providers to emit traces,
metrics, and logs *)
val ambient_span_provider : Trace_core.Ambient_span_provider.t
(** Uses {!Ambient_context} to provide contextual spans in {!Trace_core}.*)
(** Uses {!Ambient_context} to provide contextual spans in {!Trace_core}. It is
automatically installed by the {!collector}. *)
val link_spans : Otrace.span -> Otrace.span -> unit
(** [link_spans sp1 sp2] modifies [sp1] by adding a span link to [sp2].