Transactional

Stack Traces

Understand stack trace capture, source maps, and symbolication for better debugging.

Overview

Stack traces are essential for debugging errors. They show the exact code path that led to an error, including file names, function names, and line numbers.

Stack Trace Structure

Each stack trace consists of frames - individual function calls in the call stack:

interface StackFrame {
  filename: string;      // File path
  function: string;      // Function name
  lineno: number;        // Line number
  colno?: number;        // Column number
  contextLine?: string;  // The actual code line
  preContext?: string[]; // Lines before
  postContext?: string[]; // Lines after
  inApp?: boolean;       // Is this your code?
  module?: string;       // Module/package name
}

Example Stack Trace

{
  "frames": [
    {
      "filename": "src/components/UserCard.tsx",
      "function": "UserCard",
      "lineno": 42,
      "colno": 15,
      "contextLine": "const name = user.profile.name;",
      "preContext": [
        "export function UserCard({ user }: Props) {",
        "  const [loading, setLoading] = useState(false);"
      ],
      "postContext": [
        "  const email = user.profile.email;",
        "  return ("
      ],
      "inApp": true
    },
    {
      "filename": "node_modules/react-dom/cjs/react-dom.development.js",
      "function": "renderWithHooks",
      "lineno": 14985,
      "colno": 18,
      "inApp": false,
      "module": "react-dom"
    }
  ]
}

In-App vs Library Frames

The SDK distinguishes between your code and library code:

  • In-App Frames (inApp: true) - Your application code
  • Library Frames (inApp: false) - Code from node_modules, runtime, etc.

Configuring In-App Detection

initObservability({
  dsn: 'your-dsn',
  enableErrorTracking: true,
 
  // Patterns for in-app code
  inAppInclude: [
    'src/',
    'app/',
    'components/',
  ],
 
  // Patterns for library code
  inAppExclude: [
    'node_modules/',
    'webpack/',
    '__next/',
  ],
});

Source Maps

Production code is typically minified, making stack traces unreadable:

// Minified stack trace (unhelpful)
Error: Cannot read property 'name' of undefined
    at a.render (main.js:1:4523)
    at e.update (main.js:1:8934)

Source maps translate minified code back to original source:

// With source maps (helpful!)
Error: Cannot read property 'name' of undefined
    at UserCard (src/components/UserCard.tsx:42:15)
    at Dashboard.render (src/pages/Dashboard.tsx:88:20)

Uploading Source Maps

During Build

// vite.config.ts
import { observabilityPlugin } from '@transactional/observability/vite';
 
export default {
  plugins: [
    observabilityPlugin({
      authToken: process.env.OBSERVABILITY_AUTH_TOKEN,
      org: 'your-org',
      project: 'your-project',
      release: process.env.APP_VERSION,
    }),
  ],
  build: {
    sourcemap: true, // Generate source maps
  },
};

Webpack

// webpack.config.js
const { ObservabilitySourceMapPlugin } = require('@transactional/observability/webpack');
 
module.exports = {
  devtool: 'source-map',
  plugins: [
    new ObservabilitySourceMapPlugin({
      authToken: process.env.OBSERVABILITY_AUTH_TOKEN,
      org: 'your-org',
      project: 'your-project',
      release: process.env.APP_VERSION,
    }),
  ],
};

CLI Upload

# Upload source maps manually
npx @transactional/observability sourcemaps upload \
  --auth-token $OBSERVABILITY_AUTH_TOKEN \
  --org your-org \
  --project your-project \
  --release 1.2.3 \
  ./dist

Release Matching

Source maps are associated with a release. The release in your SDK config must match:

// SDK config
initObservability({
  dsn: 'your-dsn',
  release: '1.2.3', // Must match upload
});
# Upload command
npx @transactional/observability sourcemaps upload --release 1.2.3 ./dist

Next.js Source Maps

// next.config.js
const { withObservability } = require('@transactional/observability/nextjs');
 
module.exports = withObservability({
  authToken: process.env.OBSERVABILITY_AUTH_TOKEN,
  org: 'your-org',
  project: 'your-project',
})({
  // Your Next.js config
  productionBrowserSourceMaps: true,
});

Context Lines

Context lines show the actual code around the error:

// Error occurred here:
// Line 40:   const [loading, setLoading] = useState(false);
// Line 41:
// Line 42:   const name = user.profile.name; // <-- Error line
// Line 43:   const email = user.profile.email;
// Line 44:

Configuring Context Lines

initObservability({
  dsn: 'your-dsn',
  enableErrorTracking: true,
 
  stackTrace: {
    // Number of lines before the error line
    preContextLines: 3,
 
    // Number of lines after the error line
    postContextLines: 3,
  },
});

Node.js Stack Traces

Async Stack Traces

Node.js 12+ supports async stack traces:

initObservability({
  dsn: 'your-dsn',
  enableErrorTracking: true,
  platform: 'node',
 
  stackTrace: {
    // Enable async stack traces (more context, more overhead)
    asyncStackTraces: true,
  },
});

V8 Stack Trace Limit

Increase the default stack trace limit:

// At the top of your entry file
Error.stackTraceLimit = 50; // Default is 10
 
initObservability({
  dsn: 'your-dsn',
  // ...
});

Browser Stack Traces

Cross-Origin Scripts

Errors from cross-origin scripts show limited information:

Script error.
    at <anonymous>:0:0

Fix with CORS headers:

<!-- Add crossorigin attribute -->
<script src="https://cdn.example.com/app.js" crossorigin="anonymous"></script>
Access-Control-Allow-Origin: *

Dynamically Generated Code

Stack traces from dynamically generated code strings have limited context. For better error tracking, prefer static code patterns where possible.

Python Stack Traces

Symbolication

Python stack traces include full file paths:

Traceback (most recent call last):
  File "/app/services/user_service.py", line 42, in get_user
    return db.query(User).filter(User.id == user_id).one()
  File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2789, in one
    raise exc.NoResultFound("No row was found for one()")
sqlalchemy.orm.exc.NoResultFound: No row was found for one()

Frame Filtering

from transactional_observability import init_observability
 
init_observability(
    dsn="your-dsn",
    enable_error_tracking=True,
 
    # Include patterns for in-app code
    in_app_include=[
        "app",
        "services",
        "api",
    ],
 
    # Exclude patterns
    in_app_exclude=[
        "site-packages",
        "venv",
    ],
)

Stack Trace Limits

To avoid excessive data, stack traces are limited:

SettingDefaultMax
Max frames100250
Context lines (pre)310
Context lines (post)310
Max frame filename length256512

Configuring Limits

initObservability({
  dsn: 'your-dsn',
  enableErrorTracking: true,
 
  stackTrace: {
    maxFrames: 150,
    preContextLines: 5,
    postContextLines: 5,
    maxFilenameLength: 300,
  },
});

Viewing Stack Traces

In the Dashboard

  1. Go to Issues in your project
  2. Click an issue to view details
  3. Stack traces are shown with:
    • In-app frames highlighted
    • Expandable library frames
    • Clickable file/line links
    • Context lines when available

Frame Grouping

The dashboard groups frames by:

  • In-App - Your application code (expanded by default)
  • Framework - React, Next.js, etc.
  • Library - node_modules packages
  • System - Runtime and native code

Troubleshooting

Missing Source Maps

  1. Check upload - Verify source maps were uploaded for the release
  2. Check release - Ensure SDK release matches upload --release
  3. Check paths - Source map paths must match deployed file paths

Minified Stack Traces

If you see minified code:

  1. Enable source map generation in your bundler
  2. Upload source maps with matching release
  3. Check that production builds include source map references

Missing Context Lines

Context lines require:

  • Source maps with embedded sources, OR
  • Source map upload with original files
// vite.config.ts
export default {
  build: {
    sourcemap: true,
    rollupOptions: {
      output: {
        sourcemapExcludeSources: false, // Include sources in map
      },
    },
  },
};

Best Practices

1. Always Generate Source Maps

// vite.config.ts
export default {
  build: {
    sourcemap: true,
  },
};

2. Upload on Every Deploy

# CI/CD pipeline
- name: Build
  run: npm run build
 
- name: Upload Source Maps
  run: |
    npx @transactional/observability sourcemaps upload \
      --release ${{ github.sha }} \
      ./dist

3. Use Semantic Releases

initObservability({
  dsn: 'your-dsn',
  release: `${process.env.APP_NAME}@${process.env.APP_VERSION}`,
});

4. Clean Up Old Source Maps

# Delete source maps older than 90 days
npx @transactional/observability sourcemaps delete \
  --older-than 90d \
  --org your-org \
  --project your-project

Next Steps