refator core OTEL: remove Scope, directly use Span as builder

now that fields are mutable, it's cheaper and easier
This commit is contained in:
Simon Cruanes 2025-12-03 16:06:20 -05:00
parent cb4be48746
commit bd335ecadd
No known key found for this signature in database
GPG key ID: EBFFF6F283F3A2B4
8 changed files with 175 additions and 247 deletions

View file

@ -6,3 +6,6 @@ let conv (k, v) =
let open Proto.Common in
let value = Value.conv v in
make_key_value ~key:k ?value ()
let of_otel (kv : Proto.Common.key_value) : t =
kv.key, Value.of_otel_opt kv.value

View file

@ -1,7 +1,5 @@
(** Opentelemetry types and instrumentation *)
open Common_
module Rand_bytes = Rand_bytes
(** Generation of random identifiers. *)
@ -29,9 +27,6 @@ module Timestamp_ns = Timestamp_ns
module Exporter = Exporter
module Collector = Exporter [@@deprecated "Use 'Exporter' instead"]
module Tick_callbacks = Tick_callbacks
(** Helper to implement part of the exporter *)
(** {2 Identifiers} *)
module Trace_id = Trace_id
@ -63,10 +58,6 @@ module Span_link = Span_link
module Span_status = Span_status
module Span_kind = Span_kind
(** {2 Scopes} *)
module Scope = Scope
(** {2 Traces} *)
module Span = Span

View file

@ -1,131 +0,0 @@
open Common_
type item_list =
| Nil
| Ev of Event.t * item_list
| Attr of Key_value.t * item_list
| Span_link of Span_link.t * item_list
| Span_status of Span_status.t * item_list
| Span_kind of Span_kind.t * item_list
type t = {
trace_id: Trace_id.t;
span_id: Span_id.t;
mutable items: item_list;
}
let attrs scope =
let rec loop acc = function
| Nil -> acc
| Attr (attr, l) -> loop (attr :: acc) l
| Ev (_, l) | Span_kind (_, l) | Span_link (_, l) | Span_status (_, l) ->
loop acc l
in
loop [] scope.items
let events scope =
let rec loop acc = function
| Nil -> acc
| Ev (event, l) -> loop (event :: acc) l
| Attr (_, l) | Span_kind (_, l) | Span_link (_, l) | Span_status (_, l) ->
loop acc l
in
loop [] scope.items
let links scope =
let rec loop acc = function
| Nil -> acc
| Span_link (span_link, l) -> loop (span_link :: acc) l
| Ev (_, l) | Span_kind (_, l) | Attr (_, l) | Span_status (_, l) ->
loop acc l
in
loop [] scope.items
let status scope =
let rec loop = function
| Nil -> None
| Span_status (status, _) -> Some status
| Ev (_, l) | Attr (_, l) | Span_kind (_, l) | Span_link (_, l) -> loop l
in
loop scope.items
let kind scope =
let rec loop = function
| Nil -> None
| Span_kind (k, _) -> Some k
| Ev (_, l) | Span_status (_, l) | Attr (_, l) | Span_link (_, l) -> loop l
in
loop scope.items
let make ~trace_id ~span_id ?(events = []) ?(attrs = []) ?(links = []) ?status
() : t =
let items =
let items =
match status with
| None -> Nil
| Some status -> Span_status (status, Nil)
in
let items = List.fold_left (fun acc ev -> Ev (ev, acc)) items events in
let items = List.fold_left (fun acc attr -> Attr (attr, acc)) items attrs in
List.fold_left (fun acc link -> Span_link (link, acc)) items links
in
{ trace_id; span_id; items }
let[@inline] to_span_link ?trace_state ?attrs ?dropped_attributes_count
(self : t) : Span_link.t =
Span_link.make ?trace_state ?attrs ?dropped_attributes_count
~trace_id:self.trace_id ~span_id:self.span_id ()
let[@inline] to_span_ctx (self : t) : Span_ctx.t =
Span_ctx.make ~trace_id:self.trace_id ~parent_id:self.span_id ()
open struct
let[@inline] is_not_dummy (self : t) : bool = Span_id.is_valid self.span_id
end
let[@inline] add_event (self : t) (ev : unit -> Event.t) : unit =
if is_not_dummy self then self.items <- Ev (ev (), self.items)
let[@inline] record_exception (self : t) (exn : exn)
(bt : Printexc.raw_backtrace) : unit =
if is_not_dummy self then (
let ev =
Event.make "exception"
~attrs:
[
"exception.message", `String (Printexc.to_string exn);
"exception.type", `String (Printexc.exn_slot_name exn);
( "exception.stacktrace",
`String (Printexc.raw_backtrace_to_string bt) );
]
in
self.items <- Ev (ev, self.items)
)
let[@inline] add_attrs (self : t) (attrs : unit -> Key_value.t list) : unit =
if is_not_dummy self then
self.items <-
List.fold_left (fun acc attr -> Attr (attr, acc)) self.items (attrs ())
let[@inline] add_links (self : t) (links : unit -> Span_link.t list) : unit =
if is_not_dummy self then
self.items <-
List.fold_left
(fun acc link -> Span_link (link, acc))
self.items (links ())
let set_status (self : t) (status : Span_status.t) : unit =
if is_not_dummy self then self.items <- Span_status (status, self.items)
let set_kind (self : t) (k : Span_kind.t) : unit =
if is_not_dummy self then self.items <- Span_kind (k, self.items)
let ambient_scope_key : t Ambient_context.key = Ambient_context.create_key ()
let get_ambient_scope ?scope () : t option =
match scope with
| Some _ -> scope
| None -> Ambient_context.get ambient_scope_key
let[@inline] with_ambient_scope (sc : t) (f : unit -> 'a) : 'a =
Ambient_context.with_binding ambient_scope_key sc (fun _ -> f ())

View file

@ -1,89 +0,0 @@
(** Scopes.
A scope is a trace ID and the span ID of the currently active span. *)
open Common_
type item_list
type t = {
trace_id: Trace_id.t;
span_id: Span_id.t;
mutable items: item_list;
}
val attrs : t -> Key_value.t list
val events : t -> Event.t list
val links : t -> Span_link.t list
val status : t -> Span_status.t option
val kind : t -> Span_kind.t option
val make :
trace_id:Trace_id.t ->
span_id:Span_id.t ->
?events:Event.t list ->
?attrs:Key_value.t list ->
?links:Span_link.t list ->
?status:Span_status.t ->
unit ->
t
val to_span_link :
?trace_state:string ->
?attrs:Key_value.t list ->
?dropped_attributes_count:int ->
t ->
Span_link.t
(** Turn the scope into a span link *)
val to_span_ctx : t -> Span_ctx.t
(** Turn the scope into a span context *)
val add_event : t -> (unit -> Event.t) -> unit
(** Add an event to the scope. It will be aggregated into the span.
Note that this takes a function that produces an event, and will only call
it if there is an instrumentation backend. *)
val record_exception : t -> exn -> Printexc.raw_backtrace -> unit
val add_attrs : t -> (unit -> Key_value.t list) -> unit
(** Add attributes to the scope. It will be aggregated into the span.
Note that this takes a function that produces attributes, and will only call
it if there is an instrumentation backend. *)
val add_links : t -> (unit -> Span_link.t list) -> unit
(** Add links to the scope. It will be aggregated into the span.
Note that this takes a function that produces links, and will only call it
if there is an instrumentation backend. *)
val set_status : t -> Span_status.t -> unit
(** set the span status.
Note that this function will be called only if there is an instrumentation
backend. *)
val set_kind : t -> Span_kind.t -> unit
(** Set the span's kind.
@since 0.11 *)
val ambient_scope_key : t Ambient_context.key
(** The opaque key necessary to access/set the ambient scope with
{!Ambient_context}. *)
val get_ambient_scope : ?scope:t -> unit -> t option
(** Obtain current scope from {!Ambient_context}, if available. *)
val with_ambient_scope : t -> (unit -> 'a) -> 'a
(** [with_ambient_scope sc thunk] calls [thunk()] in a context where [sc] is the
(thread|continuation)-local scope, then reverts to the previous local scope,
if any.
@see <https://github.com/ELLIOTTCABLE/ocaml-ambient-context>
ambient-context docs *)

View file

@ -22,11 +22,15 @@ type key_value =
| `None
]
let id self = Span_id.of_bytes self.span_id
let[@inline] id self = Span_id.of_bytes self.span_id
let create ?(kind = !Globals.default_span_kind) ?(id = Span_id.create ())
?trace_state ?(attrs = []) ?(events = []) ?status ~trace_id ?parent
?(links = []) ~start_time ~end_time name : t * id =
let[@inline] trace_id self = Trace_id.of_bytes self.trace_id
let[@inline] is_not_dummy self = Span_id.is_valid (id self)
let make ?(kind = !Globals.default_span_kind) ?trace_state ?(attrs = [])
?(events = []) ?status ~trace_id ~id ?parent ?(links = []) ~start_time
~end_time name : t =
let trace_id = Trace_id.to_bytes trace_id in
let parent_span_id = Option.map Span_id.to_bytes parent in
let attributes = List.map Key_value.conv attrs in
@ -35,4 +39,85 @@ let create ?(kind = !Globals.default_span_kind) ?(id = Span_id.create ())
~attributes ~events ?trace_state ?status ~kind ~name ~links
~start_time_unix_nano:start_time ~end_time_unix_nano:end_time ()
in
span, id
span
let create_new ?kind ?(id = Span_id.create ()) ?trace_state ?attrs ?events
?status ~trace_id ?parent ?links ~start_time ~end_time name : t =
make ?kind ~id ~trace_id ?trace_state ?attrs ?events ?status ?parent ?links
~start_time ~end_time name
let attrs self = self.attributes |> List.rev_map Key_value.of_otel
let events self = self.events
let links self : Span_link.t list = self.links
let status self = self.status
let kind self =
let k = self.kind in
if k = Span_kind_unspecified then
None
else
Some k
let to_span_link (self : t) : Span_link.t =
make_span_link ~attributes:self.attributes
?dropped_attributes_count:
(if span_has_dropped_attributes_count self then
Some self.dropped_attributes_count
else
None)
?trace_state:
(if span_has_trace_state self then
Some self.trace_state
else
None)
~trace_id:self.trace_id ~span_id:self.span_id ()
let[@inline] to_span_ctx (self : t) : Span_ctx.t =
Span_ctx.make ~trace_id:(trace_id self) ~parent_id:(id self) ()
let[@inline] add_event self ev : unit =
if is_not_dummy self then span_set_events self (ev :: self.events)
let add_event' self ev : unit = if is_not_dummy self then add_event self (ev ())
let record_exception (self : t) (exn : exn) (bt : Printexc.raw_backtrace) : unit
=
if is_not_dummy self then (
let ev =
Event.make "exception"
~attrs:
[
"exception.message", `String (Printexc.to_string exn);
"exception.type", `String (Printexc.exn_slot_name exn);
( "exception.stacktrace",
`String (Printexc.raw_backtrace_to_string bt) );
]
in
add_event self ev
)
let[@inline] add_attrs (self : t) (attrs : unit -> Key_value.t list) : unit =
if is_not_dummy self then (
let attrs = List.rev_map Key_value.conv (attrs ()) in
let attrs = List.rev_append attrs self.attributes in
span_set_attributes self attrs
)
let add_links (self : t) (links : Span_link.t list) : unit =
if links <> [] then (
let links = List.rev_append links self.links in
span_set_links self links
)
let add_links' (self : t) (links : unit -> Span_link.t list) : unit =
if is_not_dummy self then (
let links = List.rev_append (links ()) self.links in
span_set_links self links
)
let set_status = span_set_status
let set_kind = span_set_kind

View file

@ -24,9 +24,30 @@ val id : t -> Span_id.t
type key_value = Key_value.t
val create :
val make :
?kind:kind ->
?id:id ->
?trace_state:string ->
?attrs:key_value list ->
?events:Event.t list ->
?status:status ->
trace_id:Trace_id.t ->
id:Span_id.t ->
?parent:id ->
?links:Span_link.t list ->
start_time:Timestamp_ns.t ->
end_time:Timestamp_ns.t ->
string ->
t
(** [make ~trace_id ~id name] creates a new span
@param trace_id the trace this belongs to
@param parent parent span, if any
@param links
list of links to other spans, each with their trace state (see
{{:https://www.w3.org/TR/trace-context/#tracestate-header} w3.org}) *)
val create_new :
?kind:kind ->
?id:Span_id.t ->
?trace_state:string ->
?attrs:key_value list ->
?events:Event.t list ->
@ -37,10 +58,50 @@ val create :
start_time:Timestamp_ns.t ->
end_time:Timestamp_ns.t ->
string ->
t * id
(** [create ~trace_id name] creates a new span with its unique ID.
@param trace_id the trace this belongs to
@param parent parent span, if any
@param links
list of links to other spans, each with their trace state (see
{{:https://www.w3.org/TR/trace-context/#tracestate-header} w3.org}) *)
t
val attrs : t -> Key_value.t list
val events : t -> Event.t list
val links : t -> Span_link.t list
val status : t -> Span_status.t option
val kind : t -> Span_kind.t option
val to_span_link : t -> Span_link.t
(** Turn the scope into a span link *)
val to_span_ctx : t -> Span_ctx.t
(** Turn the scope into a span context *)
val add_event : t -> Event.t -> unit
val add_event' : t -> (unit -> Event.t) -> unit
(** Add an event to the scope. It will be aggregated into the span.
Note that this takes a function that produces an event, and will only call
it if there is an instrumentation backend. *)
val record_exception : t -> exn -> Printexc.raw_backtrace -> unit
val add_links : t -> Span_link.t list -> unit
val add_links' : t -> (unit -> Span_link.t list) -> unit
(** Add links to the scope. It will be aggregated into the span.
Note that this takes a function that produces links, and will only call it
if there is an instrumentation backend. *)
val add_attrs : t -> (unit -> Key_value.t list) -> unit
val set_status : t -> Span_status.t -> unit
(** set the span status.
Note that this function will be called only if there is an instrumentation
backend. *)
val set_kind : t -> Span_kind.t -> unit
(** Set the span's kind.
@since 0.11 *)

View file

@ -49,11 +49,11 @@ let simple_main_exporter : t =
{b NOTE} be careful not to call this inside a Gc alarm, as it can cause
deadlocks. *)
let emit ?service_name:_ ?attrs:_ (spans : span list) : unit =
let (emit [@deprecated "use an explicit tracer"]) =
fun ?service_name:_ ?attrs:_ (spans : span list) : unit ->
match Exporter.Main_exporter.get () with
| None -> ()
| Some exp -> exp#send_trace spans
[@@deprecated "use an explicit tracer"]
(* TODO: remove scope, use span directly *)
type scope = Scope.t = {
@ -113,11 +113,11 @@ let with_' ?(tracer = simple_main_exporter) ?(force_new_trace_id = false)
(make_status ~code:Status_code_error ~message:(Printexc.to_string e)
()))
in
let span, _ =
let span =
(* TODO: should the attrs passed to with_ go on the Span
(in Span.create) or on the ResourceSpan (in emit)?
(question also applies to Opentelemetry_lwt.Trace.with) *)
Span.create ?kind ~trace_id ?parent ~links:(Scope.links scope) ~id:span_id
Span.make ?kind ~trace_id ?parent ~links:(Scope.links scope) ~id:span_id
?trace_state ~attrs:(Scope.attrs scope) ~events:(Scope.events scope)
~start_time
~end_time:(Timestamp_ns.now_unix_ns ())

View file

@ -17,3 +17,11 @@ let conv =
| `Bool b -> Some (Bool_value b)
| `Float f -> Some (Double_value f)
| `None -> None
let of_otel_opt (v : Proto.Common.any_value option) : t =
match v with
| Some (Int_value i) -> `Int (Int64.to_int i)
| Some (String_value s) -> `String s
| Some (Bool_value b) -> `Bool b
| Some (Double_value f) -> `Float f
| Some (Array_value _ | Kvlist_value _ | Bytes_value _) | None -> `None