Skip to content

Repository files navigation

sourcemap-otel

Source map resolution for OpenTelemetry errors. Automatically resolves minified stack traces back to original source locations using debug IDs.

How It Works

  1. Build time: Source maps are collected and stored (locally or in a remote store)
  2. Client side: Exception stack traces are enriched with debug IDs from bundler-injected globals (_debugIds or __DEBUG_IDS__)
  3. Server side: A request handler resolves minified stack traces using the stored source maps before forwarding to your OTEL collector

Packages

Package Description
smapped-traces Core library — client exporter, route handler, source map resolver, and store abstraction
@smapped-traces/nextjs Next.js plugin — build-time source map collection via withSourceMaps()
@smapped-traces/vite Vite plugin — build-time source map collection via sourceMaps() and collectSourceMaps()
@smapped-traces/sqlite SQLite-backed source map store for local and single-server deployments
@smapped-traces/s3 S3-compatible source map store (AWS S3, GCS, Cloudflare R2)

Quick Start (Next.js)

1. Install

npm install smapped-traces @smapped-traces/nextjs @smapped-traces/sqlite

2. Configure Next.js

// next.config.mjs
import { withSourceMaps } from "@smapped-traces/nextjs";
import { createSqliteStore } from "@smapped-traces/sqlite";
import { join } from "node:path";

export default withSourceMaps(
  {
    // your config
  },
  {
    store: (distDir) => createSqliteStore(join(distDir, "sourcemaps.db")),
  }
);

3. Set Up the Client Exporter

// instrumentation-client.ts
import { SourceMappedSpanExporter } from "smapped-traces/client";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";

const exporter = new SourceMappedSpanExporter("/api/sourcemaps");
const provider = new WebTracerProvider({
  spanProcessors: [new SimpleSpanProcessor(exporter)],
});
provider.register();

4. Create the Route Handler

// app/api/sourcemaps/route.ts
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { createTracesHandler } from "smapped-traces/route";
import { createSqliteStore } from "@smapped-traces/sqlite";
import { join } from "node:path";

const OTLP_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318";

export const POST = createTracesHandler({
  exporter: new OTLPTraceExporter({ url: `${OTLP_ENDPOINT}/v1/traces` }),
  store: createSqliteStore(join(process.cwd(), ".next/sourcemaps.db")),
});

Storage

Source maps are accessed through the SourceMapStore interface. Built-in stores:

Store Package Description
createSqliteStore(dbPath) @smapped-traces/sqlite Local SQLite database
createHttpStore(url) smapped-traces/store HTTP client for a remote store handler
createS3Store(options) @smapped-traces/s3 S3-compatible bucket (AWS, GCS, R2)

Remote Store

Deploy a storage service and point your build + handler at it:

// storage-service.ts — deploy separately
import { createStoreHandler } from "smapped-traces/store";
import { createSqliteStore } from "@smapped-traces/sqlite";

const store = createSqliteStore("./sourcemaps.db");
Bun.serve({ port: 8081, fetch: createStoreHandler(store) });
// next.config.mjs — build uploads to the remote store
import { withSourceMaps } from "@smapped-traces/nextjs";
import { createHttpStore } from "smapped-traces/store";

export default withSourceMaps(
  { /* your config */ },
  { store: () => createHttpStore("https://sourcemaps.internal") }
);
// traces-handler.ts — resolves from the same remote store
import { createTracesHandler } from "smapped-traces/route";
import { createHttpStore } from "smapped-traces/store";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

const handler = createTracesHandler({
  exporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }),
  store: createHttpStore("https://sourcemaps.internal"),
});

S3 Store

npm install @smapped-traces/s3 @aws-sdk/client-s3
import { S3Client } from "@aws-sdk/client-s3";
import { createS3Store } from "@smapped-traces/s3";

const store = createS3Store({
  client: new S3Client({ region: "us-east-1" }),
  bucket: "my-sourcemaps",
  prefix: "sourcemaps/",
});

// Use with withSourceMaps(), createTracesHandler(), or createSourceMapResolver()

Standalone Server

The handler uses standard Web Request/Response, so it works outside Next.js:

// Bun
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { createTracesHandler } from "smapped-traces/route";
import { createSqliteStore } from "@smapped-traces/sqlite";

const handler = createTracesHandler({
  exporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }),
  store: createSqliteStore("./sourcemaps.db"),
});

Bun.serve({ port: 8080, fetch: handler });

Exports

smapped-traces

Path Description
smapped-traces/client SourceMappedSpanExporter — Client-side span exporter with debug ID enrichment
smapped-traces/route createTracesHandler() — Request handler with source map resolution
smapped-traces/resolve createSourceMapResolver() — Standalone source map resolver
smapped-traces/store SourceMapStore, createHttpStore(), createStoreHandler()

@smapped-traces/nextjs

Path Description
@smapped-traces/nextjs withSourceMaps() — Next.js config helper for build-time source map collection

@smapped-traces/sqlite

Path Description
@smapped-traces/sqlite createSqliteStore() — SQLite-backed store

@smapped-traces/s3

Path Description
@smapped-traces/s3 createS3Store() — S3-compatible bucket store

Debug ID Support

The client exporter reads debug IDs from two global variables:

Global Bundler Format
globalThis._debugIds Turbopack Stack trace keys → debug ID UUIDs
globalThis.__DEBUG_IDS__ TC39 spec / webpack URL keys → debug ID UUIDs

Both are checked and merged automatically.

Requirements

  • Next.js 16+ (only for @smapped-traces/nextjs — uses runAfterProductionCompile hook)
  • OpenTelemetry SDK v2+

Contributing

We review change proposals in Discussions before code. Start a Discussion and wait for a maintainer's review. If we accept the proposal, a Mathematic maintainer or agent will implement it and open the pull request. When Mathematic implements a proposal, the implementation PR will link to the Discussion and credit its original author. GitHub limits pull request creation to Mathematic maintainers and repository collaborators with write, maintain, or admin access, plus authorized maintenance agents. See CONTRIBUTING.md for the full process.

License

Apache-2.0

This project is free and open-source work by a 501(c)(3) non-profit. If you find it useful, please consider donating.

About

Source map resolution for OpenTelemetry — automatically resolves minified stack traces back to original source locations using debug IDs

Topics

Resources

Contributing

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages