Skip to main content
This page covers instrumenting a Node.js application with the OpenTelemetry SDK to send logs and traces to Bronto over OTLP/HTTP via a local OTel Collector. Logs are bridged via a Winston or Pino transport — no log statement changes needed. Traces are added by configuring a tracer provider alongside the log provider.
If you don’t have a Collector and want to export directly from your application to Bronto, see Direct export to Bronto at the bottom of this page.

Prerequisites

Install dependencies

npm install \
  @opentelemetry/api \
  @opentelemetry/sdk-logs \
  @opentelemetry/exporter-logs-otlp-http \
  @opentelemetry/resources \
  @opentelemetry/semantic-conventions \
  @opentelemetry/winston-transport \
  winston
PackagePurpose
@opentelemetry/sdk-logsSDK — LoggerProvider, processors
@opentelemetry/exporter-logs-otlp-httpOTLP/HTTP exporter
@opentelemetry/resourcesResource builder
@opentelemetry/semantic-conventionsStandard attribute key constants
@opentelemetry/winston-transportBridges Winston into OTel

Configure the OTel logger provider

Set up a LoggerProvider with an OTLP exporter. This is the same regardless of which logger you use.
otel-logging.js
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_NAMESPACE } = require('@opentelemetry/semantic-conventions');

const resource = new Resource({
  [SEMRESATTRS_SERVICE_NAME]: 'my-service',
  [SEMRESATTRS_SERVICE_NAMESPACE]: 'my-team',
  'deployment.environment': 'production',
});

const exporter = new OTLPLogExporter({
  url: 'http://localhost:4318/v1/logs',
});

const loggerProvider = new LoggerProvider({ resource });
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(exporter));

module.exports = { loggerProvider };

Configure the log bridge

const winston = require('winston');
const { OpenTelemetryTransportV3 } = require('@opentelemetry/winston-transport');

const logger = winston.createLogger({
  level: 'info',
  transports: [
    new winston.transports.Console(),
    new OpenTelemetryTransportV3(), // forwards to the global LoggerProvider
  ],
});

module.exports = logger;

Set resource attributes

Resource attributes are attached to every log record exported from this process. Two attributes drive how Bronto organises incoming logs:
OTel attributeBronto conceptDescription
service.nameDatasetGroups logs from one service
service.namespaceCollectionGroups related services or a team’s services
These are set via the Resource constructor in the provider setup above.

Complete example

app.js
// Must be required before any logger usage
const { loggerProvider } = require('./otel-logging');
const winston = require('winston');
const { OpenTelemetryTransportV3 } = require('@opentelemetry/winston-transport');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console(),
    new OpenTelemetryTransportV3(),
  ],
});

logger.info('Application started');
logger.warn('Low disk space', { disk_free_gb: 2.1 });
logger.error('Database connection failed', { error: 'timeout' });

// Flush on shutdown
process.on('SIGTERM', async () => {
  await loggerProvider.shutdown();
  process.exit(0);
});

Verify log collection

After running your application, open the Search page in Bronto. Filter by the dataset name you set in service.name — your log records should appear within a few seconds. If no logs appear, check:
  • The OTel Collector is running and reachable at the configured endpoint.
  • The Collector’s pipeline includes a logs pipeline with an otlp receiver and the Bronto exporter — see Connect Open Telemetry to Bronto.
  • otel-logging.js is required before any logger is created — the OpenTelemetryTransportV3 picks up the global LoggerProvider on instantiation.
  • BatchLogRecordProcessor exports on a background timer — for short-lived scripts, call loggerProvider.shutdown() before exit to flush pending records.

Traces

For Express, HTTP, gRPC, database drivers, and other popular libraries, use @opentelemetry/auto-instrumentations-node — it instruments all supported libraries automatically at startup with no code changes. Run node -r @opentelemetry/auto-instrumentations-node/register app.js or require it at the top of your entry point. See Node.js auto-instrumentation for setup details.

Install tracing dependencies

npm install \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/exporter-trace-otlp-http
PackagePurpose
@opentelemetry/sdk-trace-nodeNodeTracerProvider with automatic context propagation
@opentelemetry/exporter-trace-otlp-httpOTLP/HTTP span exporter

Configure the tracer provider

otel-tracing.js
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');

function configureOtelTracing(resource) {
  const exporter = new OTLPTraceExporter({
    url: 'http://localhost:4318/v1/traces',
  });

  const provider = new NodeTracerProvider({ resource });
  provider.addSpanProcessor(new BatchSpanProcessor(exporter));
  provider.register(); // sets global trace context propagation

  return provider;
}

module.exports = { configureOtelTracing };
Pass the same resource object used for logging so both signals share service.name and service.namespace.

Creating spans

const { trace } = require('@opentelemetry/api');

const tracer = trace.getTracer('my-service');

async function processPayment(amount, currency) {
  return tracer.startActiveSpan('process-payment', async (span) => {
    span.setAttributes({ 'payment.amount': amount, 'payment.currency': currency });
    logger.info('Processing payment');  // trace_id and span_id injected automatically
    // your code here
    span.end();
  });
}
Any log emitted inside an active span will automatically have trace_id and span_id attached via the OpenTelemetryTransportV3.

Complete example

app.js
const { resource, loggerProvider } = require('./otel-logging');
const { configureOtelTracing } = require('./otel-tracing');
const { trace } = require('@opentelemetry/api');
const winston = require('winston');
const { OpenTelemetryTransportV3 } = require('@opentelemetry/winston-transport');

// Tracing — pass the same resource used for logging
configureOtelTracing(resource);

const logger = winston.createLogger({
  transports: [new winston.transports.Console(), new OpenTelemetryTransportV3()],
});

const tracer = trace.getTracer('my-service');

tracer.startActiveSpan('handle-request', (span) => {
  span.setAttribute('http.method', 'GET');
  logger.info('Handling request');  // trace_id + span_id attached automatically
  span.end();
});

Direct export to Bronto

If you are not using an OTel Collector, export directly to Bronto by replacing the exporter configurations with the Bronto OTLP endpoints and your API key:
const logExporter = new OTLPLogExporter({
  url: 'https://ingestion.eu.bronto.io/v1/logs', // or ingestion.us.bronto.io
  headers: { 'x-bronto-api-key': '<YOUR_API_KEY>' },
});

const traceExporter = new OTLPTraceExporter({
  url: 'https://ingestion.eu.bronto.io/v1/traces', // or ingestion.us.bronto.io
  headers: { 'x-bronto-api-key': '<YOUR_API_KEY>' },
});
RegionLogs endpointTraces endpoint
EUhttps://ingestion.eu.bronto.io/v1/logshttps://ingestion.eu.bronto.io/v1/traces
UShttps://ingestion.us.bronto.io/v1/logshttps://ingestion.us.bronto.io/v1/traces
See API Keys for how to create a key with ingestion permissions. No other changes to the rest of the setup are required.