Symbolicator
OpenTelemetry

OpenTelemetry

Applications instrumented with vendor-neutral OpenTelemetry export over OTLP/HTTP to https://ingest.tracepath.dev/api/otel, and exceptions arriving there are symbolicated before they are stored. There is no processor to install and no collector to run: upload the build's debug info, and minified or obfuscated stack traces in spans, span events, and log records are rewritten to original files, lines, columns, and function names on the way in.

This page is the OTLP-specific contract: which records are symbolicated, which attributes carry the trace, and what changes if you are coming from a collector that used to do this work. For the engine itself, see Architecture.

What gets symbolicated

SignalEndpointLanguages
Traces/api/otel/v1/tracesJavaScript, Android
Logs/api/otel/v1/logsJavaScript

An exception is picked up from a span event, from a span carrying exception.stacktrace directly, or from a log record with that attribute. Which resolver runs is decided by the telemetry.sdk.language resource attribute:

ResolverSelected when
JavaScripttelemetry.sdk.language is webjs, nodejs, javascript, or typescript — or, when the attribute is absent, the instrumentation scope is an npm-style scoped name (@scope/package) or next.js
Androidtelemetry.sdk.language is android, java, or kotlin

Everything else is stored exactly as it arrived. Dart and iOS crashes are not routed here; those resolve on the /api/report path described on the Dart and iOS pages.

The project also has to have an Upload Token generated — it is the credential the upload endpoints accept and the switch the ingest path checks.

The attributes it reads

Exception attributes follow the OpenTelemetry semantic conventions, plus the structured-stack-trace arrays that web and Android instrumentations emit:

AttributeUsed for
exception.type, exception.messagethe header line of the rebuilt trace
exception.stacktracethe raw stack, when no structured arrays are present
exception.structured_stacktrace.urls / .functions / .lines / .columnsparallel arrays from web instrumentations; preferred over string parsing
exception.structured_stacktrace.classes / .methods / .source_files / .linesparallel arrays from Android instrumentations
app.debug.proguard_uuid (resource)picks the R8 mapping for an Android build

Precedence is: the web structured arrays (whenever urls is non-empty), else exception.stacktrace, else the Android structured arrays. Whatever the source, the result is normalized into one canonical trace, symbolicated, and then hashed for grouping — so inline expansion and resolved names never split one error into several issues.

Over OTLP, JavaScript source maps are matched by file basename, not by debug ID. Content-hashed bundle filenames (the default in Vite, Next, Angular, and most bundlers) keep concurrent releases from colliding; with stable filenames the most recent upload wins.

Exporter configuration

Point the OTLP/HTTP exporter at the ingest host and authenticate with the project token. In a collector:

exporters:
  otlphttp:
    endpoint: https://ingest.tracepath.dev/api/otel
    headers:
      Authorization: Bearer <project token>
 
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp]

Exporting straight from the application works the same way — the endpoint and the Authorization header are the whole contract. The project token is the ingest credential, distinct from the Upload Token that debug info is posted with.

Uploading the debug info

Symbolication needs the build's artifacts, posted from the release job:

LanguageEndpointReference
JavaScriptPOST /api/sourcemaps/uploadmaps and bundles
AndroidPOST /api/symbols/upload with a proguard_uuid fielduploading mapping.txt

Both endpoints live on the API host, https://app.tracepath.dev, and take the project's Upload Token as a bearer credential. Uploads take effect immediately for exceptions that arrive after them.

Coming from a collector that symbolicated for you

If your pipeline runs a symbolicating processor today (Honeycomb's source_map_symbolicator being the common one), the instrumentation side needs no changes: TracePath reads the same exception attributes, the same app.debug.proguard_uuid resource attribute, and keys Android mappings by the same <uuid>.txt naming. Moving over is three steps:

  1. Point the exporter at https://ingest.tracepath.dev/api/otel with a project token.
  2. Send the build's maps, bundles, or mapping.txt to the upload endpoints above instead of to the processor's store.
  3. Drop the processor from the pipeline — symbolication now happens at ingest, once, before storage.

The visible difference is where the work happens. A collector processor rewrites spans in flight and needs its own store, cache directory, and memory budget on your infrastructure; at ingest the resolver is built once per artifact and shared across every instance, and a missing artifact degrades to an unsymbolicated trace instead of back-pressuring your pipeline. What that degradation looks like, and how to tell it apart from a misconfigured upload, is on Performance.

Related pages

  • Architecture: when symbolication runs, the cache tiers, and the .tw format
  • JavaScript: the source-map resolution pipeline in full
  • Android: R8 retrace and the build UUID contract