From a64565f1040d995b8bb7d8eab4dd1d8872393b73 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 1 Aug 2023 21:03:58 +0000 Subject: [PATCH 01/25] store: Extract thread_local, abstract for lwt/eio --- dune-project | 2 + opentelemetry-lwt.opam | 1 + opentelemetry.opam | 1 + src/dune | 2 +- src/opentelemetry.ml | 8 +-- src/thread_local.ml | 100 ------------------------------ src/thread_local.mli | 27 --------- src/trace/dune | 2 +- src/trace/opentelemetry_trace.ml | 101 +++++++------------------------ 9 files changed, 30 insertions(+), 214 deletions(-) delete mode 100644 src/thread_local.ml delete mode 100644 src/thread_local.mli diff --git a/dune-project b/dune-project index 6e948d02..14d68d3f 100644 --- a/dune-project +++ b/dune-project @@ -17,6 +17,7 @@ (depends (ocaml (>= "4.08")) ptime + ambient-context (odoc :with-doc) (pbrt (>= 2.3))) (depopts @@ -29,6 +30,7 @@ (synopsis "Lwt-compatible instrumentation for https://opentelemetry.io") (depends (ocaml (>= "4.08")) + ambient-context (opentelemetry (= :version)) (cohttp-lwt-unix :with-test) (odoc :with-doc) diff --git a/opentelemetry-lwt.opam b/opentelemetry-lwt.opam index 9740df2a..dc5f90d5 100644 --- a/opentelemetry-lwt.opam +++ b/opentelemetry-lwt.opam @@ -11,6 +11,7 @@ bug-reports: "https://github.com/imandra-ai/ocaml-opentelemetry/issues" depends: [ "dune" {>= "2.7"} "ocaml" {>= "4.08"} + "ambient-context" "opentelemetry" {= version} "cohttp-lwt-unix" {with-test} "odoc" {with-doc} diff --git a/opentelemetry.opam b/opentelemetry.opam index d7cd5e08..e13b4a10 100644 --- a/opentelemetry.opam +++ b/opentelemetry.opam @@ -12,6 +12,7 @@ depends: [ "dune" {>= "2.7"} "ocaml" {>= "4.08"} "ptime" + "ambient-context" "odoc" {with-doc} "pbrt" {>= "2.3"} ] diff --git a/src/dune b/src/dune index ea4b4070..8faa01ee 100644 --- a/src/dune +++ b/src/dune @@ -2,7 +2,7 @@ (name opentelemetry) (synopsis "API for opentelemetry instrumentation") (flags :standard -warn-error -a+8) - (libraries ptime ptime.clock.os pbrt threads opentelemetry.atomic) + (libraries ambient-context ptime ptime.clock.os pbrt threads opentelemetry.atomic) (public_name opentelemetry)) ; ### protobuf rules ### diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index 38dc60e4..60d374fa 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -1,7 +1,5 @@ (** Opentelemetry types and instrumentation *) -module Thread_local = Thread_local - module Lock = Lock (** Global lock. *) @@ -522,7 +520,7 @@ module Scope = struct (**/**) - let _global_scope : t Thread_local.t = Thread_local.create () + let _ambient_scope : t Ambient_context.key = Ambient_context.create_key () (**/**) @@ -530,12 +528,12 @@ module Scope = struct let get_surrounding ?scope () : t option = match scope with | Some _ -> scope - | None -> Thread_local.get _global_scope + | None -> Ambient_context.get _ambient_scope (** [with_scope sc f] calls [f()] in a context where [sc] is the (thread)-local scope, then reverts to the previous local scope, if any. *) let[@inline] with_scope (sc : t) (f : unit -> 'a) : 'a = - Thread_local.with_ _global_scope sc (fun _ -> f ()) + Ambient_context.with_binding _ambient_scope sc (fun _ -> f ()) end open struct diff --git a/src/thread_local.ml b/src/thread_local.ml deleted file mode 100644 index 4c4eadfb..00000000 --- a/src/thread_local.ml +++ /dev/null @@ -1,100 +0,0 @@ -module A = Opentelemetry_atomic.Atomic - -type key = int - -let[@inline] get_key_ () : key = Thread.id (Thread.self ()) - -module Key_map_ = Map.Make (struct - type t = key - - let compare : t -> t -> int = compare -end) - -type 'a t = 'a ref Key_map_.t A.t -(** The TLS variable is made of a global atomic reference - (which has very low contention: it's modified only when a - thread is started/stopped). - - Inside that atomic variable, is a map from thread ID to a mutable [ref] - holding the actual data. Because this [ref] is only ever accessed - by the thread with this given ID, it's safe to modify. *) - -let create () : _ t = A.make Key_map_.empty - -let[@inline] get_exn (self : _ t) = - let m = A.get self in - let key = get_key_ () in - !(Key_map_.find key m) - -let[@inline] get self = try Some (get_exn self) with Not_found -> None - -(* remove reference for the key *) -let remove_ref_ self key : unit = - while - let m = A.get self in - let m' = Key_map_.remove key m in - not (A.compare_and_set self m m') - do - Thread.yield () - done - -let set_ref_ self key (r : _ ref) : unit = - while - let m = A.get self in - let m' = Key_map_.add key r m in - not (A.compare_and_set self m m') - do - Thread.yield () - done - -(* get or associate a reference to [key], and return it. - Also return a function to remove the reference if we just created it. *) -let get_or_create_ref_ (self : _ t) key ~v : _ ref * _ option = - try - let r = Key_map_.find key (A.get self) in - let old = !r in - r := v; - r, Some old - with Not_found -> - let r = ref v in - set_ref_ self key r; - r, None - -let set (self : _ t) v : unit = - let key = get_key_ () in - let _, _ = get_or_create_ref_ self key ~v in - () - -let remove (self : _ t) : unit = - let key = get_key_ () in - remove_ref_ self key - -let get_or_create ~create (self : 'a t) : 'a = - let key = get_key_ () in - try - let r = Key_map_.find key (A.get self) in - !r - with Not_found -> - Gc.finalise (fun _ -> remove_ref_ self key) (Thread.self ()); - let v = create () in - let r = ref v in - set_ref_ self key r; - v - -let with_ self v f = - let key = get_key_ () in - let r, old = get_or_create_ref_ self key ~v in - - let restore_ () : unit = - match old with - | None -> remove_ref_ self key - | Some old -> r := old - in - - try - let res = f old in - restore_ (); - res - with e -> - restore_ (); - raise e diff --git a/src/thread_local.mli b/src/thread_local.mli deleted file mode 100644 index 7a33b709..00000000 --- a/src/thread_local.mli +++ /dev/null @@ -1,27 +0,0 @@ -(** Thread/Domain local storage - - This allows the creation of global state that is per-domain or per-thread. -*) - -type 'a t - -val create : unit -> 'a t -(** Create new storage *) - -val get : 'a t -> 'a option -(** Get current value *) - -val get_exn : 'a t -> 'a -(** Like {!get} but fails with an exception - @raise Not_found if no value was found *) - -val set : 'a t -> 'a -> unit - -val remove : _ t -> unit - -val get_or_create : create:(unit -> 'a) -> 'a t -> 'a - -val with_ : 'a t -> 'a -> ('a option -> 'b) -> 'b -(** [with_ var x f] sets [var] to [x] for this thread, calls [f prev] where - [prev] is the value currently in [var] (if any), and - then restores the old value of [var] for this thread. *) diff --git a/src/trace/dune b/src/trace/dune index 8391f103..69d9e44f 100644 --- a/src/trace/dune +++ b/src/trace/dune @@ -4,4 +4,4 @@ (public_name opentelemetry.trace) (synopsis "Use opentelemetry as a collector for trace") (optional) - (libraries trace opentelemetry)) + (libraries ambient-context trace opentelemetry)) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 2670429c..5f0d2c24 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -1,38 +1,4 @@ module Otel = Opentelemetry -module TLS = Otel.Thread_local - -type span = Trace.span - -(** Table indexed by Trace spans *) -module Span_tbl = Hashtbl.Make (struct - include Int64 - - let hash : t -> int = Hashtbl.hash -end) - -(** Per-thread set of active spans. *) -module Active_spans = struct - type span_begin = { - span_id: Otel.Span_id.t; - start_time: int64; - name: string; - data: (string * Trace.user_data) list; - __FILE__: string; - __LINE__: int; - new_scope: Otel.Scope.t; - old_scope: Otel.Scope.t option; - } - (** Information we get at the beginning of the span *) - - type t = { tbl: span_begin Span_tbl.t } [@@unboxed] - (** Storage for active spans *) - - let create () : t = { tbl = Span_tbl.create 8 } - - let tls : t TLS.t = TLS.create () - - let[@inline] get () : t = TLS.get_or_create tls ~create -end let conv_span_to_i64 (id : Otel.Span_id.t) : int64 = let bs = Otel.Span_id.to_bytes id in @@ -47,7 +13,7 @@ let span_of_i64 (id : int64) : Otel.Span_id.t = let collector () : Trace.collector = let module M = struct - let enter_span ?__FUNCTION__:_ ~__FILE__ ~__LINE__ ~data name : span = + let with_span ~__FUNCTION__:_ ~__FILE__ ~__LINE__ ~data name cb = let span_id = Otel.Span_id.create () in let span = conv_span_to_i64 span_id in @@ -63,57 +29,32 @@ let collector () : Trace.collector = let new_scope = { Otel.Scope.span_id; trace_id; events = []; attrs = [] } in - TLS.set Otel.Scope._global_scope new_scope; - let active_spans = Active_spans.get () in - Span_tbl.add active_spans.tbl span - { - span_id; - start_time; - __FILE__; - __LINE__; - old_scope; - new_scope; - name; - data; - }; + Ambient_context.with_binding Otel.Scope._ambient_scope new_scope + @@ fun () -> + let rv = cb span in - span + let end_time = Otel.Timestamp_ns.now_unix_ns () in - let exit_span (span : span) : unit = - let active_spans = Active_spans.get () in - match Span_tbl.find_opt active_spans.tbl span with - | None -> () (* TODO: log warning *) - | Some - { - span_id; - start_time; - name; - __FILE__; - __LINE__; - new_scope; - old_scope; - data; - } -> - let end_time = Otel.Timestamp_ns.now_unix_ns () in - - (* restore previous scope *) - (match old_scope with - | None -> TLS.remove Otel.Scope._global_scope - | Some sc -> TLS.set Otel.Scope._global_scope sc); - - let o_span : Otel.Span.t = - let attrs = - [ "file", `String __FILE__; "line", `Int __LINE__ ] @ data - in - Otel.Span.create ~trace_id:new_scope.trace_id ~id:span_id ~start_time - ~end_time ~attrs name - |> fst + let o_span : Otel.Span.t = + let attrs = + [ "file", `String __FILE__; "line", `Int __LINE__ ] @ data in + Otel.Span.create ~trace_id:new_scope.trace_id ~id:span_id ~start_time + ~end_time ~attrs name + |> fst + in - Otel.Trace.emit [ o_span ]; + Otel.Trace.emit [ o_span ]; - () + rv + + let enter_explicit_span ~surrounding:_ ?__FUNCTION__:_ ~__FILE__:_ + ~__LINE__:_ ~data:_ _name : Trace.explicit_span = + failwith "nyi" + + let exit_explicit_span _sp = + failwith "nyi" let message ?span ~data:_ msg : unit = (* gather information from context *) From d668f5c4727917b10bd15a89d41cc1f880e6ab64 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 1 Aug 2023 21:42:35 +0000 Subject: [PATCH 02/25] lwt: Share impl details with non-lwt with_ --- src/lwt/opentelemetry_lwt.ml | 42 ++++++++---------------------------- src/opentelemetry.ml | 34 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/src/lwt/opentelemetry_lwt.ml b/src/lwt/opentelemetry_lwt.ml index 8c50e284..887fe6f7 100644 --- a/src/lwt/opentelemetry_lwt.ml +++ b/src/lwt/opentelemetry_lwt.ml @@ -12,45 +12,21 @@ module Metrics_callbacks = Metrics_callbacks module Trace_context = Trace_context module Trace = struct - open Proto.Trace include Trace (** Sync span guard *) - let with_ ?trace_state ?service_name ?(attrs = []) ?kind ?trace_id ?parent - ?scope ?links name (f : Scope.t -> 'a Lwt.t) : 'a Lwt.t = - let trace_id = - match trace_id, scope with - | Some trace_id, _ -> trace_id - | None, Some scope -> scope.trace_id - | None, None -> Trace_id.create () - in - let parent = - match parent, scope with - | Some span_id, _ -> Some span_id - | None, Some scope -> Some scope.span_id - | None, None -> None - in - let start_time = Timestamp_ns.now_unix_ns () in - let span_id = Span_id.create () in - let scope = { trace_id; span_id; events = []; attrs } in - let finally ok = - let status = - match ok with - | Ok () -> default_status ~code:Status_code_ok () - | Error e -> default_status ~code:Status_code_error ~message:e () - in - let span, _ = - Span.create ?kind ~trace_id ?parent ?links ~id:span_id ?trace_state - ~attrs:scope.attrs ~events:scope.events ~start_time - ~end_time:(Timestamp_ns.now_unix_ns ()) - ~status name - in - emit ?service_name [ span ] + let with_ ?force_new_trace_id ?trace_state ?service_name ?attrs ?kind + ?trace_id ?parent ?scope ?links name (cb : Scope.t -> 'a Lwt.t) : 'a Lwt.t + = + let thunk, finally = + with_' ?force_new_trace_id ?trace_state ?service_name ?attrs ?kind + ?trace_id ?parent ?scope ?links name cb in + try%lwt - let* x = f scope in + let* rv = thunk () in let () = finally (Ok ()) in - Lwt.return x + Lwt.return rv with e -> let () = finally (Error (Printexc.to_string e)) in Lwt.fail e diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index 60d374fa..6d27bf1d 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -716,17 +716,9 @@ module Trace = struct let add_attrs = Scope.add_attrs [@@deprecated "use Scope.add_attrs"] - (** Sync span guard. - - @param force_new_trace_id if true (default false), the span will not use a - surrounding context, or [scope], or [trace_id], but will always - create a fresh new trace ID. - - {b NOTE} be careful not to call this inside a Gc alarm, as it can - cause deadlocks. *) - let with_ ?(force_new_trace_id = false) ?trace_state ?service_name + let with_' ?(force_new_trace_id = false) ?trace_state ?service_name ?(attrs : (string * [< value ]) list = []) ?kind ?trace_id ?parent ?scope - ?links name (f : Scope.t -> 'a) : 'a = + ?links name cb = let scope = if force_new_trace_id then None @@ -770,10 +762,28 @@ module Trace = struct in emit ?service_name [ span ] in + let thunk () = cb scope in + thunk, finally + + (** Sync span guard. + + @param force_new_trace_id if true (default false), the span will not use a + surrounding context, or [scope], or [trace_id], but will always + create a fresh new trace ID. + + {b NOTE} be careful not to call this inside a Gc alarm, as it can + cause deadlocks. *) + let with_ ?force_new_trace_id ?trace_state ?service_name ?attrs ?kind + ?trace_id ?parent ?scope ?links name (cb : Scope.t -> 'a) : 'a = + let thunk, finally = + with_' ?force_new_trace_id ?trace_state ?service_name ?attrs ?kind + ?trace_id ?parent ?scope ?links name cb + in + try - let x = f scope in + let rv = thunk () in finally (Ok ()); - x + rv with e -> finally (Error (Printexc.to_string e)); raise e From 1a1c360f4c6dd1b5a75f4f63fa1a9d07f1ea44b4 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 2 Aug 2023 00:19:24 +0000 Subject: [PATCH 03/25] store: Rename 'surrounding' scope to 'ambient' --- src/opentelemetry.ml | 30 ++++++++++++------------------ src/trace/opentelemetry_trace.ml | 10 ++++------ 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index 6d27bf1d..9994ba9b 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -518,26 +518,20 @@ module Scope = struct if Collector.has_backend () then scope.attrs <- List.rev_append (attrs ()) scope.attrs - (**/**) + (** The opaque key necessary to access/set the ambient scope with + {!Ambient_context}. *) + let ambient_scope_key : t Ambient_context.key = Ambient_context.create_key () - let _ambient_scope : t Ambient_context.key = Ambient_context.create_key () - - (**/**) - - (** Obtain current scope from thread-local storage, if available *) - let get_surrounding ?scope () : t option = + (** Obtain current scope from {!Ambient_context}, if available. *) + let get_ambient_scope ?scope () : t option = match scope with | Some _ -> scope - | None -> Ambient_context.get _ambient_scope + | None -> Ambient_context.get ambient_scope_key - (** [with_scope sc f] calls [f()] in a context where [sc] is the + (** [with_ambient_scope sc f] calls [f()] in a context where [sc] is the (thread)-local scope, then reverts to the previous local scope, if any. *) - let[@inline] with_scope (sc : t) (f : unit -> 'a) : 'a = - Ambient_context.with_binding _ambient_scope sc (fun _ -> f ()) -end - -open struct - let get_surrounding_scope = Scope.get_surrounding + let[@inline] with_ambient_scope (sc : t) (f : unit -> 'a) : 'a = + Ambient_context.with_binding ambient_scope_key sc (fun _ -> f ()) end (** Span Link @@ -723,7 +717,7 @@ module Trace = struct if force_new_trace_id then None else - get_surrounding_scope ?scope () + Scope.get_ambient_scope ?scope () in let trace_id = match trace_id, scope with @@ -743,7 +737,7 @@ module Trace = struct let span_id = Span_id.create () in let scope = { trace_id; span_id; events = []; attrs } in (* set global scope in this thread *) - Scope.with_scope scope @@ fun () -> + Scope.with_ambient_scope scope @@ fun () -> (* called once we're done, to emit a span *) let finally res = let status = @@ -768,7 +762,7 @@ module Trace = struct (** Sync span guard. @param force_new_trace_id if true (default false), the span will not use a - surrounding context, or [scope], or [trace_id], but will always + ambient scope, [scope], or [trace_id], but will always create a fresh new trace ID. {b NOTE} be careful not to call this inside a Gc alarm, as it can diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 5f0d2c24..86e3f61a 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -19,7 +19,7 @@ let collector () : Trace.collector = let start_time = Otel.Timestamp_ns.now_unix_ns () in - let old_scope = Otel.Scope.get_surrounding () in + let old_scope = Otel.Scope.get_ambient_scope () in let trace_id = match old_scope with | None -> Otel.Trace_id.create () @@ -30,8 +30,7 @@ let collector () : Trace.collector = { Otel.Scope.span_id; trace_id; events = []; attrs = [] } in - Ambient_context.with_binding Otel.Scope._ambient_scope new_scope - @@ fun () -> + Otel.Scope.with_ambient_scope new_scope @@ fun () -> let rv = cb span in let end_time = Otel.Timestamp_ns.now_unix_ns () in @@ -53,12 +52,11 @@ let collector () : Trace.collector = ~__LINE__:_ ~data:_ _name : Trace.explicit_span = failwith "nyi" - let exit_explicit_span _sp = - failwith "nyi" + let exit_explicit_span _sp = failwith "nyi" let message ?span ~data:_ msg : unit = (* gather information from context *) - let old_scope = Otel.Scope.get_surrounding () in + let old_scope = Otel.Scope.get_ambient_scope () in let trace_id = Option.map (fun sc -> sc.Otel.Scope.trace_id) old_scope in let span_id = From 9e65566d886e7fee80c7ea2315bd4ce78f7303cf Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Thu, 3 Aug 2023 20:16:34 +0000 Subject: [PATCH 04/25] trace-collector: Use OTel semconv for __FUNCTION__ etc --- src/trace/opentelemetry_trace.ml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 86e3f61a..b987829d 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -13,7 +13,7 @@ let span_of_i64 (id : int64) : Otel.Span_id.t = let collector () : Trace.collector = let module M = struct - let with_span ~__FUNCTION__:_ ~__FILE__ ~__LINE__ ~data name cb = + let with_span ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name cb = let span_id = Otel.Span_id.create () in let span = conv_span_to_i64 span_id in @@ -36,8 +36,20 @@ let collector () : Trace.collector = let end_time = Otel.Timestamp_ns.now_unix_ns () in let o_span : Otel.Span.t = + let last_dot = String.rindex __FUNCTION__ '.' in + let module_path = String.sub __FUNCTION__ 0 last_dot in + let function_name = + String.sub __FUNCTION__ (last_dot + 1) + (String.length __FUNCTION__ - last_dot - 1) + in let attrs = - [ "file", `String __FILE__; "line", `Int __LINE__ ] @ data + [ + "code.filepath", `String __FILE__; + "code.lineno", `Int __LINE__; + "code.function", `String function_name; + "code.namespace", `String module_path; + ] + @ data in Otel.Span.create ~trace_id:new_scope.trace_id ~id:span_id ~start_time ~end_time ~attrs name From 9fd5fd853607957e7f0214e2b310aff795cc2090 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Thu, 3 Aug 2023 20:23:05 +0000 Subject: [PATCH 05/25] meta: Auto-format dune-project --- dune-project | 113 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 41 deletions(-) diff --git a/dune-project b/dune-project index 14d68d3f..e8351e30 100644 --- a/dune-project +++ b/dune-project @@ -1,27 +1,36 @@ (lang dune 2.7) + (name opentelemetry) + (generate_opam_files true) + (source (github imandra-ai/ocaml-opentelemetry)) (version 0.5) (authors "the Imandra team and contributors") + (maintainers "the Imandra team and contributors") + (license MIT) + ;(documentation https://url/to/documentation) (package (name opentelemetry) (synopsis "Instrumentation for https://opentelemetry.io") (depends - (ocaml (>= "4.08")) - ptime - ambient-context - (odoc :with-doc) - (pbrt (>= 2.3))) + (ocaml + (>= "4.08")) + ptime + ambient-context + (odoc :with-doc) + (pbrt + (>= 2.3))) (depopts - (trace (>= 0.1))) + (trace + (>= 0.1))) (tags (instrumentation tracing opentelemetry datadog jaeger))) @@ -29,50 +38,72 @@ (name opentelemetry-lwt) (synopsis "Lwt-compatible instrumentation for https://opentelemetry.io") (depends - (ocaml (>= "4.08")) - ambient-context - (opentelemetry (= :version)) - (cohttp-lwt-unix :with-test) - (odoc :with-doc) - (lwt (>= "5.3")) - (lwt_ppx (>= "2.0"))) + (ocaml + (>= "4.08")) + ambient-context + (opentelemetry + (= :version)) + (cohttp-lwt-unix :with-test) + (odoc :with-doc) + (lwt + (>= "5.3")) + (lwt_ppx + (>= "2.0"))) (tags (instrumentation tracing opentelemetry datadog lwt))) (package - (name opentelemetry-client-ocurl) - (depends - (ocaml (>= "4.08")) - (mtime (>= "1.4")) ; for spans - ; atomic ; vendored - (opentelemetry (= :version)) - (pbrt (>= 2.3)) - (odoc :with-doc) - (ezcurl (>= 0.2.3)) - ocurl) - (synopsis "Collector client for opentelemetry, using http + ezcurl")) + (name opentelemetry-client-ocurl) + (depends + (ocaml + (>= "4.08")) + (mtime + (>= "1.4")) + ; for spans + ; atomic ; vendored + (opentelemetry + (= :version)) + (pbrt + (>= 2.3)) + (odoc :with-doc) + (ezcurl + (>= 0.2.3)) + ocurl) + (synopsis "Collector client for opentelemetry, using http + ezcurl")) (package (name opentelemetry-cohttp-lwt) (depends - (ocaml (>= "4.08")) - (opentelemetry (= :version)) - (opentelemetry-lwt (= :version)) + (ocaml + (>= "4.08")) + (opentelemetry + (= :version)) + (opentelemetry-lwt + (= :version)) (odoc :with-doc) - (lwt (>= "5.3")) - (cohttp-lwt (>= "4.0.0"))) + (lwt + (>= "5.3")) + (cohttp-lwt + (>= "4.0.0"))) (synopsis "Opentelemetry tracing for Cohttp HTTP servers")) (package - (name opentelemetry-client-cohttp-lwt) - (depends - (ocaml (>= "4.08")) - (mtime (>= "1.4")) ; for spans - (opentelemetry (= :version)) - (pbrt (>= 2.2)) - (odoc :with-doc) - (lwt (>= "5.3")) - (lwt_ppx (>= "2.0")) - cohttp-lwt - cohttp-lwt-unix) - (synopsis "Collector client for opentelemetry, using cohttp + lwt")) + (name opentelemetry-client-cohttp-lwt) + (depends + (ocaml + (>= "4.08")) + (mtime + (>= "1.4")) + ; for spans + (opentelemetry + (= :version)) + (pbrt + (>= 2.2)) + (odoc :with-doc) + (lwt + (>= "5.3")) + (lwt_ppx + (>= "2.0")) + cohttp-lwt + cohttp-lwt-unix) + (synopsis "Collector client for opentelemetry, using cohttp + lwt")) From c82cd6d7981affe5267b8d003f84c10f067c5601 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Thu, 3 Aug 2023 20:24:09 +0000 Subject: [PATCH 06/25] meta: Lock down the version of ocamlformat --- dune-project | 8 +++++++- opentelemetry.opam | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dune-project b/dune-project index e8351e30..b4617e51 100644 --- a/dune-project +++ b/dune-project @@ -27,7 +27,13 @@ ambient-context (odoc :with-doc) (pbrt - (>= 2.3))) + (>= 2.3)) + (ocaml-lsp-server :with-dev-setup) + (ocamlformat + (and + :with-dev-setup + (>= 0.24) + (< 0.25)))) (depopts (trace (>= 0.1))) diff --git a/opentelemetry.opam b/opentelemetry.opam index e13b4a10..0e81945c 100644 --- a/opentelemetry.opam +++ b/opentelemetry.opam @@ -15,6 +15,8 @@ depends: [ "ambient-context" "odoc" {with-doc} "pbrt" {>= "2.3"} + "ocaml-lsp-server" {with-dev-setup} + "ocamlformat" {with-dev-setup & >= "0.24" & < "0.25"} ] depopts: [ "trace" {>= "0.1"} From de35c80799986cfa73fd87afec15f946298cc135 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Thu, 3 Aug 2023 20:29:35 +0000 Subject: [PATCH 07/25] trace-collector: Match new Trace API --- src/trace/opentelemetry_trace.ml | 43 +++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index b987829d..0699ef43 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -36,23 +36,26 @@ let collector () : Trace.collector = let end_time = Otel.Timestamp_ns.now_unix_ns () in let o_span : Otel.Span.t = - let last_dot = String.rindex __FUNCTION__ '.' in - let module_path = String.sub __FUNCTION__ 0 last_dot in - let function_name = - String.sub __FUNCTION__ (last_dot + 1) - (String.length __FUNCTION__ - last_dot - 1) - in let attrs = - [ - "code.filepath", `String __FILE__; - "code.lineno", `Int __LINE__; - "code.function", `String function_name; - "code.namespace", `String module_path; - ] - @ data + match __FUNCTION__ with + | None -> + [ "code.filepath", `String __FILE__; "code.lineno", `Int __LINE__ ] + | Some __FUNCTION__ -> + let last_dot = String.rindex __FUNCTION__ '.' in + let module_path = String.sub __FUNCTION__ 0 last_dot in + let function_name = + String.sub __FUNCTION__ (last_dot + 1) + (String.length __FUNCTION__ - last_dot - 1) + in + [ + "code.filepath", `String __FILE__; + "code.lineno", `Int __LINE__; + "code.function", `String function_name; + "code.namespace", `String module_path; + ] in Otel.Span.create ~trace_id:new_scope.trace_id ~id:span_id ~start_time - ~end_time ~attrs name + ~end_time ~attrs:(attrs @ data) name |> fst in @@ -60,11 +63,15 @@ let collector () : Trace.collector = rv - let enter_explicit_span ~surrounding:_ ?__FUNCTION__:_ ~__FILE__:_ - ~__LINE__:_ ~data:_ _name : Trace.explicit_span = - failwith "nyi" + let enter_manual_span ~parent:_ ~__FUNCTION__:_ ~__FILE__:_ ~__LINE__:_ + ~data:_ _name : Trace.explicit_span = + (* TODO: print debugging warning if OTEL_OCAML_DEBUG is enabled *) + Trace.Collector.dummy_explicit_span - let exit_explicit_span _sp = failwith "nyi" + let exit_manual_span es = + (* TODO: print debugging warning if OTEL_OCAML_DEBUG is enabled *) + assert (es == Trace.Collector.dummy_explicit_span); + () let message ?span ~data:_ msg : unit = (* gather information from context *) From 1822c1acaad58bb1ae1569fe70d4e72ce8b77085 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 29 Aug 2023 00:11:48 +0000 Subject: [PATCH 08/25] trace-collector: Support manual spans --- src/trace/opentelemetry_trace.ml | 234 ++++++++++++++++++++++++------- 1 file changed, 180 insertions(+), 54 deletions(-) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 0699ef43..8b6b9da2 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -1,77 +1,203 @@ module Otel = Opentelemetry +module Otrace = Trace (* ocaml-trace *) +module TLS = Ambient_context_tls.Thread_local -let conv_span_to_i64 (id : Otel.Span_id.t) : int64 = +type span_begin = { + id: Otel.Span_id.t; + start_time: int64; + name: string; + data: (string * Otrace.user_data) list; + __FILE__: string; + __LINE__: int; + __FUNCTION__: string option; + trace_id: Otel.Trace_id.t; + scope: Otel.Scope.t; + parent_id: Otel.Span_id.t option; + parent_scope: Otel.Scope.t option; +} + +(** Table indexed by ocaml-trace spans *) +module Active_span_tbl = Hashtbl.Make (struct + include Int64 + + let hash : t -> int = Hashtbl.hash +end) + +(** Per-thread set of active spans. *) +module Active_spans = struct + type t = { tbl: span_begin Active_span_tbl.t } [@@unboxed] + + let create () : t = { tbl = Active_span_tbl.create 32 } + + let tls : t TLS.t = TLS.create () + + let[@inline] get () : t = TLS.get_or_create tls ~create +end + +let otrace_of_otel (id : Otel.Span_id.t) : int64 = let bs = Otel.Span_id.to_bytes id in (* lucky that it coincides! *) assert (Bytes.length bs = 8); Bytes.get_int64_le bs 0 -let span_of_i64 (id : int64) : Otel.Span_id.t = +let otel_of_otrace (id : int64) : Otel.Span_id.t = let bs = Bytes.create 8 in Bytes.set_int64_le bs 0 id; Otel.Span_id.of_bytes bs +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 : Otel.Span.key_value list) = + 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 + +let enter_span' ?explicit_parent ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name = + let open Otel in + let otel_id = Span_id.create () in + let otrace_id = otrace_of_otel otel_id in + + let parent_scope = Scope.get_ambient_scope () in + let trace_id = + match parent_scope with + | Some sc -> sc.trace_id + | None -> Trace_id.create () + in + let parent_id = + match explicit_parent, parent_scope with + | Some p, _ -> Some (otel_of_otrace p) + | None, Some parent -> Some parent.span_id + | None, None -> None + in + + let new_scope = + { Scope.span_id = otel_id; trace_id; events = []; attrs = [] } + in + + let start_time = Timestamp_ns.now_unix_ns () in + + let sb = + { + id = otel_id; + start_time; + name; + data; + __FILE__; + __LINE__; + __FUNCTION__; + trace_id; + scope = new_scope; + parent_id; + parent_scope; + } + in + + let active_spans = Active_spans.get () in + Active_span_tbl.add active_spans.tbl otrace_id sb; + + otrace_id, sb + +let exit_span' otrace_id + { + id = otel_id; + start_time; + name; + data; + __FILE__; + __LINE__; + __FUNCTION__; + trace_id; + scope = _; + parent_id; + parent_scope = _; + } = + let open Otel in + let active_spans = Active_spans.get () in + Active_span_tbl.remove active_spans.tbl otrace_id; + + let end_time = Timestamp_ns.now_unix_ns () in + + let kind, attrs = otel_attrs_of_otrace_data data in + + let attrs = + match __FUNCTION__ with + | None -> + [ "code.filepath", `String __FILE__; "code.lineno", `Int __LINE__ ] + @ attrs + | Some __FUNCTION__ -> + let last_dot = String.rindex __FUNCTION__ '.' in + let module_path = String.sub __FUNCTION__ 0 last_dot in + let function_name = + String.sub __FUNCTION__ (last_dot + 1) + (String.length __FUNCTION__ - last_dot - 1) + in + [ + "code.filepath", `String __FILE__; + "code.lineno", `Int __LINE__; + "code.function", `String function_name; + "code.namespace", `String module_path; + ] + @ attrs + in + Span.create ~kind ~trace_id ?parent:parent_id ~id:otel_id ~start_time + ~end_time ~attrs name + |> fst + let collector () : Trace.collector = let module M = struct let with_span ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name cb = - let span_id = Otel.Span_id.create () in - let span = conv_span_to_i64 span_id in - - let start_time = Otel.Timestamp_ns.now_unix_ns () in - - let old_scope = Otel.Scope.get_ambient_scope () in - let trace_id = - match old_scope with - | None -> Otel.Trace_id.create () - | Some sc -> sc.trace_id + let otrace_id, sb = + enter_span' ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name in - let new_scope = - { Otel.Scope.span_id; trace_id; events = []; attrs = [] } - in + Otel.Scope.with_ambient_scope sb.scope @@ fun () -> + let rv = cb otrace_id in - Otel.Scope.with_ambient_scope new_scope @@ fun () -> - let rv = cb span in - - let end_time = Otel.Timestamp_ns.now_unix_ns () in - - let o_span : Otel.Span.t = - let attrs = - match __FUNCTION__ with - | None -> - [ "code.filepath", `String __FILE__; "code.lineno", `Int __LINE__ ] - | Some __FUNCTION__ -> - let last_dot = String.rindex __FUNCTION__ '.' in - let module_path = String.sub __FUNCTION__ 0 last_dot in - let function_name = - String.sub __FUNCTION__ (last_dot + 1) - (String.length __FUNCTION__ - last_dot - 1) - in - [ - "code.filepath", `String __FILE__; - "code.lineno", `Int __LINE__; - "code.function", `String function_name; - "code.namespace", `String module_path; - ] - in - Otel.Span.create ~trace_id:new_scope.trace_id ~id:span_id ~start_time - ~end_time ~attrs:(attrs @ data) name - |> fst - in - - Otel.Trace.emit [ o_span ]; + let otel_span = exit_span' otrace_id sb in + Otel.Trace.emit [ otel_span ]; rv - let enter_manual_span ~parent:_ ~__FUNCTION__:_ ~__FILE__:_ ~__LINE__:_ - ~data:_ _name : Trace.explicit_span = - (* TODO: print debugging warning if OTEL_OCAML_DEBUG is enabled *) - Trace.Collector.dummy_explicit_span + let enter_manual_span ~(parent : Otrace.explicit_span option) ~flavor:_ + ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name : Otrace.explicit_span = + let otrace_id, sb = + match parent with + | None -> enter_span' ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name + | Some { span; _ } -> + enter_span' ~explicit_parent:span ~__FUNCTION__ ~__FILE__ ~__LINE__ + ~data name + in - let exit_manual_span es = - (* TODO: print debugging warning if OTEL_OCAML_DEBUG is enabled *) - assert (es == Trace.Collector.dummy_explicit_span); - () + let active_spans = Active_spans.get () in + Active_span_tbl.add active_spans.tbl otrace_id sb; + + Otrace.{ span = otrace_id; meta = Meta_map.empty } + + let exit_manual_span Otrace.{ span = otrace_id; _ } = + let active_spans = Active_spans.get () in + match Active_span_tbl.find_opt active_spans.tbl otrace_id with + | None -> + (* FIXME: some kind of error/debug logging *) + () + | Some sb -> + let otel_span = exit_span' otrace_id sb in + Otel.Trace.emit [ otel_span ] let message ?span ~data:_ msg : unit = (* gather information from context *) @@ -80,7 +206,7 @@ let collector () : Trace.collector = let span_id = match span with - | Some id -> Some (span_of_i64 id) + | Some id -> Some (otel_of_otrace id) | None -> Option.map (fun sc -> sc.Otel.Scope.span_id) old_scope in From 9a26bb5e8b068e20640362ed404d8d539951d89b Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 29 Aug 2023 01:33:01 +0000 Subject: [PATCH 09/25] deps: Require Trace >= 0.3 --- dune-project | 2 +- opentelemetry.opam | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dune-project b/dune-project index b4617e51..8040c2f8 100644 --- a/dune-project +++ b/dune-project @@ -36,7 +36,7 @@ (< 0.25)))) (depopts (trace - (>= 0.1))) + (>= 0.3))) (tags (instrumentation tracing opentelemetry datadog jaeger))) diff --git a/opentelemetry.opam b/opentelemetry.opam index 0e81945c..4ebb692e 100644 --- a/opentelemetry.opam +++ b/opentelemetry.opam @@ -19,7 +19,7 @@ depends: [ "ocamlformat" {with-dev-setup & >= "0.24" & < "0.25"} ] depopts: [ - "trace" {>= "0.1"} + "trace" {>= "0.3"} ] build: [ ["dune" "subst"] {dev} From 6bf59ee21eac113d87f973ee0d68b735ec57064f Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 29 Aug 2023 01:39:42 +0000 Subject: [PATCH 10/25] deps: Follow @kit-ty-kate's advice /see ocaml/opam-repository#24240 --- dune-project | 7 +++++-- opentelemetry.opam | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dune-project b/dune-project index 8040c2f8..a15d0426 100644 --- a/dune-project +++ b/dune-project @@ -34,9 +34,12 @@ :with-dev-setup (>= 0.24) (< 0.25)))) - (depopts + (depopts trace) + (conflicts (trace - (>= 0.3))) + (or + (< 0.3) + (>= 0.4)))) (tags (instrumentation tracing opentelemetry datadog jaeger))) diff --git a/opentelemetry.opam b/opentelemetry.opam index 4ebb692e..168a88e6 100644 --- a/opentelemetry.opam +++ b/opentelemetry.opam @@ -18,8 +18,9 @@ depends: [ "ocaml-lsp-server" {with-dev-setup} "ocamlformat" {with-dev-setup & >= "0.24" & < "0.25"} ] -depopts: [ - "trace" {>= "0.3"} +depopts: ["trace"] +conflicts: [ + "trace" {< "0.3" | >= "0.4"} ] build: [ ["dune" "subst"] {dev} From 2b3e3d733c1d66edd68d79578f67bf99f63a3eaf Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 30 Aug 2023 01:21:30 +0000 Subject: [PATCH 11/25] trace-collector: Expose Internal module --- src/trace/opentelemetry_trace.ml | 281 +++++++++++++++--------------- src/trace/opentelemetry_trace.mli | 97 ++++++++++- 2 files changed, 237 insertions(+), 141 deletions(-) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 8b6b9da2..34010ef9 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -2,165 +2,166 @@ module Otel = Opentelemetry module Otrace = Trace (* ocaml-trace *) module TLS = Ambient_context_tls.Thread_local -type span_begin = { - id: Otel.Span_id.t; - start_time: int64; - name: string; - data: (string * Otrace.user_data) list; - __FILE__: string; - __LINE__: int; - __FUNCTION__: string option; - trace_id: Otel.Trace_id.t; - scope: Otel.Scope.t; - parent_id: Otel.Span_id.t option; - parent_scope: Otel.Scope.t option; -} +module Internal = struct + type span_begin = { + id: Otel.Span_id.t; + start_time: int64; + name: string; + data: (string * Otrace.user_data) list; + __FILE__: string; + __LINE__: int; + __FUNCTION__: string option; + trace_id: Otel.Trace_id.t; + scope: Otel.Scope.t; + parent_id: Otel.Span_id.t option; + parent_scope: Otel.Scope.t option; + } -(** Table indexed by ocaml-trace spans *) -module Active_span_tbl = Hashtbl.Make (struct - include Int64 + (** Table indexed by ocaml-trace spans *) + module Active_span_tbl = Hashtbl.Make (struct + include Int64 - let hash : t -> int = Hashtbl.hash -end) + let hash : t -> int = Hashtbl.hash + end) -(** Per-thread set of active spans. *) -module Active_spans = struct - type t = { tbl: span_begin Active_span_tbl.t } [@@unboxed] + (** Per-thread set of active spans. *) + module Active_spans = struct + type t = { tbl: span_begin Active_span_tbl.t } [@@unboxed] - let create () : t = { tbl = Active_span_tbl.create 32 } + let create () : t = { tbl = Active_span_tbl.create 32 } - let tls : t TLS.t = TLS.create () + let tls : t TLS.t = TLS.create () - let[@inline] get () : t = TLS.get_or_create tls ~create -end + let[@inline] get () : t = TLS.get_or_create tls ~create + end -let otrace_of_otel (id : Otel.Span_id.t) : int64 = - let bs = Otel.Span_id.to_bytes id in - (* lucky that it coincides! *) - assert (Bytes.length bs = 8); - Bytes.get_int64_le bs 0 + let otrace_of_otel (id : Otel.Span_id.t) : int64 = + let bs = Otel.Span_id.to_bytes id in + (* lucky that it coincides! *) + assert (Bytes.length bs = 8); + Bytes.get_int64_le bs 0 -let otel_of_otrace (id : int64) : Otel.Span_id.t = - let bs = Bytes.create 8 in - Bytes.set_int64_le bs 0 id; - Otel.Span_id.of_bytes bs + let otel_of_otrace (id : int64) : Otel.Span_id.t = + let bs = Bytes.create 8 in + Bytes.set_int64_le bs 0 id; + Otel.Span_id.of_bytes bs -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 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 : Otel.Span.key_value list) = - 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 + 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 -let enter_span' ?explicit_parent ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name = - let open Otel in - let otel_id = Span_id.create () in - let otrace_id = otrace_of_otel otel_id in + let enter_span' ?explicit_parent ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name + = + let open Otel in + let otel_id = Span_id.create () in + let otrace_id = otrace_of_otel otel_id in - let parent_scope = Scope.get_ambient_scope () in - let trace_id = - match parent_scope with - | Some sc -> sc.trace_id - | None -> Trace_id.create () - in - let parent_id = - match explicit_parent, parent_scope with - | Some p, _ -> Some (otel_of_otrace p) - | None, Some parent -> Some parent.span_id - | None, None -> None - in + let parent_scope = Scope.get_ambient_scope () in + let trace_id = + match parent_scope with + | Some sc -> sc.trace_id + | None -> Trace_id.create () + in + let parent_id = + match explicit_parent, parent_scope with + | Some p, _ -> Some (otel_of_otrace p) + | None, Some parent -> Some parent.span_id + | None, None -> None + in - let new_scope = - { Scope.span_id = otel_id; trace_id; events = []; attrs = [] } - in + let new_scope = + { Scope.span_id = otel_id; trace_id; events = []; attrs = [] } + in - let start_time = Timestamp_ns.now_unix_ns () in + let start_time = Timestamp_ns.now_unix_ns () in - let sb = - { - id = otel_id; - start_time; - name; - data; - __FILE__; - __LINE__; - __FUNCTION__; - trace_id; - scope = new_scope; - parent_id; - parent_scope; - } - in + let sb = + { + id = otel_id; + start_time; + name; + data; + __FILE__; + __LINE__; + __FUNCTION__; + trace_id; + scope = new_scope; + parent_id; + parent_scope; + } + in - let active_spans = Active_spans.get () in - Active_span_tbl.add active_spans.tbl otrace_id sb; + let active_spans = Active_spans.get () in + Active_span_tbl.add active_spans.tbl otrace_id sb; - otrace_id, sb + otrace_id, sb -let exit_span' otrace_id - { - id = otel_id; - start_time; - name; - data; - __FILE__; - __LINE__; - __FUNCTION__; - trace_id; - scope = _; - parent_id; - parent_scope = _; - } = - let open Otel in - let active_spans = Active_spans.get () in - Active_span_tbl.remove active_spans.tbl otrace_id; + let exit_span' otrace_id + { + id = otel_id; + start_time; + name; + data; + __FILE__; + __LINE__; + __FUNCTION__; + trace_id; + scope = _; + parent_id; + parent_scope = _; + } = + let open Otel in + let active_spans = Active_spans.get () in + Active_span_tbl.remove active_spans.tbl otrace_id; - let end_time = Timestamp_ns.now_unix_ns () in + let end_time = Timestamp_ns.now_unix_ns () in - let kind, attrs = otel_attrs_of_otrace_data data in + let kind, attrs = otel_attrs_of_otrace_data data in - let attrs = - match __FUNCTION__ with - | None -> - [ "code.filepath", `String __FILE__; "code.lineno", `Int __LINE__ ] - @ attrs - | Some __FUNCTION__ -> - let last_dot = String.rindex __FUNCTION__ '.' in - let module_path = String.sub __FUNCTION__ 0 last_dot in - let function_name = - String.sub __FUNCTION__ (last_dot + 1) - (String.length __FUNCTION__ - last_dot - 1) - in - [ - "code.filepath", `String __FILE__; - "code.lineno", `Int __LINE__; - "code.function", `String function_name; - "code.namespace", `String module_path; - ] - @ attrs - in - Span.create ~kind ~trace_id ?parent:parent_id ~id:otel_id ~start_time - ~end_time ~attrs name - |> fst + let attrs = + match __FUNCTION__ with + | None -> + [ "code.filepath", `String __FILE__; "code.lineno", `Int __LINE__ ] + @ attrs + | Some __FUNCTION__ -> + let last_dot = String.rindex __FUNCTION__ '.' in + let module_path = String.sub __FUNCTION__ 0 last_dot in + let function_name = + String.sub __FUNCTION__ (last_dot + 1) + (String.length __FUNCTION__ - last_dot - 1) + in + [ + "code.filepath", `String __FILE__; + "code.lineno", `Int __LINE__; + "code.function", `String function_name; + "code.namespace", `String module_path; + ] + @ attrs + in + Span.create ~kind ~trace_id ?parent:parent_id ~id:otel_id ~start_time + ~end_time ~attrs name + |> fst -let collector () : Trace.collector = - let module M = struct + module M = struct let with_span ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name cb = let otrace_id, sb = enter_span' ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name @@ -226,8 +227,10 @@ let collector () : Trace.collector = let counter_float name cur_val : unit = let m = Otel.Metrics.(gauge ~name [ float cur_val ]) in Otel.Metrics.emit [ m ] - end in - (module M) + end +end + +let collector () : Trace.collector = (module Internal.M) let setup () = Trace.setup_collector @@ collector () diff --git a/src/trace/opentelemetry_trace.mli b/src/trace/opentelemetry_trace.mli index 8e99befb..e506d674 100644 --- a/src/trace/opentelemetry_trace.mli +++ b/src/trace/opentelemetry_trace.mli @@ -1,8 +1,101 @@ -val collector : unit -> Trace.collector -(** Make a Trace collector that uses the OTEL backend to send spans and logs *) +module Otel := Opentelemetry +module Otrace := Trace +module TLS := Ambient_context_tls.Thread_local val setup : unit -> unit (** Install the OTEL backend as a Trace collector *) val setup_with_otel_backend : Opentelemetry.Collector.backend -> unit (** Same as {!setup}, but also install the given backend as OTEL backend *) + +val collector : unit -> Trace.collector +(** Make a Trace collector that uses the OTEL backend to send spans and logs *) + +(** 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 + + val enter_manual_span : + parent:Otrace.explicit_span option -> + flavor:'a -> + __FUNCTION__:string option -> + __FILE__:string -> + __LINE__:int -> + data:(string * Otrace.user_data) list -> + string (* span name *) -> + Otrace.explicit_span + + val exit_manual_span : Otrace.explicit_span -> 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 : string -> int -> unit + + val counter_float : string -> float -> unit + end + + type span_begin = { + id: Otel.Span_id.t; + start_time: int64; + name: string; + data: (string * Otrace.user_data) list; + __FILE__: string; + __LINE__: int; + __FUNCTION__: string option; + trace_id: Otel.Trace_id.t; + scope: Otel.Scope.t; + parent_id: Otel.Span_id.t option; + parent_scope: Otel.Scope.t option; + } + + module Active_span_tbl : Hashtbl.S with type key = Otrace.span + + module Active_spans : sig + type t = private { tbl: span_begin Active_span_tbl.t } [@@unboxed] + + val create : unit -> t + + val tls : t TLS.t + + val get : unit -> t + end + + val otrace_of_otel : Otel.Span_id.t -> Otrace.span + + val otel_of_otrace : Otrace.span -> Otel.Span_id.t + + val spankind_of_string : string -> Otel.Span.kind + + val otel_attrs_of_otrace_data : + (string * Otrace.user_data) list -> + Otel.Span.kind * Otel.Span.key_value list + + val enter_span' : + ?explicit_parent:Otrace.span -> + __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 From 925c96294514c15fc9af1233437c67b25e5bbf5a Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 30 Aug 2023 01:50:04 +0000 Subject: [PATCH 12/25] trace-collector: Documentation --- README.md | 23 +++++++++++++-- src/opentelemetry.ml | 23 +++++++++++---- src/trace/opentelemetry_trace.ml | 1 - src/trace/opentelemetry_trace.mli | 48 +++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3b1233b5..24dd9af5 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,11 @@ MIT * [x] batching, perf, etc. - [ ] async collector relying on ocurl-multi - [ ] interface with `logs` (carry context around) +- [x] implicit scope (via [ambient-context][]) ## Use -For now, instrument manually: +For now, instrument traces/spans, logs, and metrics manually: ```ocaml module Otel = Opentelemetry @@ -45,16 +46,34 @@ let foo () = ]); do_more_work(); () +``` +### Setup + +If you're writing a top-level application, you need to perform some initial configuration. + +1. Set the [`service_name`][]; +2. configure our [ambient-context][] dependency with the appropriate storage for your environment — TLS, Lwt, Eio ... (see [their docs][install-ambient-storage] for more details); +3. and install a [`Collector`][] (usually by calling your collector's `with_setup` function.) + +For example, if your application is using Lwt, and you're using `ocurl` as your collector, you might do something like this: + +```ocaml let main () = Otel.Globals.service_name := "my_service"; Otel.GC_metrics.basic_setup(); + Ambient_context.with_storage_provider (Ambient_context_lwt.storage ()) @@ fun () -> Opentelemetry_client_ocurl.with_setup () @@ fun () -> (* … *) foo (); (* … *) -``` +``` + + [`service_name`]: + [`Collector`]: + [ambient-context]: + [install-ambient-storage]: ## Configuration diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index 9994ba9b..fac821c9 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -528,8 +528,11 @@ module Scope = struct | Some _ -> scope | None -> Ambient_context.get ambient_scope_key - (** [with_ambient_scope sc f] calls [f()] in a context where [sc] is the - (thread)-local scope, then reverts to the previous local scope, if any. *) + (** [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 ambient-context docs *) let[@inline] with_ambient_scope (sc : t) (f : unit -> 'a) : 'a = Ambient_context.with_binding ambient_scope_key sc (fun _ -> f ()) end @@ -761,12 +764,20 @@ module Trace = struct (** Sync span guard. - @param force_new_trace_id if true (default false), the span will not use a - ambient scope, [scope], or [trace_id], but will always - create a fresh new trace ID. + Notably, this includes {e implicit} scope-tracking: if called without a + [~scope] argument (or [~parent]/[~trace_id]), it will check in the + {!Ambient_context} for a surrounding environment, and use that as the + scope. Similarly, it uses {!Scope.with_ambient_scope} to {e set} a new + scope in the ambient context, so that any logically-nested calls to + {!with_} will use this span as their parent. {b NOTE} be careful not to call this inside a Gc alarm, as it can - cause deadlocks. *) + cause deadlocks. + + @param force_new_trace_id if true (default false), the span will not use a + ambient scope, the [~scope] argument, nor [~trace_id], but will instead + always create fresh identifiers for this span *) + let with_ ?force_new_trace_id ?trace_state ?service_name ?attrs ?kind ?trace_id ?parent ?scope ?links name (cb : Scope.t -> 'a) : 'a = let thunk, finally = diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 34010ef9..6873fdd7 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -17,7 +17,6 @@ module Internal = struct parent_scope: Otel.Scope.t option; } - (** Table indexed by ocaml-trace spans *) module Active_span_tbl = Hashtbl.Make (struct include Int64 diff --git a/src/trace/opentelemetry_trace.mli b/src/trace/opentelemetry_trace.mli index e506d674..ca41b29c 100644 --- a/src/trace/opentelemetry_trace.mli +++ b/src/trace/opentelemetry_trace.mli @@ -2,6 +2,16 @@ module Otel := Opentelemetry module Otrace := Trace module TLS := Ambient_context_tls.Thread_local +(** [ocaml-opentelemetry.trace] implements a {!Trace_core.Collector} for {{:https://v3.ocaml.org/p/trace} ocaml-trace}. + + After installing this collector with {!setup}, you can consume libraries + that use ocaml-trace, and they will automatically emit OpenTelemetry spans + and logs. + + Both explicit scope (in the [_manual] functions such as [enter_manual_span]) + and implicit scope (in {!Internal.M.with_span}, via {!Ambient_context}) are + supported; see the detailed notes on {!Internal.M.enter_manual_span}. *) + val setup : unit -> unit (** Install the OTEL backend as a Trace collector *) @@ -22,6 +32,14 @@ module Internal : sig string (* span name *) -> (Otrace.span -> 'a) -> 'a + (** Implements {!Trace_core.Collector.S.with_span}, with the OpenTelemetry + collector as the backend. Invoked via {!Trace.with_span}. + + Notably, this has the same implicit-scope semantics as + {!Opentelemetry.Trace.with_}, and requires configuration of + {!Ambient_context}. + + @see ambient-context docs *) val enter_manual_span : parent:Otrace.explicit_span option -> @@ -32,8 +50,37 @@ module Internal : sig 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.enter_manual_toplevel_span} + and {!Trace.enter_manual_sub_span}; requires an eventual call to + {!Trace.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.explicit_span}, and exit it at any later point with + {!Trace.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.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.exit_manual_span}. Expects the [explicit_span] returned from an + earlier call to {!Trace.enter_manual_toplevel_span} or + {!Trace.enter_manual_sub_span}. + + (See the notes at {!enter_manual_span} about {!Ambient_context}.) *) val message : ?span:Otrace.span -> @@ -68,6 +115,7 @@ module Internal : sig 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] From 155ceae6ae62dfb9f9eb01d9e62ad5d6907b542e Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 30 Aug 2023 01:57:15 +0000 Subject: [PATCH 13/25] update changelog --- CHANGES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ba3b1b03..c9dcc095 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,13 @@ +## next version + +- replace `Thread_local` with `ocaml-ambient-context`, allowing for implicit scope in Lwt/Eio contexts +- update `ocaml-trace` interface to use the new `trace.0.3`-style API (breaking) ## 0.5 - new implementation for ocurl backend, using ezcurl and queues - refactor lwt: Use `try%lwt` over `Lwt.catch` -- add `opentelemetry.trace` (optional, depends on `trace`) +- add `opentelemetry.trace` (optional, depends on `ocaml-trace`) ## 0.4 From 82e3e97f1572082cfb2ff0855415d0cdd2ba0036 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 30 Aug 2023 01:57:50 +0000 Subject: [PATCH 14/25] add PR number in changelog --- CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c9dcc095..05aff23d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ ## next version -- replace `Thread_local` with `ocaml-ambient-context`, allowing for implicit scope in Lwt/Eio contexts -- update `ocaml-trace` interface to use the new `trace.0.3`-style API (breaking) +- replace `Thread_local` with `ocaml-ambient-context`, allowing for implicit scope in Lwt/Eio contexts (#34) +- update `ocaml-trace` interface to use the new `trace.0.3`-style API (breaking, see #34) ## 0.5 From a890876946b7e6c4a11498df4847260a0a5b4bdc Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 30 Aug 2023 02:20:38 +0000 Subject: [PATCH 15/25] Document otrace.spankind identifier --- src/trace/opentelemetry_trace.mli | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/trace/opentelemetry_trace.mli b/src/trace/opentelemetry_trace.mli index ca41b29c..02b2d28d 100644 --- a/src/trace/opentelemetry_trace.mli +++ b/src/trace/opentelemetry_trace.mli @@ -2,15 +2,35 @@ module Otel := Opentelemetry module Otrace := Trace module TLS := Ambient_context_tls.Thread_local -(** [ocaml-opentelemetry.trace] implements a {!Trace_core.Collector} for {{:https://v3.ocaml.org/p/trace} ocaml-trace}. +(** [ocaml-opentelemetry.trace] implements a {!Trace_core.Collector} for + {{:https://v3.ocaml.org/p/trace} ocaml-trace}. After installing this collector with {!setup}, you can consume libraries - that use ocaml-trace, and they will automatically emit OpenTelemetry spans + that use [ocaml-trace], and they will automatically emit OpenTelemetry spans and logs. Both explicit scope (in the [_manual] functions such as [enter_manual_span]) and implicit scope (in {!Internal.M.with_span}, via {!Ambient_context}) are - supported; see the detailed notes on {!Internal.M.enter_manual_span}. *) + supported; see the detailed notes on {!Internal.M.enter_manual_span}. + + {1 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: + + - If a key of ["otrace.spankind"] is included in the {!Trace.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.) + + {[ocaml + let describe () = [ "otrace.spankind", `String "CLIENT" ] in + Trace.with_span ~__FILE__ ~__LINE__ ~data:describe "my-span" @@ fun _ -> + (* ... *) + ]} + *) val setup : unit -> unit (** Install the OTEL backend as a Trace collector *) From 9ecd73cb26e63050627286abbe81a8ef326f15d6 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 30 Aug 2023 02:28:34 +0000 Subject: [PATCH 16/25] trace-collector: Add static names for well-known strings --- src/trace/opentelemetry_trace.ml | 62 +++++++++++++++++++------------ src/trace/opentelemetry_trace.mli | 35 ++++++++++++----- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 6873fdd7..bc9bb87b 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -2,6 +2,45 @@ module Otel = Opentelemetry module Otrace = Trace (* ocaml-trace *) module TLS = Ambient_context_tls.Thread_local +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 +end + +open Well_known + module Internal = struct type span_begin = { id: Otel.Span_id.t; @@ -45,29 +84,6 @@ module Internal = struct Bytes.set_int64_le bs 0 id; Otel.Span_id.of_bytes bs - 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 - let enter_span' ?explicit_parent ~__FUNCTION__ ~__FILE__ ~__LINE__ ~data name = let open Otel in diff --git a/src/trace/opentelemetry_trace.mli b/src/trace/opentelemetry_trace.mli index 02b2d28d..e7835195 100644 --- a/src/trace/opentelemetry_trace.mli +++ b/src/trace/opentelemetry_trace.mli @@ -13,20 +13,20 @@ module TLS := Ambient_context_tls.Thread_local and implicit scope (in {!Internal.M.with_span}, via {!Ambient_context}) are supported; see the detailed notes on {!Internal.M.enter_manual_span}. - {1 Well-known identifiers} + {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: - - If a key of ["otrace.spankind"] is included in the {!Trace.user_data} - passed to [with_span] et al., it will be used as the + - If a key of exactly ["otrace.spankind"] is included in the + {!Trace.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.) {[ocaml - let describe () = [ "otrace.spankind", `String "CLIENT" ] in + let describe () = [ Opentelemetry_trace.(spankind_key, client) ] in Trace.with_span ~__FILE__ ~__LINE__ ~data:describe "my-span" @@ fun _ -> (* ... *) ]} @@ -41,6 +41,27 @@ val setup_with_otel_backend : Opentelemetry.Collector.backend -> unit val collector : unit -> Trace.collector (** Make a Trace collector that uses the OTEL backend to send spans and logs *) +(** Static references for well-known identifiers; see {!label-wellknown}. *) +module Well_known : sig + val spankind_key : string + + val internal : Otrace.user_data + + val server : Otrace.user_data + + val client : Otrace.user_data + + val producer : Otrace.user_data + + val consumer : Otrace.user_data + + val spankind_of_string : string -> Otel.Span.kind + + val otel_attrs_of_otrace_data : + (string * Otrace.user_data) list -> + Otel.Span.kind * Otel.Span.key_value list +end + (** Internal implementation details; do not consider these stable. *) module Internal : sig module M : sig @@ -150,12 +171,6 @@ module Internal : sig val otel_of_otrace : Otrace.span -> Otel.Span_id.t - val spankind_of_string : string -> Otel.Span.kind - - val otel_attrs_of_otrace_data : - (string * Otrace.user_data) list -> - Otel.Span.kind * Otel.Span.key_value list - val enter_span' : ?explicit_parent:Otrace.span -> __FUNCTION__:string option -> From f9ea7043996717c20d6c49a2a523642f5c367207 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 30 Aug 2023 02:30:14 +0000 Subject: [PATCH 17/25] docs: Add a note about well-known-id stability --- src/trace/opentelemetry_trace.mli | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/trace/opentelemetry_trace.mli b/src/trace/opentelemetry_trace.mli index e7835195..bbcb28d8 100644 --- a/src/trace/opentelemetry_trace.mli +++ b/src/trace/opentelemetry_trace.mli @@ -2,7 +2,7 @@ module Otel := Opentelemetry module Otrace := Trace module TLS := Ambient_context_tls.Thread_local -(** [ocaml-opentelemetry.trace] implements a {!Trace_core.Collector} for +(** [opentelemetry.trace] implements a {!Trace_core.Collector} for {{:https://v3.ocaml.org/p/trace} ocaml-trace}. After installing this collector with {!setup}, you can consume libraries @@ -18,7 +18,10 @@ module TLS := Ambient_context_tls.Thread_local 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: + 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.user_data} passed to [with_span] et al., it will be used as the From 03f6f69bdd747e8b504c1f44d2d490ccec99b147 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 19 Sep 2023 21:35:09 +0000 Subject: [PATCH 18/25] collector: Add debug-wrapping backend --- .../opentelemetry_client_cohttp_lwt.ml | 16 +++-- .../opentelemetry_client_cohttp_lwt.mli | 6 ++ .../opentelemetry_client_ocurl.ml | 16 +++-- .../opentelemetry_client_ocurl.mli | 6 ++ src/opentelemetry.ml | 71 +++++++++++++++++++ 5 files changed, 102 insertions(+), 13 deletions(-) diff --git a/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.ml b/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.ml index 2320197c..4bae616f 100644 --- a/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.ml +++ b/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.ml @@ -108,8 +108,7 @@ end = struct try%lwt let+ r = Httpc.post ~headers ~body uri in Ok r - with e -> - Lwt.return @@ Error e + with e -> Lwt.return @@ Error e in match r with | Error e -> @@ -551,7 +550,7 @@ end) } end -let setup_ ?(stop = Atomic.make false) ~(config : Config.t) () = +let create_backend ?(stop = Atomic.make false) ?(config = Config.make ()) () = debug_ := config.debug; let module B = Backend @@ -562,12 +561,17 @@ let setup_ ?(stop = Atomic.make false) ~(config : Config.t) () = end) () in - Opentelemetry.Collector.set_backend (module B); + (module B : OT.Collector.BACKEND) + +let setup_ ?stop ?config () = + let backend = create_backend ?stop ?config () in + let (module B : OT.Collector.BACKEND) = backend in + OT.Collector.set_backend backend; B.cleanup -let setup ?stop ?(config = Config.make ()) ?(enable = true) () = +let setup ?stop ?config ?(enable = true) () = if enable then ( - let cleanup = setup_ ?stop ~config () in + let cleanup = setup_ ?stop ?config () in at_exit cleanup ) diff --git a/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.mli b/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.mli index 12e20fc7..fa25dcfe 100644 --- a/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.mli +++ b/src/client-cohttp-lwt/opentelemetry_client_cohttp_lwt.mli @@ -18,6 +18,12 @@ val set_headers : (string * string) list -> unit module Config = Config +val create_backend : + ?stop:bool Atomic.t -> + ?config:Config.t -> + unit -> + (module Opentelemetry.Collector.BACKEND) + val setup : ?stop:bool Atomic.t -> ?config:Config.t -> ?enable:bool -> unit -> unit (** Setup endpoint. This modifies {!Opentelemetry.Collector.backend}. diff --git a/src/client-ocurl/opentelemetry_client_ocurl.ml b/src/client-ocurl/opentelemetry_client_ocurl.ml index e0d6a596..2ad72b95 100644 --- a/src/client-ocurl/opentelemetry_client_ocurl.ml +++ b/src/client-ocurl/opentelemetry_client_ocurl.ml @@ -325,7 +325,8 @@ end = struct ) end -let mk_backend ~stop ~config () : (module Collector.BACKEND) = +let create_backend ?(stop = Atomic.make false) + ?(config : Config.t = Config.make ()) () : (module Collector.BACKEND) = let module M = struct open Opentelemetry.Proto open Opentelemetry.Collector @@ -426,8 +427,9 @@ let setup_ticker_thread ~stop ~sleep_ms (module B : Collector.BACKEND) () = in start_bg_thread tick_loop -let setup_ ?(stop = Atomic.make false) ~(config : Config.t) () = - let ((module B) as backend) = mk_backend ~stop ~config () in +let setup_ ?(stop = Atomic.make false) ?(config : Config.t = Config.make ()) () + = + let ((module B) as backend) = create_backend ~stop ~config () in Opentelemetry.Collector.set_backend backend; if config.ticker_thread then ( @@ -437,15 +439,15 @@ let setup_ ?(stop = Atomic.make false) ~(config : Config.t) () = B.cleanup -let setup ?stop ?(config = Config.make ()) ?(enable = true) () = +let setup ?stop ?config ?(enable = true) () = if enable then ( - let cleanup = setup_ ?stop ~config () in + let cleanup = setup_ ?stop ?config () in at_exit cleanup ) -let with_setup ?stop ?(config = Config.make ()) ?(enable = true) () f = +let with_setup ?stop ?config ?(enable = true) () f = if enable then ( - let cleanup = setup_ ?stop ~config () in + let cleanup = setup_ ?stop ?config () in Fun.protect ~finally:cleanup f ) else f () diff --git a/src/client-ocurl/opentelemetry_client_ocurl.mli b/src/client-ocurl/opentelemetry_client_ocurl.mli index 7d3ccc0a..a2c01177 100644 --- a/src/client-ocurl/opentelemetry_client_ocurl.mli +++ b/src/client-ocurl/opentelemetry_client_ocurl.mli @@ -17,6 +17,12 @@ val set_headers : (string * string) list -> unit module Atomic = Opentelemetry_atomic.Atomic module Config = Config +val create_backend : + ?stop:bool Atomic.t -> + ?config:Config.t -> + unit -> + (module Opentelemetry.Collector.BACKEND) + val setup : ?stop:bool Atomic.t -> ?config:Config.t -> ?enable:bool -> unit -> unit (** Setup endpoint. This modifies {!Opentelemetry.Collector.backend}. diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index fac821c9..d22247d0 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -147,6 +147,69 @@ module Collector = struct type backend = (module BACKEND) + module Noop_backend : BACKEND = struct + let noop_sender _ ~ret = ret () + + let send_trace : Trace.resource_spans list sender = { send = noop_sender } + + let send_metrics : Metrics.resource_metrics list sender = + { send = noop_sender } + + let send_logs : Logs.resource_logs list sender = { send = noop_sender } + + let signal_emit_gc_metrics () = () + + let tick () = () + + let set_on_tick_callbacks _cbs = () + + let cleanup () = () + end + + module Debug_backend (B : BACKEND) : BACKEND = struct + open Proto + + let send_trace : Trace.resource_spans list sender = + { + send = + (fun l ~ret -> + Format.eprintf "SPANS: %a@." + (Format.pp_print_list Trace.pp_resource_spans) + l; + B.send_trace.send l ~ret); + } + + let send_metrics : Metrics.resource_metrics list sender = + { + send = + (fun l ~ret -> + Format.eprintf "METRICS: %a@." + (Format.pp_print_list Metrics.pp_resource_metrics) + l; + B.send_metrics.send l ~ret); + } + + let send_logs : Logs.resource_logs list sender = + { + send = + (fun l ~ret -> + Format.eprintf "LOGS: %a@." + (Format.pp_print_list Logs.pp_resource_logs) + l; + B.send_logs.send l ~ret); + } + + let signal_emit_gc_metrics () = B.signal_emit_gc_metrics () + + let tick () = B.tick () + + let set_on_tick_callbacks cbs = B.set_on_tick_callbacks cbs + + let cleanup () = B.cleanup () + end + + let debug_backend : backend = (module Debug_backend (Noop_backend)) + (* hidden *) open struct let on_tick_cbs_ = ref [] @@ -193,6 +256,14 @@ module Collector = struct match !backend with | None -> () | Some (module B) -> B.tick () + + let with_setup_debug_backend b ?(enable = true) () f = + let (module B : BACKEND) = b in + if enable then ( + set_backend b; + Fun.protect ~finally:B.cleanup f + ) else + f () end module Util_ = struct From 2be60c7ba614a8791c4d01cfae4392d9695520a8 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 19 Sep 2023 23:57:42 +0000 Subject: [PATCH 19/25] tests: Add failing test for threaded implicit-scope --- dune-project | 1 + opentelemetry.opam | 1 + tests/implicit_scope/sync/dune | 3 + .../sync/test_implicit_scope_sync.ml | 74 +++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 tests/implicit_scope/sync/dune create mode 100644 tests/implicit_scope/sync/test_implicit_scope_sync.ml diff --git a/dune-project b/dune-project index a15d0426..082551c5 100644 --- a/dune-project +++ b/dune-project @@ -26,6 +26,7 @@ ptime ambient-context (odoc :with-doc) + (alcotest :with-test) (pbrt (>= 2.3)) (ocaml-lsp-server :with-dev-setup) diff --git a/opentelemetry.opam b/opentelemetry.opam index 168a88e6..d222450d 100644 --- a/opentelemetry.opam +++ b/opentelemetry.opam @@ -14,6 +14,7 @@ depends: [ "ptime" "ambient-context" "odoc" {with-doc} + "alcotest" {with-test} "pbrt" {>= "2.3"} "ocaml-lsp-server" {with-dev-setup} "ocamlformat" {with-dev-setup & >= "0.24" & < "0.25"} diff --git a/tests/implicit_scope/sync/dune b/tests/implicit_scope/sync/dune new file mode 100644 index 00000000..072eee77 --- /dev/null +++ b/tests/implicit_scope/sync/dune @@ -0,0 +1,3 @@ +(tests + (names test_implicit_scope_sync) + (libraries alcotest opentelemetry opentelemetry-client-cohttp-lwt)) diff --git a/tests/implicit_scope/sync/test_implicit_scope_sync.ml b/tests/implicit_scope/sync/test_implicit_scope_sync.ml new file mode 100644 index 00000000..d8bf632b --- /dev/null +++ b/tests/implicit_scope/sync/test_implicit_scope_sync.ml @@ -0,0 +1,74 @@ +open Alcotest +module Otel = Opentelemetry + +let spans_emitted : Otel.Proto.Trace.resource_spans list ref = ref [] + +module Test_backend = struct + open Otel.Collector + open Otel.Proto + include Noop_backend + + let record_emitted_spans (l : Trace.resource_spans list) ~ret = + spans_emitted := l @ !spans_emitted; + ret () + + let send_trace : Trace.resource_spans list sender = + { send = record_emitted_spans } +end + +let with_test_backend f = + (* uncomment for eprintf debugging: *) + (* let module Debug_and_test_backend = Otel.Collector.Debug_backend (Test_backend) in + let backend = (module Debug_and_test_backend : Otel.Collector.BACKEND) in *) + let backend = (module Test_backend : Otel.Collector.BACKEND) in + Otel.Collector.with_setup_debug_backend backend () f + +let bytes_to_hex = Otel.Util_.bytes_to_hex + +let test_stack_based_implicit_scope () = + let run () = + Otel.Trace.with_ "first trace" @@ fun _scope -> + Thread.delay 0.2; + Otel.Trace.with_ "second trace" @@ fun _scope -> + Thread.delay 0.2; + Otel.Trace.with_ "third trace" @@ fun _scope -> + Thread.delay 0.2; + () + in + with_test_backend @@ fun () -> + (* start *) + run (); + check' int ~msg:"count of spans emitted" + ~actual:(List.length !spans_emitted) + ~expected:3; + let open Otel.Proto.Trace in + let f prev_span_id { scope_spans; _ } = + Format.printf "\n%a@\n" (Format.pp_print_list pp_scope_spans) scope_spans; + check' int ~msg:"count of scope_spans in emitted span" + ~actual:(List.length scope_spans) ~expected:1; + let { scope; spans; _ } = List.hd scope_spans in + check' bool ~msg:"scope exists in emitted span" + ~actual:(Option.is_some scope) ~expected:true; + check' int ~msg:"count of spans in scope_span" ~actual:(List.length spans) + ~expected:1; + let { name; trace_id; span_id; parent_span_id; _ } = List.hd spans in + Printf.printf + "name='%s' trace_id='%s' span_id='%s' parent_span_id='%s' \ + prev_span_id='%s'\n" + name (bytes_to_hex trace_id) (bytes_to_hex span_id) + (bytes_to_hex parent_span_id) + (bytes_to_hex prev_span_id); + check' string ~msg:"previous span is parent" + ~actual:(bytes_to_hex parent_span_id) + ~expected:(bytes_to_hex prev_span_id); + span_id + in + List.fold_left f (Bytes.of_string "") !spans_emitted |> ignore + +let suite = + [ + test_case "stack-based implicit scope" `Quick + test_stack_based_implicit_scope; + ] + +let () = Alcotest.run "implicit scope" [ "sync", suite ] From caade960b6e454e5184fae6cd2508309b54f032a Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Tue, 19 Sep 2023 23:57:53 +0000 Subject: [PATCH 20/25] fix: Repair ambient scope --- src/opentelemetry.ml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index d22247d0..c65133b8 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -810,8 +810,6 @@ module Trace = struct let start_time = Timestamp_ns.now_unix_ns () in let span_id = Span_id.create () in let scope = { trace_id; span_id; events = []; attrs } in - (* set global scope in this thread *) - Scope.with_ambient_scope scope @@ fun () -> (* called once we're done, to emit a span *) let finally res = let status = @@ -830,7 +828,10 @@ module Trace = struct in emit ?service_name [ span ] in - let thunk () = cb scope in + let thunk () = + (* set global scope in this thread *) + Scope.with_ambient_scope scope @@ fun () -> cb scope + in thunk, finally (** Sync span guard. From 057a27abe9bc0897888156137d05611ece82b9cc Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 20 Sep 2023 02:13:15 +0000 Subject: [PATCH 21/25] (- fix) Use new TLS-module naming from ac.0.1.0 --- src/trace/dune | 11 +++++------ src/trace/opentelemetry_trace.ml | 2 +- src/trace/opentelemetry_trace.mli | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/trace/dune b/src/trace/dune index 69d9e44f..6207563d 100644 --- a/src/trace/dune +++ b/src/trace/dune @@ -1,7 +1,6 @@ - (library - (name opentelemetry_trace) - (public_name opentelemetry.trace) - (synopsis "Use opentelemetry as a collector for trace") - (optional) - (libraries ambient-context trace opentelemetry)) + (name opentelemetry_trace) + (public_name opentelemetry.trace) + (synopsis "Use opentelemetry as a collector for trace") + (optional) + (libraries ambient-context ambient-context.tls trace opentelemetry)) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index bc9bb87b..16990b73 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -1,6 +1,6 @@ module Otel = Opentelemetry module Otrace = Trace (* ocaml-trace *) -module TLS = Ambient_context_tls.Thread_local +module TLS = Ambient_context_tls.TLS module Well_known = struct let spankind_key = "otrace.spankind" diff --git a/src/trace/opentelemetry_trace.mli b/src/trace/opentelemetry_trace.mli index bbcb28d8..8d8e934d 100644 --- a/src/trace/opentelemetry_trace.mli +++ b/src/trace/opentelemetry_trace.mli @@ -1,6 +1,6 @@ module Otel := Opentelemetry module Otrace := Trace -module TLS := Ambient_context_tls.Thread_local +module TLS := Ambient_context_tls.TLS (** [opentelemetry.trace] implements a {!Trace_core.Collector} for {{:https://v3.ocaml.org/p/trace} ocaml-trace}. From 1809075095323281f653876256ff408e302474ce Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 20 Sep 2023 02:30:53 +0000 Subject: [PATCH 22/25] trace-collector: Support for floats, etc from trace --- src/opentelemetry.ml | 6 ++++-- src/trace/opentelemetry_trace.ml | 17 +++++++++++++++-- src/trace/opentelemetry_trace.mli | 12 ++++++++++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index c65133b8..4ad36206 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -435,6 +435,7 @@ type value = [ `Int of int | `String of string | `Bool of bool + | `Float of float | `None ] @@ -448,6 +449,7 @@ let _conv_value = | `Int i -> Some (Int_value (Int64.of_int i)) | `String s -> Some (String_value s) | `Bool b -> Some (Bool_value b) + | `Float f -> Some (Double_value f) | `None -> None (**/**) @@ -679,7 +681,7 @@ module Span : sig val id : t -> Span_id.t type key_value = - string * [ `Int of int | `String of string | `Bool of bool | `None ] + string * [ `Int of int | `String of string | `Bool of bool | `Float of float | `None ] val create : ?kind:kind -> @@ -716,7 +718,7 @@ end = struct | Span_kind_consumer type key_value = - string * [ `Int of int | `String of string | `Bool of bool | `None ] + string * [ `Int of int | `String of string | `Bool of bool | `Float of float | `None ] type nonrec status_code = status_status_code = | Status_code_unset diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index 16990b73..b7a6162d 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -215,6 +215,19 @@ module Internal = struct let otel_span = exit_span' otrace_id sb in Otel.Trace.emit [ otel_span ] + let add_data_to_span otrace_id data = + let active_spans = Active_spans.get () in + match Active_span_tbl.find_opt active_spans.tbl otrace_id with + | None -> + (* FIXME: some kind of error/debug logging *) + () + | Some sb -> + Active_span_tbl.replace active_spans.tbl otrace_id + { sb with data = sb.data @ data } + + let add_data_to_manual_span Otrace.{ span = otrace_id; _ } data = + add_data_to_span otrace_id data + let message ?span ~data:_ msg : unit = (* gather information from context *) let old_scope = Otel.Scope.get_ambient_scope () in @@ -235,11 +248,11 @@ module Internal = struct let name_thread _name = () - let counter_int name cur_val : unit = + let counter_int ~data:_ name cur_val : unit = let m = Otel.Metrics.(gauge ~name [ int cur_val ]) in Otel.Metrics.emit [ m ] - let counter_float name cur_val : unit = + let counter_float ~data:_ name cur_val : unit = let m = Otel.Metrics.(gauge ~name [ float cur_val ]) in Otel.Metrics.emit [ m ] end diff --git a/src/trace/opentelemetry_trace.mli b/src/trace/opentelemetry_trace.mli index 8d8e934d..fd3aa91b 100644 --- a/src/trace/opentelemetry_trace.mli +++ b/src/trace/opentelemetry_trace.mli @@ -126,6 +126,12 @@ module Internal : sig (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 -> @@ -138,9 +144,11 @@ module Internal : sig val name_thread : string -> unit - val counter_int : string -> int -> unit + val counter_int : + data:(string * Otrace.user_data) list -> string -> int -> unit - val counter_float : string -> float -> unit + val counter_float : + data:(string * Otrace.user_data) list -> string -> float -> unit end type span_begin = { From 28f750056f7c330fd190e569ee837c687930c77b Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 20 Sep 2023 14:45:39 +0000 Subject: [PATCH 23/25] deps: Update ocaml-trace dep to 0.4 --- dune-project | 4 ++-- opentelemetry.opam | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dune-project b/dune-project index 082551c5..a6bb23ae 100644 --- a/dune-project +++ b/dune-project @@ -39,8 +39,8 @@ (conflicts (trace (or - (< 0.3) - (>= 0.4)))) + (< 0.4) + (>= 0.5)))) (tags (instrumentation tracing opentelemetry datadog jaeger))) diff --git a/opentelemetry.opam b/opentelemetry.opam index d222450d..b2721d44 100644 --- a/opentelemetry.opam +++ b/opentelemetry.opam @@ -21,7 +21,7 @@ depends: [ ] depopts: ["trace"] conflicts: [ - "trace" {< "0.3" | >= "0.4"} + "trace" {< "0.4" | >= "0.5"} ] build: [ ["dune" "subst"] {dev} From 76c18c55c2ed484766ac8693b9f3107e0e65bb1e Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 20 Sep 2023 15:14:10 +0000 Subject: [PATCH 24/25] trace-collector: Pass user-data to OTel metrics --- src/trace/opentelemetry_trace.ml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/trace/opentelemetry_trace.ml b/src/trace/opentelemetry_trace.ml index b7a6162d..892fdb9f 100644 --- a/src/trace/opentelemetry_trace.ml +++ b/src/trace/opentelemetry_trace.ml @@ -248,12 +248,14 @@ module Internal = struct let name_thread _name = () - let counter_int ~data:_ name cur_val : unit = - let m = Otel.Metrics.(gauge ~name [ int cur_val ]) in + let counter_int ~data name cur_val : unit = + let _kind, attrs = otel_attrs_of_otrace_data data in + let m = Otel.Metrics.(gauge ~name [ int ~attrs cur_val ]) in Otel.Metrics.emit [ m ] - let counter_float ~data:_ name cur_val : unit = - let m = Otel.Metrics.(gauge ~name [ float cur_val ]) in + let counter_float ~data name cur_val : unit = + let _kind, attrs = otel_attrs_of_otrace_data data in + let m = Otel.Metrics.(gauge ~name [ float ~attrs cur_val ]) in Otel.Metrics.emit [ m ] end end From 9a834b39f7478f7a55efae91caffa38a4f921e78 Mon Sep 17 00:00:00 2001 From: Elliott Cable Date: Wed, 20 Sep 2023 15:15:58 +0000 Subject: [PATCH 25/25] dune fmt --- dune | 3 ++- src/client-cohttp-lwt/dune | 3 ++- src/dune | 3 ++- src/opentelemetry.ml | 16 ++++++++++++++-- tests/bin/dune | 6 ++++-- tests/dune | 6 ++---- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/dune b/dune index 575b5a09..6b9e175b 100644 --- a/dune +++ b/dune @@ -1,3 +1,4 @@ (env (_ - (flags :standard -warn-error -a+8 -w +a-4-30-40-41-42-44-48-70 -strict-sequence))) + (flags :standard -warn-error -a+8 -w +a-4-30-40-41-42-44-48-70 + -strict-sequence))) diff --git a/src/client-cohttp-lwt/dune b/src/client-cohttp-lwt/dune index 12d79702..f4198731 100644 --- a/src/client-cohttp-lwt/dune +++ b/src/client-cohttp-lwt/dune @@ -2,6 +2,7 @@ (name opentelemetry_client_cohttp_lwt) (public_name opentelemetry-client-cohttp-lwt) (synopsis "Opentelemetry collector using cohttp+lwt+unix") - (preprocess (pps lwt_ppx)) + (preprocess + (pps lwt_ppx)) (libraries opentelemetry lwt cohttp-lwt cohttp-lwt-unix pbrt mtime mtime.clock.os)) diff --git a/src/dune b/src/dune index 99867650..f91ee2aa 100644 --- a/src/dune +++ b/src/dune @@ -2,7 +2,8 @@ (name opentelemetry) (synopsis "API for opentelemetry instrumentation") (flags :standard -warn-error -a+8) - (libraries ambient-context ptime ptime.clock.os pbrt threads opentelemetry.atomic) + (libraries ambient-context ptime ptime.clock.os pbrt threads + opentelemetry.atomic) (public_name opentelemetry)) ; ### protobuf rules ### diff --git a/src/opentelemetry.ml b/src/opentelemetry.ml index 12f43f56..66ca27d2 100644 --- a/src/opentelemetry.ml +++ b/src/opentelemetry.ml @@ -682,7 +682,13 @@ module Span : sig val id : t -> Span_id.t type key_value = - string * [ `Int of int | `String of string | `Bool of bool | `Float of float | `None ] + string + * [ `Int of int + | `String of string + | `Bool of bool + | `Float of float + | `None + ] val create : ?kind:kind -> @@ -719,7 +725,13 @@ end = struct | Span_kind_consumer type key_value = - string * [ `Int of int | `String of string | `Bool of bool | `Float of float | `None ] + string + * [ `Int of int + | `String of string + | `Bool of bool + | `Float of float + | `None + ] type nonrec status_code = status_status_code = | Status_code_unset diff --git a/tests/bin/dune b/tests/bin/dune index 7ac29933..7d0ebe08 100644 --- a/tests/bin/dune +++ b/tests/bin/dune @@ -6,8 +6,10 @@ (executable (name emit1_cohttp) (modules emit1_cohttp) - (preprocess (pps lwt_ppx)) - (libraries unix opentelemetry opentelemetry-lwt opentelemetry-client-cohttp-lwt lwt.unix)) + (preprocess + (pps lwt_ppx)) + (libraries unix opentelemetry opentelemetry-lwt + opentelemetry-client-cohttp-lwt lwt.unix)) (executable (name cohttp_client) diff --git a/tests/dune b/tests/dune index 2d608045..c005f866 100644 --- a/tests/dune +++ b/tests/dune @@ -1,6 +1,4 @@ (tests (names test_trace_context test_get_url) - (libraries - opentelemetry - opentelemetry-client-ocurl - opentelemetry-client-cohttp-lwt)) + (libraries opentelemetry opentelemetry-client-ocurl + opentelemetry-client-cohttp-lwt))