Source file span_ctx.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
open Common_

(* see: https://opentelemetry.io/docs/specs/otel/trace/api/#spancontext *)

(* TODO: trace state *)

external int_of_bool : bool -> int = "%identity"

module Flags = struct
  let sampled = 1

  let remote = 2
end

type t = {
  trace_id: Trace_id.t;
  parent_id: Span_id.t;
  flags: int;
}

let dummy = { trace_id = Trace_id.dummy; parent_id = Span_id.dummy; flags = 0 }

let make ?(remote = false) ?(sampled = false) ~trace_id ~parent_id () : t =
  let flags =
    0
    lor (int_of_bool remote lsl Flags.remote)
    lor (int_of_bool sampled lsl Flags.sampled)
  in
  { trace_id; parent_id; flags }

let[@inline] is_valid self =
  Trace_id.is_valid self.trace_id && Span_id.is_valid self.parent_id

let[@inline] sampled self = self.flags land (1 lsl Flags.sampled) != 0

let[@inline] is_remote self = self.flags land (1 lsl Flags.remote) != 0

let[@inline] trace_id self = self.trace_id

let[@inline] parent_id self = self.parent_id

let to_w3c_trace_context (self : t) : bytes =
  let bs = Bytes.create 55 in
  Bytes.set bs 0 '0';
  Bytes.set bs 1 '0';
  Bytes.set bs 2 '-';
  Trace_id.to_hex_into self.trace_id bs 3;
  (* +32 *)
  Bytes.set bs (3 + 32) '-';
  Span_id.to_hex_into self.parent_id bs 36;
  (* +16 *)
  Bytes.set bs 52 '-';
  Bytes.set bs 53 '0';
  Bytes.set bs 54
    (if sampled self then
       '1'
     else
       '0');
  bs

let of_w3c_trace_context bs : _ result =
  try
    if Bytes.length bs <> 55 then invalid_arg "trace context must be 55 bytes";
    (match int_of_string_opt (Bytes.sub_string bs 0 2) with
    | Some 0 -> ()
    | Some n -> invalid_arg @@ spf "version is %d, expected 0" n
    | None -> invalid_arg "expected 2-digit version");
    if Bytes.get bs 2 <> '-' then invalid_arg "expected '-' before trace_id";
    let trace_id =
      try Trace_id.of_hex_substring (Bytes.unsafe_to_string bs) 3
      with Invalid_argument msg -> invalid_arg (spf "in trace id: %s" msg)
    in
    if Bytes.get bs (3 + 32) <> '-' then
      invalid_arg "expected '-' before parent_id";
    let parent_id =
      try Span_id.of_hex_substring (Bytes.unsafe_to_string bs) 36
      with Invalid_argument msg -> invalid_arg (spf "in span id: %s" msg)
    in
    if Bytes.get bs 52 <> '-' then invalid_arg "expected '-' after parent_id";
    let sampled =
      match int_of_string_opt ("0x" ^ Bytes.sub_string bs 53 2) with
      | Some flags -> flags land 1 = 1
      | None -> false
    in

    (* ignore other flags *)
    Ok (make ~remote:true ~sampled ~trace_id ~parent_id ())
  with Invalid_argument msg -> Error msg

let of_w3c_trace_context_exn bs =
  match of_w3c_trace_context bs with
  | Ok t -> t
  | Error msg -> invalid_arg @@ spf "invalid w3c trace context: %s" msg

let k_ambient : t Hmap.key = Hmap.Key.create ()