ocaml-opentelemetry/src/core/exporter.ml
Simon Cruanes 841d58ab67
large refactor: split core library into many modules; change API design
follow more closely the official OTEL recommendations, and also try
to reduce global state.

- use a class type for `Exporter.t` (instead of 1st class module `backend`)
- have tracer, logger, metrics_emitter as explicit objects
- keep a `Main_exporter` to make migration easier, but discouraged
- add stdout_exporter and debug_exporter to opentelemetry.client
2026-01-20 00:15:09 -05:00

128 lines
3.8 KiB
OCaml

(** Exporter.
This is the pluggable component that actually sends signals to a OTEL
collector, or prints them, or saves them somewhere.
This is part of the SDK, not just the API, so most real implementations live
in their own library. *)
open Common_
open struct
module Proto = Opentelemetry_proto
end
(** Main exporter interface *)
class type t = object
method send_trace : Proto.Trace.span list -> unit
method send_metrics : Proto.Metrics.metric list -> unit
method send_logs : Proto.Logs.log_record list -> unit
method tick : unit -> unit
(** Should be called regularly for background processing, timeout checks, etc.
*)
method add_on_tick_callback : (unit -> unit) -> unit
(** Add the given of callback to the exporter when [tick()] is called. The
callback should be short and reentrant. Depending on the exporter's
implementation, it might be called from a thread that is not the one that
called [on_tick]. *)
method cleanup : on_done:(unit -> unit) -> unit -> unit
(** [cleanup ~on_done ()] is called when the exporter is shut down, and is
responsible for sending remaining batches, flushing sockets, etc.
@param on_done
callback invoked after the cleanup is done. @since 0.12 *)
end
(** Dummy exporter, does nothing *)
let dummy : t =
let ticker = Tick_callbacks.create () in
object
method send_trace = ignore
method send_metrics = ignore
method send_logs = ignore
method tick () = Tick_callbacks.tick ticker
method add_on_tick_callback cb = Tick_callbacks.on_tick ticker cb
method cleanup ~on_done () = on_done ()
end
let[@inline] send_trace (self : #t) (l : Proto.Trace.span list) =
self#send_trace l
let[@inline] send_metrics (self : #t) (l : Proto.Metrics.metric list) =
self#send_metrics l
let[@inline] send_logs (self : #t) (l : Proto.Logs.log_record list) =
self#send_logs l
let[@inline] on_tick (self : #t) f = self#add_on_tick_callback f
(** Do background work. Call this regularly if the collector doesn't already
have a ticker thread or internal timer. *)
let[@inline] tick (self : #t) = self#tick ()
let[@inline] cleanup (self : #t) ~on_done : unit = self#cleanup ~on_done ()
(** Main exporter, used by the main tracing functions.
It is better to pass an explicit exporter when possible. *)
module Main_exporter = struct
(* hidden *)
open struct
(* a list of callbacks automatically added to the main exporter *)
let on_tick_cbs_ = AList.make ()
let exporter : t option Atomic.t = Atomic.make None
end
(** Set the global exporter *)
let set (exp : t) : unit =
List.iter exp#add_on_tick_callback (AList.get on_tick_cbs_);
Atomic.set exporter (Some exp)
(** Remove current exporter, if any.
@param on_done see {!t#cleanup}, @since 0.12 *)
let remove ~on_done () : unit =
match Atomic.exchange exporter None with
| None -> ()
| Some exp ->
exp#tick ();
cleanup exp ~on_done
(** Is there a configured exporter? *)
let present () : bool = Option.is_some (Atomic.get exporter)
(** Current exporter, if any *)
let[@inline] get () : t option = Atomic.get exporter
let add_on_tick_callback f =
AList.add on_tick_cbs_ f;
Option.iter (fun exp -> exp#add_on_tick_callback f) (get ())
end
let set_backend = Main_exporter.set [@@deprecated "use `Main_exporter.set`"]
let remove_backend = Main_exporter.remove
[@@deprecated "use `Main_exporter.remove`"]
let has_backend = Main_exporter.present
[@@deprecated "use `Main_exporter.present`"]
let get_backend = Main_exporter.get [@@deprecated "use `Main_exporter.ge"]
let with_setup_debug_backend ?(on_done = ignore) (exp : #t) ?(enable = true) ()
f =
let exp = (exp :> t) in
if enable then (
set_backend exp;
Fun.protect ~finally:(fun () -> cleanup exp ~on_done) f
) else
f ()