> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Node.js logs, metrics, and traces to Bronto

> Instrument Node.js and JavaScript applications with the OpenTelemetry JS SDK to send logs, metrics, and traces to Bronto over OTLP/HTTP, via an OTel Collector or directly.

One preloaded module sends **logs, metrics, and traces** from your Node.js application to Bronto. `NodeSDK` wires all three signals to one resource identity, and the auto-instrumentations package traces Express, Fastify, HTTP, gRPC, and database clients — and bridges Winston and Pino output into the OTLP logs pipeline — with no changes to your application code.

The application exports to an [OpenTelemetry Collector](/agent-setup/open-telemetry), which forwards to Bronto. If you don't run a Collector, [export directly to Bronto](#direct-export-to-bronto) instead — the code is identical, only two environment variables change.

## Prerequisites

* Node.js 18 or later
* An OTel Collector reachable from your application, with `logs`, `metrics`, and `traces` pipelines forwarding to Bronto — see [Connect OpenTelemetry Collector to Bronto](/agent-setup/open-telemetry)

## Install dependencies

```bash theme={"dark"}
npm install \
  @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/sdk-logs \
  @opentelemetry/sdk-metrics \
  @opentelemetry/exporter-logs-otlp-http \
  @opentelemetry/exporter-metrics-otlp-http \
  @opentelemetry/exporter-trace-otlp-http
```

## Configure the environment

The SDK reads its endpoint and identity from environment variables, so nothing in the code below is environment-specific.

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_SERVICE_NAME="my-service"
export OTEL_RESOURCE_ATTRIBUTES="service.namespace=my-team,deployment.environment=production"
```

The SDK appends the signal path (`/v1/logs`, `/v1/metrics`, `/v1/traces`) to `OTEL_EXPORTER_OTLP_ENDPOINT` automatically.

Two resource attributes determine how Bronto organises your data:

| OTel attribute      | Bronto concept | Set with                   |
| ------------------- | -------------- | -------------------------- |
| `service.name`      | Dataset        | `OTEL_SERVICE_NAME`        |
| `service.namespace` | Collection     | `OTEL_RESOURCE_ATTRIBUTES` |

<Note>
  `http://localhost:4318` is the standard OTLP/HTTP address for a Collector on the same host. Use the address reachable from your application if the Collector runs in another container, pod, or host. No authentication is needed between the application and the Collector — the Collector holds the Bronto API key.
</Note>

## Initialise the SDK

```javascript instrumentation.js theme={"dark"}
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter() }),
  logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

// Flush pending telemetry on shutdown.
process.on('SIGTERM', () => sdk.shutdown().finally(() => process.exit(0)));
```

Preload it so the instrumentations patch your dependencies before they are required:

```bash theme={"dark"}
node --require ./instrumentation.js app.js
```

<Note>
  For ESM projects, use `node --import ./instrumentation.mjs app.js` and the `register()` hook — see [Node.js instrumentation setup](https://opentelemetry.io/docs/languages/js/getting-started/nodejs/).
</Note>

## Instrument your application

Existing Winston and Pino calls need no changes — the auto-instrumentations bridge them into the OTLP logs pipeline and attach `trace_id` and `span_id` to any log emitted inside an active span, so you can jump from a log line to its trace in Bronto.

For spans and metrics around your own business logic:

```javascript payment.js theme={"dark"}
const { metrics, trace } = require('@opentelemetry/api');

const tracer = trace.getTracer('my-service');
const payments = metrics.getMeter('my-service').createCounter('app.payments');

async function processPayment(amount) {
  return tracer.startActiveSpan('process-payment', async (span) => {
    span.setAttribute('payment.amount', amount);
    payments.add(1);
    logger.info('Processing payment');  // trace_id and span_id attached automatically
    span.end();
  });
}
```

## Verify

Run your application, then check each signal in Bronto, filtering by the `service.name` you set:

* **Logs** — the [Search](https://app.bronto.io/search) page, in the dataset named after your service
* **Metrics** — the Metric Explorer
* **Traces** — the Explore Traces page

If nothing arrives:

* Confirm the Collector is running and reachable at `OTEL_EXPORTER_OTLP_ENDPOINT`, and that its pipelines include an `otlp` receiver and the Bronto exporters.
* Confirm `instrumentation.js` is preloaded with `--require` rather than imported from inside `app.js` — instrumentations must run before the libraries they patch are loaded.
* The batch processors export on a background timer. Short-lived scripts can exit before the first flush — call `sdk.shutdown()` before exit.

For signal-specific reference material, see [Send Metrics to Bronto](/metrics/send-metrics) and [Send Traces to Bronto](/tracing/send-traces).

## Direct export to Bronto

Without a Collector, the application exports straight to Bronto over OTLP/HTTP. The code in `instrumentation.js` is unchanged — point the endpoint at your Bronto region and add your API key:

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingestion.eu.bronto.io"   # or ingestion.us.bronto.io
export OTEL_EXPORTER_OTLP_HEADERS="x-bronto-api-key=<YOUR_API_KEY>"
```

| Region | Base endpoint                    |
| ------ | -------------------------------- |
| EU     | `https://ingestion.eu.bronto.io` |
| US     | `https://ingestion.us.bronto.io` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions.

<Tip>
  Prefer the Collector for multi-service environments — it batches, filters, and enriches telemetry, and keeps the API key out of every application. Direct export suits single services and simple architectures.
</Tip>

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry's [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) define `gen_ai.*` span attributes for model, token usage, and prompt and response content.

For OpenAI, add the contrib instrumentation to the `instrumentations` array:

```bash theme={"dark"}
npm install @opentelemetry/instrumentation-openai
```

```javascript instrumentation.js theme={"dark"}
const { OpenAIInstrumentation } = require('@opentelemetry/instrumentation-openai');

instrumentations: [getNodeAutoInstrumentations(), new OpenAIInstrumentation()],
```

For **Amazon Bedrock**, no extra package is needed — [`@opentelemetry/instrumentation-aws-sdk`](https://www.npmjs.com/package/@opentelemetry/instrumentation-aws-sdk) implements the GenAI conventions for Bedrock Runtime calls (`Converse`, `InvokeModel`) and is already bundled in `@opentelemetry/auto-instrumentations-node`.

Each model call then produces a span such as `chat gpt-4o-mini` in place of a plain HTTP client span:

| Attribute                        | Example       |
| -------------------------------- | ------------- |
| `gen_ai.provider.name`           | `openai`      |
| `gen_ai.request.model`           | `gpt-4o-mini` |
| `gen_ai.usage.input_tokens`      | `33`          |
| `gen_ai.usage.output_tokens`     | `74`          |
| `gen_ai.response.finish_reasons` | `["stop"]`    |

<Tip>
  Need broader provider or framework coverage? See [OpenLLMetry](/integrations/openllmetry) for supported packages, setup, and content-capture controls.
</Tip>

### Capture prompts and responses

Content capture is off by default:

```bash theme={"dark"}
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
```

The JS contrib instrumentations emit captured content as OTel log records rather than span attributes, so it travels through the logs pipeline correlated by `trace_id` and is not queryable on the span itself. To make it searchable alongside the span, emit it as a structured log record following [LLM Observability](/ai-features/llm-observability). The `OTEL_SEMCONV_STABILITY_OPT_IN` variant used by the Python instrumentations is not read by the JS packages.

### Manual spans

Where no instrumentation package exists for your provider, set the same attributes yourself:

```javascript theme={"dark"}
tracer.startActiveSpan('chat gpt-4o-mini', (span) => {
  span.setAttributes({
    'gen_ai.provider.name': 'openai',
    'gen_ai.request.model': 'gpt-4o-mini',
    'gen_ai.usage.input_tokens': 33,
    'gen_ai.usage.output_tokens': 74,
    // Bronto flattens array attributes — this is searchable as finish_reasons.0
    'gen_ai.response.finish_reasons': ['stop'],
  });
  span.end();
});
```

See the [GenAI span conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for the full attribute list.

For the recommended attribute set and Bronto search queries, see [LLM Observability](/ai-features/llm-observability).
