Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript

Export traces from Node or a browser

Axial.Telemetry.JavaScript writes spans through the JavaScript OpenTelemetry API. JavaScript uses different span and context-propagation APIs from the .NET adapter. Both packages consume the same ambient Axial.Telemetry.Context from core and emit the same Axial attribute names.

Install the packages

dotnet add package Axial.Telemetry.JavaScript
npm install @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http

@opentelemetry/sdk-trace-node pulls in @opentelemetry/context-async-hooks and registers the AsyncLocalStorageContextManager Node applications need. A browser host installs @opentelemetry/sdk-trace-web and @opentelemetry/context-zone instead, then registers a zone-based context manager. Axial accepts the API object; it does not import or configure an SDK for you.

A minimal Node bootstrap registers a provider, an exporter, and the default context manager before any Axial workflow runs:

const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter()));
provider.register(); // registers the provider and AsyncLocalStorageContextManager

The browser equivalent is new WebTracerProvider() from @opentelemetry/sdk-trace-web with provider.register({ contextManager: new ZoneContextManager() }) from @opentelemetry/context-zone. Axial then reads the registered API object in Install the tracer.

Install the tracer

After the host has registered its OpenTelemetry provider and context manager, pass the API object and the application's instrumentation scope name to Axial:

Fragment
open Fable.Core.JsInterop
open Axial.Telemetry.JavaScript

Otel.installNamed (importAll "@opentelemetry/api") "Checkout.Web"

Checkout.Web identifies the application code being traced; it is not the service name or a span name. The JavaScript SDK separately configures the service resource, while calls such as Otel.trace "checkout.submit" provide span names. Otel.install remains a shortcut using the default Axial scope, and Otel.installWith accepts an explicitly created tracer.

Node applications normally use an AsyncLocalStorageContextManager. Browser applications commonly use a zone-based context manager. Without a context manager, a span can lose its active parent after an awaited boundary.

Trace and tag a workflow

The context API works on .NET and JavaScript. This snippet is schematic: checkout, order, userId, CheckoutAttributes, tenantId, and CheckoutError.describe are application values defined in the checked example of the .NET telemetry guide. Only the JavaScript Otel.traceWith adapter and the extra Axial.Telemetry.JavaScript open differ here; the context calls are identical.

Fragment
open Axial.Telemetry
open Axial.Telemetry.JavaScript

checkout order
|> Context.withEndUserId userId
|> Context.withAttributes [
    Context.attribute CheckoutAttributes.tenantId tenantId
]
|> Otel.traceWith CheckoutError.describe "checkout.submit"

Otel.traceWith is the JavaScript counterpart of Activity.traceWith. It records the same Flow outcome, typed-error, defect, interruption, fiber, runtime-annotation, and ambient-context attributes.

Connect browser and server traces

OpenTelemetry's HTTP instrumentation propagates the W3C traceparent header. To see one trace across a browser and a .NET backend:

  1. Register fetch or XMLHttpRequest instrumentation in the browser.
  2. Allow traceparent through CORS for cross-origin requests.
  3. Configure propagateTraceHeaderCorsUrls for the backend origins.
  4. Enable ASP.NET Core instrumentation on the server.
  5. Export both applications to the same collector or Aspire-compatible OTLP backend.

The browser workflow span then parents the fetch span, and the server request span continues the same trace. Axial does not manually copy correlation identifiers; the OpenTelemetry SDKs propagate trace context.

Observe fibers

Install defect-only observation at the application edge:

Fragment
application
|> FiberTelemetry.observe

Use FiberTelemetry.observeWithSpans when you need one span per forked fiber. Under Fable, unobserved defects are reported at deterministic runtime detection points. JavaScript garbage collection does not provide the same finalization signal used by .NET.

Verify the result

Open your trace backend and confirm that:

  • the workflow span has the expected parent;
  • axial.flow.outcome matches the workflow exit;
  • enduser.id and custom context attributes are present;
  • defects carry exception.type, exception.message, and exception.stacktrace;
  • named fiber spans appear when span-per-fiber observation is enabled.

For the signal model, Aspire walkthrough, metrics, and fiber dumps, read Trace workflows and inspect them in Aspire. Fiber metrics and System.Diagnostics-based dump events are provided by the .NET adapter; the core FiberRegistry and rendered dumps remain available on supported Fable hosts.