# OpenTelemetry Tracing API for JavaScript

> Learn how to use the OpenTelemetry JavaScript API to instrument your applications with distributed tracing capabilities.

![undefined](/devicon/javascript-original.svg)## Prerequisites

<partial path="otel-prereq-js">



</partial>

## OpenTelemetry-JS

[OpenTelemetry-JS](https://github.com/open-telemetry/opentelemetry-js) is the JavaScript implementation of OpenTelemetry. It provides the OpenTelemetry Tracing API which you can use to instrument your application with [OpenTelemetry tracing](/opentelemetry/distributed-tracing).

### Installation

Install the OpenTelemetry API:

```shell
# npm
npm install @opentelemetry/api --save

# yarn
yarn add @opentelemetry/api --save
```

## Quickstart

This section demonstrates how to instrument a simple function step by step.

**Step 1: Basic Function**

Let's instrument the following Redis function:

```js
async function redisGet(key) {
  return await client.get(key)
}
```

**Step 2: Add Span Wrapper**

Wrap the operation with a [span](/opentelemetry/distributed-tracing#spans):

```js
const otel = require('@opentelemetry/api')

const tracer = otel.trace.getTracer('app_or_package_name', '1.0.0')

async function redisGet(key) {
  return await tracer.startActiveSpan('redis.get', async (span) => {
    const value = await client.get(key)
    span.end()
    return value
  })
}
```

**Step 3: Add Error Handling**

Record errors and set [status code](/opentelemetry/distributed-tracing#status-code):

```js
async function redisGet(key) {
  return await tracer.startActiveSpan('redis.get', async (span) => {
    let value
    try {
      value = await client.get(key)
    } catch (exc) {
      span.recordException(exc)
      span.setStatus({
        code: otel.SpanStatusCode.ERROR,
        message: String(exc)
      })
      throw exc
    } finally {
      span.end()
    }
    return value
  })
}
```

**Step 4: Add Contextual Information**

Record contextual information with [attributes](/opentelemetry/distributed-tracing#attributes):

```js
async function redisGet(key) {
  return await tracer.startActiveSpan('redis.get', async (span) => {
    // Add attributes for better observability
    if (span.isRecording()) {
      span.setAttributes({
        'db.system': 'redis',
        'db.operation': 'get',
        'db.redis.key': key,
      })
    }

    let value
    try {
      value = await client.get(key)
      span.setStatus({ code: otel.SpanStatusCode.OK })
    } catch (exc) {
      span.recordException(exc)
      span.setStatus({
        code: otel.SpanStatusCode.ERROR,
        message: String(exc)
      })
      throw exc
    } finally {
      span.end()
    }
    return value
  })
}
```

The operation is now fully instrumented with proper error handling, status codes, and contextual attributes!

## Tracer

To start creating [spans](/opentelemetry/distributed-tracing#spans), you need a tracer. You can create a tracer by providing the name and version of the library or application doing the instrumentation:

```js
const otel = require('@opentelemetry/api')

const tracer = otel.trace.getTracer('app_or_package_name', '1.0.0')
```

### Tracer naming

- Use descriptive names that identify the library or service
- Include version information for better debugging
- Create one tracer per logical component or service
- Use consistent naming across your application

```js
// Good examples
const authTracer = otel.trace.getTracer('auth-service', '2.1.0')
const dbTracer = otel.trace.getTracer('database-layer', '1.0.0')
const apiTracer = otel.trace.getTracer('user-api', '3.2.1')
```

## Creating spans

Once you have a tracer, creating [spans](/opentelemetry/distributed-tracing#spans) is straightforward. Spans represent units of work in your application.

### Basic span creation

```js
// Create a span with name and kind
tracer.startActiveSpan(
  'operation-name',
  { kind: otel.SpanKind.SERVER },
  (span) => {
    doSomeWork()
    span.end()
  },
)
```

### Span kinds

Choose the appropriate span kind based on the operation:

```js
// Server span - for handling incoming requests
tracer.startActiveSpan(
  'handle-request',
  { kind: otel.SpanKind.SERVER },
  (span) => {
    // Handle HTTP request
    span.end()
  },
)

// Client span - for outgoing requests
tracer.startActiveSpan('api-call', { kind: otel.SpanKind.CLIENT }, (span) => {
  // Make API call
  span.end()
})

// Internal span - for internal operations
tracer.startActiveSpan(
  'process-data',
  { kind: otel.SpanKind.INTERNAL },
  (span) => {
    // Internal processing
    span.end()
  },
)
```

### Async operations

For asynchronous operations, ensure proper span lifecycle management:

```js
async function processData() {
  return await tracer.startActiveSpan('process-data', async (span) => {
    try {
      const result = await performAsyncWork()
      span.setAttributes({
        'operation.result.count': result.length,
        'operation.success': true,
      })
      return result
    } catch (error) {
      span.recordException(error)
      span.setStatus({
        code: otel.SpanStatusCode.ERROR,
        message: String(error),
      })
      throw error
    } finally {
      span.end()
    }
  })
}
```

## Adding span attributes

To record contextual information, annotate spans with [attributes](/opentelemetry/distributed-tracing#attributes). Attributes provide valuable metadata for debugging and monitoring.

### Performance optimization

```js
// Check if span is recording to avoid expensive computations
if (span.isRecording()) {
  span.setAttributes({
    'http.method': 'GET',
    'http.route': '/projects/:id',
    'http.status_code': 200,
    'user.id': userId,
  })
}
```

### Semantic conventions

Use [semantic attributes](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/) for common operations:

```js
// HTTP operations
span.setAttributes({
  'http.method': 'POST',
  'http.url': 'https://api.example.com/users',
  'http.status_code': 201,
  'http.user_agent': userAgent,
})

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

// Custom business logic
span.setAttributes({
  'user.id': '12345',
  'order.total': 99.99,
  'payment.method': 'credit_card',
})
```

## Adding span events

Annotate spans with [events](/opentelemetry/distributed-tracing#events) to capture point-in-time occurrences during span execution:

### Log events

```js
span.addEvent('log', {
  'log.severity': 'error',
  'log.message': 'User authentication failed',
  'user.id': '123',
  'auth.method': 'oauth',
})
```

### Business events

```js
span.addEvent('order.processed', {
  'order.id': 'ord_123',
  'order.total': 99.99,
  'processing.duration_ms': 150,
})

span.addEvent('cache.miss', {
  'cache.key': 'user:profile:123',
  'cache.ttl': 3600,
})
```

### Exception events

```js
try {
  // risky operation
} catch (error) {
  span.addEvent('exception', {
    'exception.type': error.constructor.name,
    'exception.message': error.message,
    'exception.stacktrace': error.stack,
  })
}
```

## Setting status code

Use [status codes](/opentelemetry/distributed-tracing#status-code) to indicate the outcome of operations:

### Success status

```js
// Explicitly set success status
span.setStatus({ code: otel.SpanStatusCode.OK })
```

### Error status

```js
try {
  // operation that might fail
} catch (err) {
  span.setStatus({
    code: otel.SpanStatusCode.ERROR,
    message: String(err),
  })
}
```

### Conditional status

```js
const result = await performOperation()

if (result.success) {
  span.setStatus({ code: otel.SpanStatusCode.OK })
} else {
  span.setStatus({
    code: otel.SpanStatusCode.ERROR,
    message: result.errorMessage,
  })
}
```

## Recording exceptions

OpenTelemetry provides a convenient method to record exceptions, typically used together with `setStatus`:

### Basic exception

```js
try {
  // risky operation
} catch (exc) {
  // Record the exception and update the span status
  span.recordException(exc)
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: String(exc) })
}
```

### Advanced exception

```js
try {
  await processPayment(paymentData)
} catch (exc) {
  span.recordException(exc)
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: String(exc) })
  span.setAttributes({
    'error.type': exc.constructor.name,
    'payment.id': paymentData.id,
    'payment.amount': paymentData.amount,
  })
}
```

## Context management

OpenTelemetry stores the active span in a [context](/opentelemetry/distributed-tracing#context) and saves the context in pluggable context storage. Context enables automatic parent-child span relationships.

### Working with context

```js
// Create a new context with a span
otel.context.with(otel.trace.setSpan(otel.context.active(), span), () => {
  // span is active inside this callback
  doSomeWork() // Any spans created here will be children of 'span'
})
```

### Getting the active span

```js
// Get the currently active span
const activeSpan = otel.trace.getSpan(otel.context.active())

if (activeSpan) {
  activeSpan.addEvent('processing.started')
}
```

### Manual propagation

```js
function processWithContext() {
  const currentContext = otel.context.active()

  setTimeout(() => {
    // Manually propagate context to async callback
    otel.context.with(currentContext, () => {
      tracer.startActiveSpan('delayed-operation', (span) => {
        // This span will be a child of the original active span
        span.end()
      })
    })
  }, 1000)
}
```

## Advanced patterns

### Conditional instrumentation

```js
function conditionalTrace(operation, shouldTrace = true) {
  if (!shouldTrace) {
    return operation()
  }

  return tracer.startActiveSpan('conditional-operation', (span) => {
    try {
      const result = operation()
      span.setStatus({ code: otel.SpanStatusCode.OK })
      return result
    } catch (error) {
      span.recordException(error)
      span.setStatus({
        code: otel.SpanStatusCode.ERROR,
        message: String(error),
      })
      throw error
    } finally {
      span.end()
    }
  })
}
```

### Span decorators

```js
function traced(operationName) {
  return function (target, propertyKey, descriptor) {
    const originalMethod = descriptor.value

    descriptor.value = function (...args) {
      return tracer.startActiveSpan(operationName, (span) => {
        try {
          const result = originalMethod.apply(this, args)
          span.setStatus({ code: otel.SpanStatusCode.OK })
          return result
        } catch (error) {
          span.recordException(error)
          span.setStatus({
            code: otel.SpanStatusCode.ERROR,
            message: String(error),
          })
          throw error
        } finally {
          span.end()
        }
      })
    }

    return descriptor
  }
}

// Usage
class UserService {
  @traced('user.get')
  getUser(id) {
    return database.findUser(id)
  }
}
```

## Querying data

Once your application is instrumented and sending trace data to Uptrace, you can leverage the collected spans for monitoring and debugging:

### Span analysis

- **Service topology**: Visualize how your services communicate
- **Performance bottlenecks**: Identify slow operations and optimize them
- **Error tracking**: Monitor error rates and investigate failures
- **Dependency mapping**: Understand service dependencies and call patterns

### Search and filtering

- **Operation filtering**: Find spans by operation name, service, or duration
- **Attribute queries**: Search spans using custom attributes and semantic conventions
- **Error analysis**: Filter spans by status code and exception details
- **Performance analysis**: Query spans by duration thresholds and percentiles

## What's next?

- [Learn about OpenTelemetry JavaScript Metrics API](/get/opentelemetry-js/metrics)
- [Learn about OpenTelemetry JavaScript Resource detectors](/get/opentelemetry-js/resources)
- [Learn about OpenTelemetry JavaScript Context Propagation](/get/opentelemetry-js/propagation)
- [OpenTelemetry Express.js](/guides/opentelemetry-express)
