mirror of
https://github.com/ocaml-tracing/ocaml-opentelemetry.git
synced 2026-03-07 18:37:56 -05:00
Bug #1: Fix worker count logic in generic_consumer - Was: min 2 (max 500 n_workers) - always created 2 workers - Now: max 2 (min 500 n_workers) - properly clamps between 2-500 - Impact: Worker configuration was completely ignored Bug #2: Handle missing dot in __FUNCTION__ name - Added exception handling for String.rindex in trace span creation - Prevents crash when tracing top-level or non-module functions - Uses option type for module_path when no dot is present - Scoped try/catch to only parsing logic
239 lines
7.9 KiB
OCaml
239 lines
7.9 KiB
OCaml
open Common_
|
|
|
|
module Extensions = struct
|
|
type Otrace.extension_event +=
|
|
| Ev_link_span of Otrace.span * OTEL.Span_ctx.t
|
|
| Ev_record_exn of {
|
|
sp: Otrace.span;
|
|
exn: exn;
|
|
bt: Printexc.raw_backtrace;
|
|
}
|
|
| Ev_set_span_kind of Otrace.span * OTEL.Span_kind.t
|
|
| Ev_set_span_status of Otrace.span * OTEL.Span_status.t
|
|
|
|
type Otrace.metric +=
|
|
| Metric_hist of OTEL.Metrics.histogram_data_point
|
|
| Metric_sum_int of int
|
|
| Metric_sum_float of float
|
|
end
|
|
|
|
open Extensions
|
|
|
|
open struct
|
|
type Otrace.span += Span_otel of OTEL.Span.t
|
|
end
|
|
|
|
open struct
|
|
type state = {
|
|
clock: Opentelemetry_core.Clock.t;
|
|
exporter: OTEL.Exporter.t;
|
|
}
|
|
|
|
let create_state ~(exporter : OTEL.Exporter.t) () : state =
|
|
let clock = exporter.clock in
|
|
{ clock; exporter }
|
|
|
|
(* sanity check: otrace meta-map must be the same as hmap *)
|
|
let () = ignore (fun (k : _ Hmap.key) : _ Ambient_context.Context.key -> k)
|
|
|
|
(** Key to access the current span context. *)
|
|
let k_span_ctx : OTEL.Span_ctx.t Ambient_context.Context.key =
|
|
Ambient_context.Context.new_key ()
|
|
|
|
let enter_span (self : state) ~__FUNCTION__ ~__FILE__ ~__LINE__ ~level:_
|
|
~params:_ ~(data : (_ * Otrace.user_data) list) ~parent name : Otrace.span
|
|
=
|
|
let start_time = OTEL.Clock.now self.clock in
|
|
let trace_id, parent_id =
|
|
match parent with
|
|
| Otrace.P_some (Span_otel sp) ->
|
|
OTEL.Span.trace_id sp, Some (OTEL.Span.id sp)
|
|
| _ ->
|
|
(match Ambient_context.get k_span_ctx with
|
|
| Some sp_ctx ->
|
|
OTEL.Span_ctx.trace_id sp_ctx, Some (OTEL.Span_ctx.parent_id sp_ctx)
|
|
| None -> OTEL.Trace_id.create (), None)
|
|
in
|
|
|
|
let span_id = OTEL.Span_id.create () in
|
|
|
|
let attrs =
|
|
("code.filepath", `String __FILE__)
|
|
:: ("code.lineno", `Int __LINE__)
|
|
:: data
|
|
in
|
|
|
|
let otel_sp : OTEL.Span.t =
|
|
OTEL.Span.make ~start_time ~id:span_id ~trace_id ~attrs ?parent:parent_id
|
|
~end_time:0L name
|
|
in
|
|
|
|
(* add more data if [__FUNCTION__] is present *)
|
|
(match __FUNCTION__ with
|
|
| Some __FUNCTION__ when OTEL.Span.is_not_dummy otel_sp ->
|
|
let function_name, module_path =
|
|
try
|
|
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
|
|
function_name, Some module_path
|
|
with Not_found ->
|
|
(* __FUNCTION__ has no dot, use it as-is *)
|
|
__FUNCTION__, None
|
|
in
|
|
let attrs =
|
|
("code.function", `String function_name)
|
|
::
|
|
(match module_path with
|
|
| Some module_path -> [ "code.namespace", `String module_path ]
|
|
| None -> [])
|
|
in
|
|
OTEL.Span.add_attrs otel_sp attrs
|
|
| _ -> ());
|
|
|
|
Span_otel otel_sp
|
|
|
|
let exit_span (self : state) sp =
|
|
match sp with
|
|
| Span_otel span when OTEL.Span.is_not_dummy span ->
|
|
(* emit the span after setting the end timestamp *)
|
|
let end_time = OTEL.Clock.now self.clock in
|
|
OTEL.Proto.Trace.span_set_end_time_unix_nano span end_time;
|
|
OTEL.Exporter.send_trace self.exporter [ span ]
|
|
| _ -> ()
|
|
|
|
let add_data_to_span _self span (data : (_ * Otrace.user_data) list) =
|
|
match span with
|
|
| Span_otel sp -> OTEL.Span.add_attrs sp data
|
|
| _ -> ()
|
|
|
|
let severity_of_level : Trace_core.Level.t -> _ = function
|
|
| Trace -> OTEL.Log_record.Severity_number_trace
|
|
| Debug1 -> OTEL.Log_record.Severity_number_debug
|
|
| Debug2 -> OTEL.Log_record.Severity_number_debug2
|
|
| Debug3 -> OTEL.Log_record.Severity_number_debug3
|
|
| Error -> OTEL.Log_record.Severity_number_error
|
|
| Info -> OTEL.Log_record.Severity_number_info
|
|
| Warning -> OTEL.Log_record.Severity_number_warn
|
|
|
|
let message (self : state) ~(level : Trace_core.Level.t) ~params:_ ~data ~span
|
|
msg : unit =
|
|
let observed_time_unix_nano = OTEL.Clock.now self.clock in
|
|
let trace_id, span_id =
|
|
match span with
|
|
| Some (Span_otel sp) ->
|
|
Some (OTEL.Span.trace_id sp), Some (OTEL.Span.id sp)
|
|
| _ ->
|
|
(match Ambient_context.get k_span_ctx with
|
|
| Some sp ->
|
|
Some (OTEL.Span_ctx.trace_id sp), Some (OTEL.Span_ctx.parent_id sp)
|
|
| _ -> None, None)
|
|
in
|
|
|
|
let severity = severity_of_level level in
|
|
let log =
|
|
OTEL.Log_record.make ~severity ?trace_id ?span_id ~attrs:data
|
|
~observed_time_unix_nano (`String msg)
|
|
in
|
|
OTEL.Exporter.send_logs self.exporter [ log ]
|
|
|
|
let metric (self : state) ~level:_ ~params:_ ~data:attrs name v : unit =
|
|
let now = OTEL.Clock.now self.clock in
|
|
let kind =
|
|
let open Trace_core.Core_ext in
|
|
match v with
|
|
| Metric_int i -> `gauge (OTEL.Metrics.int ~attrs ~now i)
|
|
| Metric_float v -> `gauge (OTEL.Metrics.float ~attrs ~now v)
|
|
| Metric_sum_int i -> `sum (OTEL.Metrics.int ~attrs ~now i)
|
|
| Metric_sum_float v -> `sum (OTEL.Metrics.float ~attrs ~now v)
|
|
| Metric_hist h -> `hist h
|
|
| _ -> `none
|
|
in
|
|
|
|
let m =
|
|
match kind with
|
|
| `none -> []
|
|
| `gauge v -> [ OTEL.Metrics.gauge ~name [ v ] ]
|
|
| `sum v -> [ OTEL.Metrics.sum ~name [ v ] ]
|
|
| `hist h -> [ OTEL.Metrics.histogram ~name [ h ] ]
|
|
in
|
|
if m <> [] then OTEL.Exporter.send_metrics self.exporter m
|
|
|
|
let extension (_self : state) ~level:_ ev =
|
|
match ev with
|
|
| Ev_link_span (Span_otel sp1, sc2) ->
|
|
OTEL.Span.add_links sp1 [ OTEL.Span_link.of_span_ctx sc2 ]
|
|
| Ev_link_span _ -> ()
|
|
| Ev_set_span_kind (Span_otel sp, k) -> OTEL.Span.set_kind sp k
|
|
| Ev_set_span_kind _ -> ()
|
|
| Ev_set_span_status (Span_otel sp, st) -> OTEL.Span.set_status sp st
|
|
| Ev_set_span_status _ -> ()
|
|
| Ev_record_exn { sp = Span_otel sp; exn; bt } ->
|
|
OTEL.Span.record_exception sp exn bt
|
|
| Ev_record_exn _ -> ()
|
|
| _ -> ()
|
|
|
|
let shutdown self = OTEL.Exporter.shutdown self.exporter
|
|
|
|
let callbacks : state Otrace.Collector.Callbacks.t =
|
|
Otrace.Collector.Callbacks.make ~enter_span ~exit_span ~add_data_to_span
|
|
~message ~metric ~extension ~shutdown ()
|
|
end
|
|
|
|
let collector_of_exporter (exporter : OTEL.Exporter.t) : Trace_core.collector =
|
|
let st = create_state ~exporter () in
|
|
Trace_core.Collector.C_some (st, callbacks)
|
|
|
|
let with_ambient_span (sp : Otrace.span) f =
|
|
match sp with
|
|
| Span_otel sp ->
|
|
Ambient_context.with_key_bound_to k_span_ctx (OTEL.Span.to_span_ctx sp) f
|
|
| _ -> f ()
|
|
|
|
let with_ambient_span_ctx (sp : OTEL.Span_ctx.t) f =
|
|
Ambient_context.with_key_bound_to k_span_ctx sp f
|
|
|
|
let link_span_to_otel_ctx (sp1 : Otrace.span) (sp2 : OTEL.Span_ctx.t) : unit =
|
|
if Otrace.enabled () then Otrace.extension_event @@ Ev_link_span (sp1, sp2)
|
|
|
|
let link_spans (sp1 : Otrace.span) (sp2 : Otrace.span) : unit =
|
|
if Otrace.enabled () then (
|
|
match sp2 with
|
|
| Span_otel sp2 ->
|
|
Otrace.extension_event @@ Ev_link_span (sp1, OTEL.Span.to_span_ctx sp2)
|
|
| _ -> ()
|
|
)
|
|
|
|
let[@inline] set_span_kind sp k : unit =
|
|
if Otrace.enabled () then Otrace.extension_event @@ Ev_set_span_kind (sp, k)
|
|
|
|
let[@inline] set_span_status sp status : unit =
|
|
if Otrace.enabled () then
|
|
Otrace.extension_event @@ Ev_set_span_status (sp, status)
|
|
|
|
let record_exception sp exn bt : unit =
|
|
if Otrace.enabled () then
|
|
Otrace.extension_event @@ Ev_record_exn { sp; exn; bt }
|
|
|
|
(** Collector that forwards to the {b currently installed} OTEL exporter. *)
|
|
let collector_main_otel_exporter () : Otrace.collector =
|
|
collector_of_exporter OTEL.Main_exporter.dynamic_forward_to_main_exporter
|
|
|
|
let (collector
|
|
[@deprecated "use collector_of_exporter or collector_main_otel_exporter"])
|
|
=
|
|
collector_main_otel_exporter
|
|
|
|
let setup () = Otrace.setup_collector @@ collector_main_otel_exporter ()
|
|
|
|
let setup_with_otel_exporter exp : unit =
|
|
let coll = collector_of_exporter exp in
|
|
OTEL.Main_exporter.set exp;
|
|
Otrace.setup_collector coll
|
|
|
|
let setup_with_otel_backend = setup_with_otel_exporter
|
|
|
|
module Well_known = struct end
|