From 103e9ac34a5a8b3c78db24f967fba979c5e0c190 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Wed, 2 Aug 2023 12:10:42 +0200 Subject: [PATCH 1/2] Update tracing documentation --- docs/restate/tracing.md | 68 +++++++++++++++++++++++----- docs/tour-of-restate.mdx | 2 +- static/img/jaeger-trace.png | Bin 563942 -> 0 bytes static/img/jaeger_search.png | Bin 0 -> 38352 bytes static/img/observability.jpeg | Bin 0 -> 64011 bytes static/img/understanding_traces.png | Bin 0 -> 152677 bytes 6 files changed, 57 insertions(+), 13 deletions(-) delete mode 100644 static/img/jaeger-trace.png create mode 100644 static/img/jaeger_search.png create mode 100644 static/img/observability.jpeg create mode 100644 static/img/understanding_traces.png diff --git a/docs/restate/tracing.md b/docs/restate/tracing.md index 8544cb19..f067dfe6 100644 --- a/docs/restate/tracing.md +++ b/docs/restate/tracing.md @@ -11,13 +11,13 @@ Restate supports the following tracing features: * Exporting traces to OTLP-compatible systems (e.g. Jaeger) * Correlating parent traces of incoming gRPC/Connect HTTP requests, using the [W3C TraceContext](https://github.com/w3c/trace-context) specification. -## Setup OTLP exporter +## Setting up OTLP exporter -To set up the OTLP exporter, you need to set the configuration entry `observability.tracing.endpoint` to point to your trace collector. -The exporter sends OTLP trace data via gRPC ([OTLP exporter](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/otlpexporter/README.md)). +Set up the [OTLP exporter](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/otlpexporter/README.md) by pointing the configuration entry `observability.tracing.endpoint` to your trace collector. +The exporter sends OTLP trace data via gRPC. -### Exporting OTLP traces to Jaeger +## Exporting OTLP traces to Jaeger Jaeger accepts OTLP trace data via gRPC on port `4317`. @@ -33,24 +33,68 @@ Configure the tracing endpoint in Restate as a fully specified URL: `http:// + + +```shell +docker run --name restate_dev --rm -d -e RESTATE_OBSERVABILITY__TRACING__ENDPOINT=http://localhost:4317 --network=host ghcr.io/restatedev/restate-dist:VAR::RESTATE_DIST_VERSION +``` + + + + +```shell +docker run --name restate_dev --rm -d -e RESTATE_OBSERVABILITY__TRACING__ENDPOINT=http://host.docker.internal:4317 -p 8081:8081 -p 9091:9091 -p 9090:9090 ghcr.io/restatedev/restate-dist:VAR::RESTATE_DIST_VERSION +``` + + + + +Go to the Jaeger UI at http://localhost:16686. + +If you now spin up your Restate services and send requests to them, you will see the traces appear in the Jaeger UI. + +An example from the [shopping cart example](https://github.com/restatedev/example-shopping-cart-typescript): +![Observability](/img/observability.jpeg) + +## Setting up Jaeger file exporter + +If you can't configure a Jaeger agent, you can export traces by writing them to files, using the Jaeger JSON format. To do so, set up the configuration entry `observability.tracing.json_file_export_path` pointing towards the path where trace files should be written. You can import the trace files using the Jaeger UI: ![Jaeger UI File import](/img/jaeger-import-file.png) + ## Understanding traces +The traces contain detailed information about the context calls that were done during the invocation (e.g. sleep, one-way calls, interaction with state): + +![Understanding traces](/img/understanding_traces.png) -Similarly to logs, traces export [attributes/tags](#components-and-log-event-context-fields) that correlate the trace with the service and/or invocation. For example, you can filter directly in the Jaeger UI all the traces belonging to the service `org.example.ExampleService` by setting the tag filter `rpc.service=org.example.ExampleService`. +The initial `ingress_service_invocation` spans show when the gRPC/Connect HTTP request was received by Restate. The `invoke` span beneath it shows when Restate invoked the service endpoint to process the request. -Restate traces don't look like traditional HTTP server traces, because of the inner workings of Restate and OpenTelemetry/Jaeger. For each invocation, a span named `service_invocation` is created to mark the beginning of the invocation, and a child span `end_invocation` is created to mark the end of an invocation. You can easily check for every invocation if it ended or not by checking whether the span `service_invocation` has the `end_invocation` child or not. +The tags of the spans contain the metadata of the context calls (e.g. call arguments, invocation id). -The spans `ingress_service_invocation` informs when the gRPC/Connect HTTP request is received, and `invoker_invocation_task` informs when the runtime invokes the service endpoint to process the request. +When a service invokes another service, the child invocation is linked automatically to the parent invocation, as you can see in the image. +Note that the spans of one-way calls are shown as separate traces. The parent invocation only shows that the one-way call was scheduled, not its entire tracing span. +To see this information, search for the trace of the one-way call by filtering on the invocation id tag: +``` +restate.invocation.sid="example.MyExampleService-AzEyMw==-0189b536906b746c8da6f83f0257acda" +``` -![Jaeger trace](/img/jaeger-trace.png) +## Searching traces -When a service invokes another service, the child invocation will be automatically linked to the span `service_invocation` of the parent invocation. +Traces export attributes and tags that correlate the trace with the service and/or invocation. For example, in the Jaeger UI, you can filter the traces belonging to the service `org.example.ExampleService` by setting the tag filter `rpc.service=org.example.ExampleService`. Or you can filter on the method (`rpc.method`) or invocation id (`restate.invocation.sid`) or any other tag: -We recommend to set up either Jaeger or Jaeger File trace exporter with filter `info,restate_worker::partition::effects=debug`. With this filter, the traces will also contain all the steps executed by the Restate internal state machine to drive the invocation to completion. +![Jaeger method search](/img/jaeger_search.png) \ No newline at end of file diff --git a/docs/tour-of-restate.mdx b/docs/tour-of-restate.mdx index d715dabb..8e8c1de9 100644 --- a/docs/tour-of-restate.mdx +++ b/docs/tour-of-restate.mdx @@ -1262,7 +1262,7 @@ Payment call succeeded for idempotency key dc2048f8-62c1-4b3e-bb92-25dc9da14bac ## Observability -Restate exposes OpenTelemetry traces of your invocations +Restate exposes OpenTelemetry traces of your invocations. Run the Jaeger container with: ```shell diff --git a/static/img/jaeger-trace.png b/static/img/jaeger-trace.png deleted file mode 100644 index b214d812c1c03e7fc4fafab8e00f409fe7dae8eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 563942 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