Skip to content

Commit 1f997b1

Browse files
committed
CP-311150: add opentelemetry wrappers for XAPI client RPC calls
Signed-off-by: Edwin Török <edwin.torok@citrix.com>
1 parent e3732fa commit 1f997b1

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
let rpc_system_name = "rpc.system.name"
2+
3+
let rpc_method = "rpc.method"
4+
5+
let error_type = "error.type"
6+
7+
let other = "_OTHER"
8+
9+
let rpc_message = "rpc.message"
10+
11+
let rpc_message_id = "rpc.message.id"
12+
13+
let rpc_message_type = "rpc.message.type"
14+
15+
let sent = "SENT"
16+
17+
let received = "RECEIVED"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(** @see <https://opentelemetry.io/docs/specs/semconv/rpc/> *)
2+
3+
val rpc_system_name : string
4+
5+
val rpc_method : string
6+
7+
val error_type : string
8+
9+
val other : string
10+
11+
val rpc_message : string
12+
13+
val rpc_message_id : string
14+
15+
val rpc_message_type : string
16+
17+
val sent : string
18+
19+
val received : string

ocaml/quicktest/trace/rpclib/dune

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(library
2+
(name quicktest_trace_rpc)
3+
(libraries
4+
quicktest_trace
5+
rpclib.core
6+
))
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
(*
2+
* Copyright (c) Cloud Software Group, Inc
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published
6+
* by the Free Software Foundation; version 2.1 only. with the special
7+
* exception on linking described in file LICENSE.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*)
14+
open Quicktest_trace
15+
open Conventions
16+
17+
let id_sent = Atomic.make 1
18+
19+
let id_received = Atomic.make 1
20+
21+
let rpc_event ~id kind =
22+
let id = Atomic.fetch_and_add id 1 in
23+
Opentelemetry.Event.make
24+
~attrs:[(rpc_message_id, `Int id); (rpc_message_type, `String kind)]
25+
rpc_message
26+
27+
let code_of_response response =
28+
match response.Rpc.contents with
29+
| Rpc.Enum (Rpc.String code :: _) ->
30+
code
31+
| _ ->
32+
other
33+
34+
let attrs_of_error response =
35+
[(error_type, `String (code_of_response response))]
36+
37+
let rec any_value_of_rpc =
38+
let open Opentelemetry_proto.Common in
39+
function
40+
| Rpc.Null ->
41+
None
42+
| Rpc.Bool b ->
43+
Some (Bool_value b)
44+
| Rpc.Int32 i32 ->
45+
Some (Int_value (Int64.of_int32 i32))
46+
| Rpc.Int i ->
47+
Some (Int_value i)
48+
| Rpc.Float f ->
49+
Some (Double_value f)
50+
| Rpc.Base64 s ->
51+
(* don't log the full Base64 entry, the UEFI NVRAM is huge *)
52+
Some (String_value (Printf.sprintf "base64(len=%d)" (String.length s)))
53+
| Rpc.DateTime s | Rpc.String s ->
54+
Some (String_value s)
55+
| Rpc.Enum lst ->
56+
let values = lst |> List.filter_map any_value_of_rpc in
57+
Some (Array_value (make_array_value ~values ()))
58+
| Rpc.Dict dict ->
59+
let values =
60+
dict
61+
|> List.map (fun (key, v) ->
62+
make_key_value ~key ~value:(any_value_of_rpc v) ()
63+
)
64+
in
65+
Some (Kvlist_value (make_key_value_list ~values ()))
66+
67+
let log_rpc ?(time_unix_nano = Opentelemetry.Timestamp_ns.now_unix_ns ()) scope
68+
key rpc =
69+
let trace_id = Scope.trace_id scope |> Opentelemetry.Trace_id.to_bytes
70+
and span_id = Scope.span_id scope |> Opentelemetry.Span_id.to_bytes in
71+
let open Opentelemetry_proto in
72+
let body =
73+
Some
74+
Common.(
75+
Kvlist_value
76+
(make_key_value_list
77+
~values:[make_key_value ~key ~value:(any_value_of_rpc rpc) ()]
78+
()
79+
)
80+
)
81+
in
82+
Logs.make_log_record ~time_unix_nano ~observed_time_unix_nano:time_unix_nano
83+
~severity_number:Severity_number_trace ~severity_text:"TRACE" ~body
84+
~attributes:[] ~dropped_attributes_count:0l ~flags:0l ~trace_id ~span_id ()
85+
86+
let wrap ?(log_body = false) rpc call =
87+
let attrs =
88+
[(rpc_system_name, `String "xmlrpc"); (rpc_method, `String call.Rpc.name)]
89+
in
90+
let () =
91+
if log_body then
92+
match Scope.get_ambient_scope () with
93+
| None ->
94+
()
95+
| Some scope ->
96+
(* log the actual bodies of the RPC,
97+
this is only for testing purposes, since they may contain secrets *)
98+
Scope.add_log scope (fun () ->
99+
log_rpc scope call.Rpc.name (Rpc.Enum call.Rpc.params)
100+
)
101+
in
102+
Trace.with_ ~kind:Opentelemetry.Span.Span_kind_client ~attrs call.Rpc.name
103+
@@ fun scope ->
104+
Scope.add_event scope (fun () -> rpc_event ~id:id_sent sent) ;
105+
106+
let (response : Rpc.response) = rpc call in
107+
108+
Scope.add_event scope (fun () -> rpc_event ~id:id_received received) ;
109+
if log_body then
110+
Scope.add_delayed_log scope (fun () ->
111+
log_rpc scope "response" response.contents
112+
) ;
113+
114+
if not response.Rpc.success then begin
115+
Scope.set_status scope
116+
@@ Span_status.make ~code:Status_code_error
117+
~message:(code_of_response response) ;
118+
Scope.add_attrs scope (fun () -> attrs_of_error response)
119+
end ;
120+
response
121+
122+
let http_headers () =
123+
match Scope.get_ambient_scope () with
124+
| None ->
125+
[]
126+
| Some scope ->
127+
let open Opentelemetry.Trace_context.Traceparent in
128+
let traceparent =
129+
to_value ~trace_id:(Scope.trace_id scope)
130+
~parent_id:(Scope.span_id scope) ()
131+
in
132+
Scope.add_attrs scope (fun () -> [(name, `String traceparent)]) ;
133+
[(name, traceparent)]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
(*
2+
* Copyright (c) Cloud Software Group, Inc
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published
6+
* by the Free Software Foundation; version 2.1 only. with the special
7+
* exception on linking described in file LICENSE.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*)
14+
15+
val log_rpc :
16+
?time_unix_nano:Opentelemetry.Timestamp_ns.t
17+
-> Quicktest_trace.Scope.t
18+
-> string
19+
-> Rpc.t
20+
-> Opentelemetry.Logs.t
21+
(** [log_rpc ?time_unix_nano scope key rpc] logs the [rpc] at timestamp
22+
[time_unix_nano] with the specific [key].
23+
24+
This is useful for structured logging (the full nested dictionary is
25+
retained in Opentelemetry format, it isn't converted to a string).
26+
*)
27+
28+
val wrap :
29+
?log_body:bool -> (Rpc.call -> Rpc.response) -> Rpc.call -> Rpc.response
30+
(** [wrap ?log_body rpc] wraps an [rpc] call: creates an Opentelemetry span
31+
following the semantic {!module:Conventions} for RPCs.
32+
33+
[log_body] can be set to [true] to also logs the body of the request, and on error the body of the reply.
34+
This is suitable for testing, but not production use (it may log sensitive information)
35+
*)
36+
37+
val http_headers : unit -> (string * string) list
38+
(** [http_headers ()] returns the additional HTTP headers to insert for W3C
39+
TraceContext propagation.
40+
@see <https://www.w3.org/TR/trace-context/>
41+
*)

0 commit comments

Comments
 (0)