package ppx_trace
Install
Dune Dependency
Authors
Maintainers
Sources
sha256=34cfa5662b611c1e246f0fb8131ee605eeb90b359c105e882f51adc7e70878c3
sha512=ea47974a77a0ab26c58fe6d1bc898d4f3e6a5f865e4c1acb4188b9acd7ba8e7527d0ea7f2ae66574ceccc14f11127ee203aedba2be334d17b36c83dabff61261
README.md.html
Trace
This small library provides basic types that can be used to instrument a library or application, either by hand or via a ppx.
Features
[x] spans
[x] messages
[x] counters
[ ] other metrics?
[x] ppx to help instrumentation
Usage
To instrument your code, you can simply add trace
to your dune/opam files, and then write code like such:
let f x =
Trace.with_span ~__FILE__ ~__LINE__ "inside-f" @@ fun _sp ->
(* … code for f *)
let g x =
Trace.with_span ~__FILE__ ~__LINE__ "inside-g" @@ fun _sp ->
let y = f x in
(* … code for f *)
let () =
Some_trace_backend.setup () @@ fun () ->
let result = g 42 in
print_result result
The file test/t1.ml
follows this pattern, using trace-tef
as a simple backend that emits one JSON object per span/message:
let run () =
Trace.set_process_name "main";
Trace.set_thread_name "t1";
let n = ref 0 in
for _i = 1 to 50 do
Trace.with_span ~__FILE__ ~__LINE__ "outer.loop" @@ fun _sp ->
for _j = 2 to 5 do
incr n;
Trace.with_span ~__FILE__ ~__LINE__ "inner.loop" @@ fun _sp ->
Trace.messagef (fun k -> k "hello %d %d" _i _j);
Trace.message "world";
Trace.counter_int "n" !n
done
done
let () =
Trace_tef.with_setup ~out:(`File "trace.json") () @@ fun () ->
run ()
After running this, the file "trace.json" will contain something like:
[{"pid":2,"name":"process_name","ph":"M","args": {"name":"main"}},
{"pid":2,"tid": 3,"name":"thread_name","ph":"M","args": {"name":"t1"}},
{"pid":2,"cat":"","tid": 3,"ts": 2.00,"name":"hello 1 2","ph":"I"},
{"pid":2,"cat":"","tid": 3,"ts": 3.00,"name":"world","ph":"I"},
{"pid":2,"tid":3,"ts":4.00,"name":"c","ph":"C","args": {"n":1}},
…
Opening it in https://ui.perfetto.dev we get something like this:
ppx_trace
On OCaml >= 4.12, and with ppxlib
installed, you can install ppx_trace
. This is a preprocessor that will rewrite like so:
let%trace f x y z =
do_sth x;
do_sth y;
begin
let%trace () = "sub-span" in
do_sth z
end
This more or less corresponds to:
let f x y z =
let _trace_span = Trace_core.enter_span ~__FILE__ ~__LINE__ "Foo.f" in
match
do_sth x;
do_sth y;
begin
let _trace_span = Trace_core.enter_span ~__FILE__ ~__LINE__ "sub-span" in
match do_sth z with
| res ->
Trace_core.exit_span _trace_span;
res
| exception e ->
Trace_core.exit_span _trace_span
raise e
end;
with
| res ->
Trace_core.exit_span _trace_span
res
| exception e ->
Trace_core.exit_span _trace_span
raise e
Alternatively, a name can be provided for the span, which is useful if you want to access it and use functions like Trace.add_data_to_span
:
let%trace f x y z =
do_sth x;
do_sth y;
begin
let%trace _sp = "sub-span" in
do_sth z;
Trace.add_data_to_span _sp ["x", `Int 42]
end
Dune configuration
In your library
or executable
stanza, add: (preprocess (pps ppx_trace))
. The dependency on trace.core
is automatically added. You still need to configure a backend to actually do collection.
Backends
Concrete tracing or observability formats such as:
[x] Fuchsia (see the spec and tracing. Can be opened in https://ui.perfetto.dev)
Catapult
[x] light bindings here with
trace-tef
. (Can be opened in https://ui.perfetto.dev)[x] backend for tldrs, a small rust daemon that aggregates TEF traces from multiple processes/clients into a single
.jsonl
file[x] tldrs, to collect TEF traces from multiple processes in a clean way. This requires the rust
tldrs
program to be in path.~~[ ] richer bindings with ocaml-catapult, with multi-process backends, etc.~~ (subsumed by tldrs)
[x] Tracy (see ocaml-tracy, more specifically
tracy-client.trace
)[x] Opentelemetry (see ocaml-opentelemetry, in
opentelemetry.trace
)[ ] landmarks?
[ ] Logs (only for messages, obviously)
Subscribers
The library trace.subscriber
defines composable subscribers, which are sets of callbacks that consume tracing events. Multiple subscribers can be aggregated together (with events being dispatched to all of them) and be installed as a normal collector.