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
| Signal | Endpoint | Languages |
|---|---|---|
| Traces | /api/otel/v1/traces | JavaScript, Android |
| Logs | /api/otel/v1/logs | JavaScript |
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:
| Resolver | Selected when |
|---|---|
| JavaScript | telemetry.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 |
| Android | telemetry.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:
| Attribute | Used for |
|---|---|
exception.type, exception.message | the header line of the rebuilt trace |
exception.stacktrace | the raw stack, when no structured arrays are present |
exception.structured_stacktrace.urls / .functions / .lines / .columns | parallel arrays from web instrumentations; preferred over string parsing |
exception.structured_stacktrace.classes / .methods / .source_files / .lines | parallel 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:
| Language | Endpoint | Reference |
|---|---|---|
| JavaScript | POST /api/sourcemaps/upload | maps and bundles |
| Android | POST /api/symbols/upload with a proguard_uuid field | uploading 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:
- Point the exporter at
https://ingest.tracepath.dev/api/otelwith a project token. - Send the build's maps, bundles, or
mapping.txtto the upload endpoints above instead of to the processor's store. - 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
.twformat - JavaScript: the source-map resolution pipeline in full
- Android: R8 retrace and the build UUID contract