wip: trace

This commit is contained in:
Simon Cruanes 2025-12-03 16:06:43 -05:00
parent bd335ecadd
commit 2170c16e7f
No known key found for this signature in database
GPG key ID: EBFFF6F283F3A2B4
2 changed files with 33 additions and 196 deletions

View file

@ -50,57 +50,30 @@ end
open Conv
module Well_known = struct
let spankind_key = "otrace.spankind"
let internal = `String "INTERNAL"
let server = `String "SERVER"
let client = `String "CLIENT"
let producer = `String "PRODUCER"
let consumer = `String "CONSUMER"
let spankind_of_string =
let open Otel.Span in
function
| "INTERNAL" -> Span_kind_internal
| "SERVER" -> Span_kind_server
| "CLIENT" -> Span_kind_client
| "PRODUCER" -> Span_kind_producer
| "CONSUMER" -> Span_kind_consumer
| _ -> Span_kind_unspecified
let otel_attrs_of_otrace_data data =
let kind : Otel.Span.kind ref = ref Otel.Span.Span_kind_unspecified in
let data =
List.filter_map
(function
| name, `String v when name = "otrace.spankind" ->
kind := spankind_of_string v;
None
| x -> Some x)
data
in
!kind, data
(** Key to store an error [Otel.Span.status] with the message. Set
["otrace.error" = "mymsg"] in a span data to set the span's status to
[{message="mymsg"; code=Error}]. *)
let status_error_key = "otrace.error"
end
open Well_known
let on_internal_error =
ref (fun msg -> Printf.eprintf "error in Opentelemetry_trace: %s\n%!" msg)
module Extensions = struct
type Otrace.extension_event +=
| Ev_link_span of Otrace.explicit_span * Otrace.explicit_span
| Ev_set_span_kind of Otrace.explicit_span * Otel.Span_kind.t
| Ev_record_exn of Otrace.explicit_span * exn * Printexc.raw_backtrace
| Ev_set_span_kind of Otrace.explicit_span * Otel.Span_kind.t
end
open Extensions
module Span_tbl = Trace_subscriber.Span_tbl
(* TODO: subscriber
type state = {
foo: unit (* TODO: *)
}
module Callbacks
*)
let subscriber_of_exporter _ = assert false
let collector_of_exporter _ = assert false
module Internal = struct
type span_begin = {
@ -190,7 +163,8 @@ module Internal = struct
{ start_time; name; __FILE__; __LINE__; __FUNCTION__; scope; parent } =
let open Otel in
let end_time = Timestamp_ns.now_unix_ns () in
let kind, attrs = otel_attrs_of_otrace_data (Scope.attrs scope) in
let kind = Scope.kind scope in
let attrs = Scope.attrs scope in
let status : Span_status.t =
match List.assoc_opt Well_known.status_error_key attrs with

View file

@ -9,25 +9,12 @@
and implicit scope (in {!Internal.M.with_span}, via {!Ambient_context}) are
supported; see the detailed notes on {!Internal.M.enter_manual_span}.
{1:wellknown Well-known identifiers}
Because [ocaml-trace]'s API is a subset of OpenTelemetry functionality, this
interface allows for a few 'well-known' identifiers to be used in
[Trace]-instrumented libraries that wish to further support OpenTelemetry
usage.
(These strings will not change in subsequent versions of this library, so
you do not need to depend on [opentelemetry.trace] to use them.)
- If a key of exactly ["otrace.spankind"] is included in the
{!Trace_core.user_data} passed to [with_span] et al., it will be used as
the {!Opentelemetry.Span.kind} of the emitted span. (See
{!Internal.spankind_of_string} for the list of supported values.)
We use [Trace_core.extension_event] to add more features on top of the
common tracing interface. For example to set the "span kind":
{[
let describe () = [ Opentelemetry_trace.(spankind_key, client) ] in
Trace_core.with_span ~__FILE__ ~__LINE__ ~data:describe "my-span"
@@ fun _ ->
let@ span = Trace_core.with_span ~__FILE__ ~__LINE__ "my-span" in
Opentelemetry_trace.set_span_kind span Span_kind_client
(* ... *)
]} *)
@ -78,7 +65,12 @@ val setup_with_otel_exporter : #Opentelemetry.Exporter.t -> unit
val setup_with_otel_backend : #Opentelemetry.Exporter.t -> unit
[@@deprecated "use setup_with_otel_exporter"]
val subscriber_of_exporter : #Otel.Exporter.t -> Trace_subscriber.t
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 OTEL backend to send spans and logs *)
val link_spans : Otrace.explicit_span -> Otrace.explicit_span -> unit
@ -115,132 +107,3 @@ module Well_known : sig
Otel.Span.kind * Otel.Span.key_value list
end
[@@deprecated "use the regular functions for this"]
(**/**)
(** Internal implementation details; do not consider these stable. *)
module Internal : sig
module M : sig
val with_span :
__FUNCTION__:string option ->
__FILE__:string ->
__LINE__:int ->
data:(string * Otrace.user_data) list ->
string (* span name *) ->
(Otrace.span -> 'a) ->
'a
(** Implements {!Trace_core.Collector.S.with_span}, with the OpenTelemetry
collector as the backend. Invoked via {!Trace_core.with_span}.
Notably, this has the same implicit-scope semantics as
{!Opentelemetry.Trace.with_}, and requires configuration of
{!Ambient_context}.
@see <https://github.com/ELLIOTTCABLE/ocaml-ambient-context>
ambient-context docs *)
val enter_manual_span :
parent:Otrace.explicit_span_ctx option ->
flavor:'a ->
__FUNCTION__:string option ->
__FILE__:string ->
__LINE__:int ->
data:(string * Otrace.user_data) list ->
string (* span name *) ->
Otrace.explicit_span
(** Implements {!Trace_core.Collector.S.enter_manual_span}, with the
OpenTelemetry collector as the backend. Invoked at
{!Trace_core.enter_manual_toplevel_span} and
{!Trace_core.enter_manual_sub_span}; requires an eventual call to
{!Trace_core.exit_manual_span}.
These 'manual span' functions {e do not} implement the same implicit-
scope semantics of {!with_span}; and thus don't need to wrap a single
stack-frame / callback; you can freely enter a span at any point, store
the returned {!Trace_core.explicit_span}, and exit it at any later point
with {!Trace_core.exit_manual_span}.
However, for that same reason, they also cannot update the
{!Ambient_context} that is, when you invoke the various [manual]
functions, if you then invoke other functions that use
{!Trace_core.with_span}, those callees {e will not} see the span you
entered manually as their [parent].
Generally, the best practice is to only use these [manual] functions at
the 'leaves' of your callstack: that is, don't invoke user callbacks
from within them; or if you do, make sure to pass the [explicit_span]
you recieve from this function onwards to the user callback, so they can
create further child-spans. *)
val exit_manual_span : Otrace.explicit_span -> unit
(** Implements {!Trace_core.Collector.S.exit_manual_span}, with the
OpenTelemetry collector as the backend. Invoked at
{!Trace_core.exit_manual_span}. Expects the [explicit_span] returned
from an earlier call to {!Trace_core.enter_manual_toplevel_span} or
{!Trace_core.enter_manual_sub_span}.
(See the notes at {!enter_manual_span} about {!Ambient_context}.) *)
val add_data_to_span :
Otrace.span -> (string * Otrace.user_data) list -> unit
val add_data_to_manual_span :
Otrace.explicit_span -> (string * Otrace.user_data) list -> unit
val message :
?span:Otrace.span ->
data:(string * Otrace.user_data) list ->
string ->
unit
val shutdown : unit -> unit
val name_process : string -> unit
val name_thread : string -> unit
val counter_int :
data:(string * Otrace.user_data) list -> string -> int -> unit
val counter_float :
data:(string * Otrace.user_data) list -> string -> float -> unit
end
type span_begin = {
start_time: int64;
name: string;
__FILE__: string;
__LINE__: int;
__FUNCTION__: string option;
scope: Otel.Scope.t;
parent: Otel.Span_ctx.t option;
}
module Active_span_tbl : Hashtbl.S with type key = Otrace.span
(** Table indexed by ocaml-trace spans. *)
module Active_spans : sig
type t = private { tbl: span_begin Active_span_tbl.t } [@@unboxed]
val create : unit -> t
val k_tls : t TLS.t
val get : unit -> t
end
val otrace_of_otel : Otel.Span_id.t -> Otrace.span
val enter_span' :
?explicit_parent:Otrace.explicit_span_ctx ->
__FUNCTION__:string option ->
__FILE__:string ->
__LINE__:int ->
data:(string * Otrace.user_data) list ->
string ->
Otrace.span * span_begin
val exit_span' : Otrace.span -> span_begin -> Otel.Span.t
end
(**/**)