diff --git a/README.md b/README.md index b096b8e6..c8176bd7 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ connectors to talk to opentelemetry software such as [jaeger](https://www.jaeger - library `opentelemetry` should be used to instrument your code and possibly libraries. It doesn't communicate with anything except - a backend (default: dummy backend); -- library `opentelemetry-client-ocurl` is a backend that communicates + an exporter (default: no-op); +- library `opentelemetry-client-ocurl` is an exporter that communicates via http+protobuf with some collector (otelcol, datadog-agent, etc.) using cURL bindings; -- library `opentelemetry-client-cohttp-lwt` is a backend that communicates +- library `opentelemetry-client-cohttp-lwt` is an exporter that communicates via http+protobuf with some collector using cohttp. ## License @@ -39,14 +39,14 @@ module Otel = Opentelemetry let (let@) = (@@) let foo () = - let@ scope = Otel.Trace.with_ "foo" + let@ span = Otel.Tracer.with_ "foo" ~attrs:["hello", `String "world"] in - do_work(); - Otel.Metrics.( - emit [ - gauge ~name:"foo.x" [int 42]; - ]); - do_more_work(); + do_work (); + let now = Otel.Clock.now Otel.Meter.default.clock in + Otel.Meter.emit1 Otel.Meter.default + Otel.Metrics.(gauge ~name:"foo.x" [int ~now 42]); + Otel.Span.add_event span (Otel.Event.make "work done"); + do_more_work (); () ``` @@ -56,14 +56,14 @@ If you're writing a top-level application, you need to perform some initial conf 1. Set the [`service_name`][]; 2. optionally configure [ambient-context][] with the appropriate storage for your environment — TLS, Lwt, Eio…; -3. and install a [`Collector`][] (usually by calling your collector's `with_setup` function.) +3. and install an exporter (usually by calling your client library'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(); + Otel.Gc_metrics.setup (); Opentelemetry_ambient_context.set_storage_provider (Opentelemetry_ambient_context_lwt.storage ()); Opentelemetry_client_ocurl.with_setup () @@ fun () -> @@ -72,10 +72,13 @@ let main () = (* … *) ``` - [`service_name`]: - [`Collector`]: + [`service_name`]: [ambient-context]: now vendored as `opentelemetry.ambient-context`, formerly +## Migration v012 → v0.13 + +see `doc/migration_guide_v0.13.md` + ## Configuration ### Environment Variables @@ -104,20 +107,20 @@ The library supports standard OpenTelemetry environment variables: - `OTEL_EXPORTER_OTLP_LOGS_HEADERS` - logs-specific headers -## Collector opentelemetry-client-ocurl +## opentelemetry-client-ocurl -This is a synchronous collector that uses the http+protobuf format -to send signals (metrics, traces, logs) to some other collector (eg. `otelcol` +This is a synchronous exporter that uses the http+protobuf format +to send signals (metrics, traces, logs) to some collector (eg. `otelcol` or the datadog agent). -Do note that this backend uses a thread pool and is incompatible +Do note that it uses a thread pool and is incompatible with uses of `fork` on some Unixy systems. See [#68](https://github.com/imandra-ai/ocaml-opentelemetry/issues/68) for a possible workaround. -## Collector opentelemetry-client-cohttp-lwt +## opentelemetry-client-cohttp-lwt -This is a Lwt-friendly collector that uses cohttp to send -signals to some other collector (e.g. `otelcol`). It must be run +This is a Lwt-friendly exporter that uses cohttp to send +signals to some collector (e.g. `otelcol`). It must be run inside a `Lwt_main.run` scope. ## Opentelemetry-trace diff --git a/doc/migration_guide_v0.13.md b/doc/migration_guide_v0.13.md new file mode 100644 index 00000000..2dff660f --- /dev/null +++ b/doc/migration_guide_v0.13.md @@ -0,0 +1,271 @@ +# Migration guide: v0.12 → v0.13 + +This guide covers breaking changes when upgrading from v0.12. + +## 1. Backend setup: `Collector` → `Sdk` + `Exporter` + +v0.12 used a first-class module `BACKEND` installed into a global slot via +`Collector.set_backend`. v0.13 replaces this with a plain record `Exporter.t` +installed via `Sdk.set`. + +The `with_setup` helper in each client library still exists, so if you use that +you mainly need to rename the module. + +```ocaml +(* v0.12 *) +Opentelemetry_client_ocurl.with_setup ~config () (fun () -> + (* your code *) + ()) + +(* v0.13: same call, internals changed; ~stop removed, ~after_shutdown added *) +Opentelemetry_client_ocurl.with_setup + ~after_shutdown:(fun _exp -> ()) + ~config () (fun () -> + (* your code *) + ()) +``` + +If you called `setup`/`remove_backend` manually: + +```ocaml +(* v0.12 *) +Opentelemetry_client_ocurl.setup ~config () +(* ... *) +Opentelemetry_client_ocurl.remove_backend () + +(* v0.13 *) +Opentelemetry_client_ocurl.setup ~config () +(* ... *) +Opentelemetry_client_ocurl.remove_exporter () +``` + +The `~stop:bool Atomic.t` parameter has been removed from the ocurl client. +Use `Sdk.active ()` (an `Aswitch.t`) to detect shutdown instead. + +## 2. `Trace.with_` → `Tracer.with_`, callback gets a `Span.t` + +The most common migration. The module is renamed and the callback argument type +changes from `Scope.t` to `Span.t`. + +```ocaml +(* v0.12 *) +Trace.with_ "my-op" ~attrs:["k", `String "v"] (fun (scope : Scope.t) -> + Scope.add_event scope (fun () -> Event.make "something happened"); + Scope.add_attrs scope (fun () -> ["extra", `Int 42]); + do_work () +) + +(* v0.13 *) +Tracer.with_ "my-op" ~attrs:["k", `String "v"] (fun (span : Span.t) -> + Span.add_event span (Event.make "something happened"); + Span.add_attrs span ["extra", `Int 42]; + do_work () +) +``` + +`Trace` is kept as a deprecated alias for `Tracer`. + +Key differences on the callback argument: + +| v0.12 (`Scope.t`) | v0.13 (`Span.t`) | +|--------------------------------------------|--------------------------------------| +| `scope.trace_id` | `Span.trace_id span` | +| `scope.span_id` | `Span.id span` | +| `Scope.add_event scope (fun () -> ev)` | `Span.add_event span ev` | +| `Scope.add_attrs scope (fun () -> attrs)` | `Span.add_attrs span attrs` | +| `Scope.set_status scope st` | `Span.set_status span st` | +| `Scope.record_exception scope e bt` | `Span.record_exception span e bt` | +| `Scope.to_span_ctx scope` | `Span.to_span_ctx span` | +| `Scope.to_span_link scope` | `Span.to_span_link span` | +| `~scope:scope` (pass parent explicitly) | `~parent:span` | + +The `~scope` parameter of `Trace.with_` is renamed to `~parent`: + +```ocaml +(* v0.12 *) +Trace.with_ "child" ~scope:parent_scope (fun child -> ...) + +(* v0.13 *) +Tracer.with_ "child" ~parent:parent_span (fun child -> ...) +``` + +In addition, `Scope.t` is entirely removed because `Span.t` is now mutable. +For additional efficiency, `Span.t` is directly encodable to protobuf +without the need to allocate further intermediate structures. + +## 3. `Logs` → `Logger`, new emit helpers + +The `Logs` module is renamed to `Logger` (`Logs` is kept as a deprecated alias). +Direct construction of log records and batch-emit is replaced by convenience +functions. + +```ocaml +(* v0.12 *) +Logs.emit [ + Logs.make_str ~severity:Severity_number_warn "something went wrong" +] + +Logs.emit [ + Logs.make_strf ~severity:Severity_number_info "processed %d items" n +] + +(* v0.13: simple string *) +Logger.log ~severity:Severity_number_warn "something went wrong" + +(* v0.13: formatted *) +Logger.logf ~severity:Severity_number_info (fun k -> k "processed %d items" n) +``` + +If you need to keep the trace/span correlation: + +```ocaml +(* v0.12 *) +Logs.emit [ + Logs.make_str ~trace_id ~span_id ~severity:Severity_number_info "ok" +] + +(* v0.13 *) +Logger.log ~trace_id ~span_id ~severity:Severity_number_info "ok" +``` + +`Log_record.make_str` / `Log_record.make` still exist if you need to build +records manually and emit them via a `Logger.t`. + +## 4. `Metrics.emit` → emit via a `Meter` + +In v0.12 `Metrics.emit` was a top-level function that sent directly to the +collector. In v0.13 metrics go through a `Meter.t`. For most code the change +is mechanical: + +```ocaml +(* v0.12 *) +Metrics.emit [ + Metrics.gauge ~name:"queue.depth" [ Metrics.int ~now depth ] +] + +(* v0.13: Meter.default emits to the global provider *) +Meter.emit1 Meter.default + (Metrics.gauge ~name:"queue.depth" [ Metrics.int ~now depth ]) +``` + +`now` is now obtained from the meter's clock rather than `Timestamp_ns.now_unix_ns ()`: + +```ocaml +(* v0.12 *) +let now = Timestamp_ns.now_unix_ns () in +Metrics.emit [ Metrics.sum ~name:"counter" [ Metrics.int ~now n ] ] + +(* v0.13 *) +let now = Clock.now Meter.default.clock in +Meter.emit1 Meter.default + (Metrics.sum ~name:"counter" [ Metrics.int ~now n ]) +``` + +## 5. `Metrics_callbacks.register` → `Meter.add_cb` + +```ocaml +(* v0.12 *) +Metrics_callbacks.register (fun () -> + [ Metrics.gauge ~name:"foo" [ Metrics.int ~now:... 42 ] ]) + +(* v0.13: callback now receives a clock *) +Meter.add_cb (fun ~clock () -> + let now = Clock.now clock in + [ Metrics.gauge ~name:"foo" [ Metrics.int ~now 42 ] ]) +``` + +After registering callbacks you must tell the SDK to drive them: + +```ocaml +(* v0.13: call once after setup to schedule periodic emission *) +Meter.add_to_main_exporter Meter.default +``` + +In v0.12 this was automatic once `Metrics_callbacks.register` was called. + +## 6. `GC_metrics.basic_setup` signature unchanged, `setup` changed + +`GC_metrics.basic_setup ()` still works. The module has been renamed +to `Gc_metrics`, but the former name persists as a deprecated alias. + +If you called the lower-level `GC_metrics.setup exp` directly: + +```ocaml +(* v0.12 *) +GC_metrics.setup exporter +(* or *) +GC_metrics.setup_on_main_exporter () + +(* v0.13 *) +Gc_metrics.setup () (* uses Meter.default *) +(* or with a specific meter: *) +Gc_metrics.setup ~meter:my_meter () +``` + +`GC_metrics.setup_on_main_exporter` has been removed. + +## 7. `Collector.on_tick` → `Sdk.add_on_tick_callback` + +```ocaml +(* v0.12 *) +Collector.on_tick (fun () -> do_background_work ()) + +(* v0.13 *) +Sdk.add_on_tick_callback (fun () -> do_background_work ()) +``` + +## 8. `?service_name` parameter removed + +`Trace.with_`, `Logs.emit`, and `Metrics.emit` accepted a `?service_name` +override. This is no longer supported per-call; set it once globally: + +```ocaml +(* v0.12 *) +Trace.with_ "op" ~service_name:"my-svc" (fun _ -> ...) + +(* v0.13: set globally before setup *) +Opentelemetry.Globals.service_name := "my-svc" +Tracer.with_ "op" (fun _ -> ...) +``` + +## 9. `create_backend` / `BACKEND` module type removed + +If you held a reference to a backend module: + +```ocaml +(* v0.12 *) +let (module B : Collector.BACKEND) = + Opentelemetry_client_ocurl.create_backend ~config () +in +Collector.set_backend (module B) + +(* v0.13 *) +let exp : Exporter.t = + Opentelemetry_client_ocurl.create_exporter ~config () +in +Sdk.set exp +``` + +## 10. New features (no migration needed) + +- **`Sdk.get_tracer/get_meter/get_logger`**: obtain a provider pre-stamped with + instrumentation-scope metadata (`~name`, `~version`, `~__MODULE__`). +- **`Trace_provider` / `Meter_provider` / `Log_provider`**: independent + per-signal providers; useful for testing or multi-backend setups. +- **`Dynamic_enricher`**: register callbacks that inject attributes into every + span and log record at creation time (wide events). +- **Batch**: much better handling of batching overall. + +## Quick checklist + +- [ ] `Trace.with_` → `Tracer.with_`; callback argument `Scope.t` → `Span.t` +- [ ] `Scope.add_event`/`add_attrs` → `Span.add_event`/`add_attrs` (no thunk wrapper) +- [ ] `~scope:` → `~parent:` in nested `with_` calls +- [ ] `Logs.emit [Logs.make_str ...]` → `Logger.log`/`Logger.logf` +- [ ] `Metrics.emit [...]` → `Meter.emit1 Meter.default ...` +- [ ] `Metrics_callbacks.register` → `Meter.add_cb` (+ call `Meter.add_to_main_exporter`) +- [ ] `GC_metrics.setup exp` → `Gc_metrics.setup ()` +- [ ] `Collector.on_tick` → `Sdk.add_on_tick_callback` +- [ ] Remove `?service_name` call-site overrides; set `Globals.service_name` once +- [ ] `create_backend` → `create_exporter`; `set_backend` → `Sdk.set` +- [ ] `~stop:bool Atomic.t` removed from ocurl client