Skip to main content
Spans are the building blocks of performance monitoring in Sentry. Each span represents a single operation or unit of work with a start and end time.

Span Basics

A span contains:
  • Span ID: Unique identifier for the span
  • Trace ID: Identifier linking it to a trace
  • Parent Span ID: Links to parent span (if any)
  • Operation: Type of operation (e.g., db.query, http.client)
  • Description/Name: What the span measures
  • Start/End Time: When the span began and ended
  • Status: Success or failure indicator
  • Attributes: Custom key-value data
import * as Sentry from '@sentry/browser';

Sentry.startSpan(
  {
    name: 'database_query',
    op: 'db.query'
  },
  (span) => {
    // Span is automatically tracked
    const result = queryDatabase();
    return result;
  }
);

Creating Spans

Auto-finishing Spans

Spans that finish when the callback completes:
Sentry.startSpan({ name: 'my_operation', op: 'function' }, (span) => {
  doWork();
  return result;
  // Span ends here automatically
});

Manual Span Control

Control span lifetime explicitly:
import { startInactiveSpan } from '@sentry/browser';

const span = startInactiveSpan({
  name: 'manual_operation',
  op: 'function'
});

try {
  doWork();
  span.setStatus({ code: 1 }); // OK
} catch (error) {
  span.setStatus({ code: 2, message: 'error' }); // Error
  throw error;
} finally {
  span.end();
}

Async Operations

Spans work seamlessly with async/await:
await Sentry.startSpan(
  { name: 'async_operation', op: 'http.client' },
  async (span) => {
    const response = await fetch('/api/data');
    const data = await response.json();
    
    span.setAttribute('response.size', JSON.stringify(data).length);
    
    return data;
  }
);

Span Hierarchy

Parent-Child Relationships

Spans automatically form a hierarchy:
Sentry.startSpan({ name: 'parent_operation' }, (parentSpan) => {
  doWork();
  
  // Child span
  Sentry.startSpan({ name: 'child_operation' }, (childSpan) => {
    doChildWork();
  });
  
  // Another child
  Sentry.startSpan({ name: 'another_child' }, (childSpan) => {
    doMoreWork();
  });
});

Root Spans (Transactions)

The top-level span in a trace is called a transaction:
// This creates a root span (transaction)
Sentry.startSpan(
  {
    name: 'checkout_flow',
    op: 'transaction'
  },
  (transaction) => {
    // Child spans
    Sentry.startSpan({ name: 'validate_cart' }, () => {});
    Sentry.startSpan({ name: 'process_payment' }, () => {});
    Sentry.startSpan({ name: 'send_confirmation' }, () => {});
  }
);
Transactions are sent to Sentry when the root span finishes. Child spans are included in the transaction.

Span Attributes

Setting Attributes

Add custom data to spans:
Sentry.startSpan({ name: 'api_call', op: 'http.client' }, (span) => {
  // Set individual attributes
  span.setAttribute('http.method', 'POST');
  span.setAttribute('http.url', 'https://api.example.com/users');
  span.setAttribute('http.status_code', 201);
  
  // Set multiple attributes
  span.setAttributes({
    'user.id': '123',
    'user.premium': true,
    'request.body.size': 1024
  });
});

Removing Attributes

Sentry.startSpan({ name: 'operation' }, (span) => {
  span.setAttribute('temp_data', 'value');
  
  // Remove attribute by setting to undefined
  span.setAttribute('temp_data', undefined);
});

Standard Attributes

Use semantic attributes for common data:
// HTTP attributes
span.setAttributes({
  'http.method': 'GET',
  'http.url': 'https://api.example.com/users',
  'http.status_code': 200,
  'http.request_content_length': 0,
  'http.response_content_length': 1024
});

// Database attributes
span.setAttributes({
  'db.system': 'postgresql',
  'db.name': 'users_db',
  'db.statement': 'SELECT * FROM users WHERE id = $1',
  'db.operation': 'SELECT'
});

// User attributes
span.setAttributes({
  'user.id': '123',
  'user.email': 'user@example.com',
  'user.role': 'admin'
});

Span Status

Indicate whether a span succeeded or failed:
import { SPAN_STATUS_OK, SPAN_STATUS_ERROR } from '@sentry/browser';

Sentry.startSpan({ name: 'operation' }, (span) => {
  try {
    doWork();
    span.setStatus({ code: SPAN_STATUS_OK });
  } catch (error) {
    span.setStatus({ code: SPAN_STATUS_ERROR, message: 'Operation failed' });
    throw error;
  }
});

Status Codes

  • SPAN_STATUS_UNSET (0): Default, no status set
  • SPAN_STATUS_OK (1): Operation completed successfully
  • SPAN_STATUS_ERROR (2): Operation failed

HTTP Status Codes

Automatically set status from HTTP responses:
import { setHttpStatus } from '@sentry/browser';

Sentry.startSpan({ name: 'api_request' }, async (span) => {
  const response = await fetch('/api/data');
  
  // Automatically sets span status based on HTTP code
  setHttpStatus(span, response.status);
  // 2xx -> OK, 4xx/5xx -> ERROR
  
  return response.json();
});

Span Events

Add timestamped events within a span:
Sentry.startSpan({ name: 'complex_operation' }, (span) => {
  span.addEvent('started_validation');
  validateInput();
  
  span.addEvent('validation_complete', {
    'validation.errors': 0
  });
  
  processData();
  
  span.addEvent('processing_complete', {
    'records.processed': 100
  });
});

Updating Span Names

Change span names dynamically:
Sentry.startSpan({ name: 'api_request' }, async (span) => {
  const response = await fetch('/api/endpoint');
  
  // Update name based on response
  span.updateName(`GET /api/endpoint [${response.status}]`);
  
  return response;
});
Use dynamic span names to categorize similar operations based on their outcome or parameters.

Span Context

Access span identifiers:
Sentry.startSpan({ name: 'operation' }, (span) => {
  const context = span.spanContext();
  
  console.log('Span ID:', context.spanId);
  console.log('Trace ID:', context.traceId);
  console.log('Trace Flags:', context.traceFlags);
});

Span Sampling

Control whether a span is recorded:
import { spanToJSON } from '@sentry/browser';

Sentry.startSpan({ name: 'operation' }, (span) => {
  const spanData = spanToJSON(span);
  
  // Check if span is being recorded
  if (span.isRecording()) {
    // Span is sampled and will be sent to Sentry
    span.setAttribute('detailed_info', expensiveComputation());
  }
});

Getting Span Data

Retrieve span information:
import { spanToJSON } from '@sentry/browser';

Sentry.startSpan({ name: 'operation' }, (span) => {
  // Get JSON representation
  const spanData = spanToJSON(span);
  
  console.log('Span ID:', spanData.span_id);
  console.log('Trace ID:', spanData.trace_id);
  console.log('Parent Span ID:', spanData.parent_span_id);
  console.log('Op:', spanData.op);
  console.log('Description:', spanData.description);
  console.log('Start Time:', spanData.start_timestamp);
  console.log('Attributes:', spanData.data);
});
Link spans to other traces:
import { startInactiveSpan } from '@sentry/browser';

const span = startInactiveSpan({
  name: 'operation_with_links'
});

// Add link to another trace
span.addLink({
  traceId: 'other-trace-id',
  spanId: 'other-span-id',
  attributes: {
    'link.type': 'related_operation'
  }
});

// Add multiple links
span.addLinks([
  { traceId: 'trace-1', spanId: 'span-1' },
  { traceId: 'trace-2', spanId: 'span-2' }
]);

span.end();
Span links allow you to connect spans across different traces, useful for batch processing or fan-out operations.

Practical Examples

Database Query

Sentry.startSpan(
  {
    name: 'SELECT users',
    op: 'db.query',
    attributes: {
      'db.system': 'postgresql',
      'db.name': 'production',
      'db.statement': 'SELECT * FROM users WHERE active = true'
    }
  },
  async (span) => {
    const start = Date.now();
    const users = await db.query('SELECT * FROM users WHERE active = true');
    const duration = Date.now() - start;
    
    span.setAttribute('db.rows_affected', users.length);
    span.setAttribute('db.duration_ms', duration);
    
    return users;
  }
);

HTTP Request

Sentry.startSpan(
  {
    name: 'GET /api/users',
    op: 'http.client',
    attributes: {
      'http.method': 'GET',
      'http.url': 'https://api.example.com/users'
    }
  },
  async (span) => {
    const startTime = performance.now();
    
    const response = await fetch('https://api.example.com/users', {
      headers: {
        'Authorization': 'Bearer token',
        'Content-Type': 'application/json'
      }
    });
    
    const duration = performance.now() - startTime;
    
    span.setAttributes({
      'http.status_code': response.status,
      'http.response_content_length': response.headers.get('content-length'),
      'http.duration_ms': duration
    });
    
    setHttpStatus(span, response.status);
    
    return response.json();
  }
);

File Processing

Sentry.startSpan(
  {
    name: 'process_file',
    op: 'file.process',
    attributes: {
      'file.name': 'data.csv',
      'file.type': 'text/csv'
    }
  },
  (span) => {
    // Read file
    Sentry.startSpan({ name: 'read_file', op: 'file.read' }, () => {
      const content = readFile('data.csv');
      span.setAttribute('file.size', content.length);
      return content;
    });
    
    // Parse
    const records = Sentry.startSpan(
      { name: 'parse_csv', op: 'parse' },
      () => parseCSV(content)
    );
    
    span.setAttribute('records.count', records.length);
    
    // Process
    const processed = Sentry.startSpan(
      { name: 'process_records', op: 'function' },
      () => records.map(processRecord)
    );
    
    span.setAttribute('records.processed', processed.length);
    
    // Save
    Sentry.startSpan(
      { name: 'save_results', op: 'db.query' },
      () => saveRecords(processed)
    );
  }
);

Caching Operation

await Sentry.startSpan(
  { name: 'get_user_data', op: 'cache.get' },
  async (span) => {
    // Try cache first
    const cached = await Sentry.startSpan(
      { name: 'check_cache', op: 'cache.get' },
      () => cache.get('user:123')
    );
    
    if (cached) {
      span.setAttribute('cache.hit', true);
      return cached;
    }
    
    span.setAttribute('cache.hit', false);
    
    // Fetch from database
    const userData = await Sentry.startSpan(
      { name: 'query_user', op: 'db.query' },
      () => db.users.findById('123')
    );
    
    // Update cache
    await Sentry.startSpan(
      { name: 'set_cache', op: 'cache.set' },
      () => cache.set('user:123', userData, { ttl: 3600 })
    );
    
    return userData;
  }
);

Span Best Practices

  1. Use descriptive names: Clearly indicate what the span measures
  2. Set appropriate operations: Use standard operation types
  3. Add relevant attributes: Include data that helps identify slow operations
  4. Set status correctly: Indicate success or failure
  5. Keep spans focused: Each span should represent one logical operation
  6. Don’t create too many spans: Balance detail with overhead
  7. Finish spans promptly: Don’t leave spans open

Span Limits

Be aware of limits:
  • Maximum 1000 spans per transaction
  • Attribute values are truncated if too large
  • Deeply nested spans may impact performance
Transactions with more than 1000 spans will have spans dropped. Keep your span count reasonable.

Next Steps

Tracing

Learn about traces and span relationships

Distributed Tracing

Track spans across services

Performance

Performance monitoring overview

Profiling

CPU profiling for code-level insights