npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swirepay-developer/common-logging-nodejs

v1.0.9

Published

Swirepay Common Logging SDK for Node.js

Downloads

802

Readme

Swirepay OpenTelemetry Node.js SDK

A drop-in observability library for Swirepay Node.js/TypeScript services. Call initObservability() once and your service ships traces, metrics, and logs over OTLP — no agent, no extra scripts, no per-environment config to copy around.

Features

  • Agentless by default. Builds the OTel SDK in-process. node app.js is all you need.
  • All three signals — traces, metrics, logs — individually toggleable.
  • Environment-aware. Resolves endpoint + sampling ratio from local / staging / production automatically.
  • AppMetrics facade — Micrometer-style count / recordTime / time / recordValue / gauge over the OTel Meter API.
  • @WithSpan / @Traced — wrap any method in a child span; caller awaits.
  • @FireAndForget — wrap a method in a child span; caller is not blocked.
  • @AutoTrace / @AutoTraceDeep — wrap every public method of a class (or its full prototype chain) in spans, no annotations required.
  • TracedService / DetachedService — extend these base classes for zero-annotation tracing without decorators.
  • traceProxy() — wrap any existing service instance with automatic per-method spans.
  • continueTrace — stitch an upstream trace (Java, mobile, etc.) into the current service so the full flow appears as one trace in Tempo.
  • @Scheduled / wrapScheduledFn — turn scheduled job executions into root spans.
  • withCurrentContext — propagate OTel context into deferred async tasks / worker queues.
  • withAsyncSpan — run async work as a child span under the current trace.
  • Kafka instrumentationwrapKafkaProducer / wrapKafkaConsumerHandler for end-to-end trace propagation.
  • DB instrumentationwrapPgPool / wrapMysqlPool / wrapMongoose / wrapMongoCollection / wrapDynamoDbClient for CLIENT spans on every query.
  • Outbound HTTP — wraps global fetch automatically; provides an axios interceptor factory.
  • Winston + OTLP logging — every log line goes to console AND the OTel Logs SDK with automatic trace_id / span_id injection.
  • Structured domain logginglogApiCall / logRequest / logWebhook with automatic body sanitization and sensitive field redaction.

Quickstart

1. Install

npm install @swirepay-developer/common-logging-nodejs

2. Start the local stack

cd java/deploy
docker compose up -d

3. Initialize once at startup

// app.ts  — must be the very first import
import { initObservability } from '@swirepay-developer/common-logging-nodejs'

const { logger, appMetrics, tracer } = await initObservability({
    serviceName: pkg.name,
    environment,
    endpoint: {
        protocol: 'http',  // or 'grpc'
        local: 'http://127.0.0.1:4318',  // Use 127.0.0.1 to avoid IPv6 issues
    },
    resourceAttributes: {
        'service.version': pkg.version,
    },
    logs: {
        level: process.env.LOG_LEVEL,
        mdcEnabled: true,
        debugEnabled: process.env.DEBUG_ENABLED === 'true',
    },
    auth: {
        type: 'bearer',
        token: process.env.OTEL_AUTH_TOKEN ?? secret?.swirepayOpenTelemetry ?? '',
    },
})

logger.info('Server started', { port: 3000 })

4. Run the example service

npm run dev

Configuration reference

All options are passed to initObservability().

| Option | Default | Description | |-------------------------------------|------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | enabled | true | Master switch. | | mode | 'sdk' | 'sdk' (in-process) or 'agent' (OTel agent owns SDK). | | serviceName | SERVICE_NAME env or 'unknown-service' | Logical service name. | | serviceNamespace | — | Optional namespace (e.g. 'payments'). | | environment | inferred from NODE_ENV | 'local' / 'staging' / 'production'. | | endpoint.override | — | Override the env-derived OTLP endpoint. | | endpoint.local | http://localhost:4318 | Default for local. | | endpoint.staging | http://otel-collector.staging...:4318 | Default for staging. | | endpoint.production | http://otel-collector.prod...:4318 | Default for production. | | endpoint.protocol | 'http' | OTLP protocol: 'http' or 'grpc'. Can also be set via OTEL_EXPORTER_OTLP_PROTOCOL env var. | | endpoint.override | — | Can be used to override existing endpoint for otel. | | traces.enabled | true | Toggle traces. | | traces.samplingRatio | 1.0 local / 0.3 staging / 0.1 production | Head-sampling ratio. | | metrics.enabled | true | Toggle metrics. | | metrics.exportIntervalMs | 30000 | OTLP push interval. | | logs.enabled | true | Toggle OTLP log export. | | logs.level | info | Logger Levels : error, warn, info, http, verbose, debug, silly | | logs.mdcEnabled | true | Inject trace_id / span_id into log records. | | logs.debugEnabled | true | Inject otel-dubugging logs into log records. | | logs.maxBodySize | undefined (no limit) | Max chars for captured request/response bodies. Truncates with …[truncated] beyond this. | | logs.additionalSensitiveKeys | [] | Extra field names to redact (case-insensitive substring match). Built-in list covers password, token, secret, apiKey, creditCard, cvv, ssn. | | instrumentation.enabled | true | Master switch for all auto-instrumentation. | | instrumentation.httpEnabled | true | HTTP server + client auto-instrumentation. | | instrumentation.httpClientEnabled | true | Wrap global fetch with CLIENT spans. | | resourceAttributes | {} | Extra resource attributes (e.g. { 'service.version': '1.0' }). | | auth.type | — | bearer, basic, header | | auth.headerName | — | If type is header then need to pass header name | | auth.token | — | token for authentication |


Usage examples

Logger — structured domain methods

logApiCall, logRequest, and logWebhook produce structured log entries with automatic body sanitization and sensitive field redaction. All three automatically pick up the active trace_id / span_id.

const { logger } = await initObservability({ serviceName: 'my-service' })

// Outbound HTTP call
logger.logApiCall({
  method: 'POST',
  url: 'https://api.stripe.com/v1/charges',
  statusCode: 200,
  duration: 142,
  requestBody: { amount: 1000, currency: 'usd' },
  responseBody: { id: 'ch_xxx', status: 'succeeded' },
})

// Inbound request (use alongside requestMiddleware or manually)
logger.logRequest({
  method: 'POST',
  path: '/api/orders',
  statusCode: 201,
  duration: 38,
  userId: 'usr_123',
  ip: '1.2.3.4',
  requestBody: req.body,
  responseBody: { orderId: 'ord_456' },
})

// Webhook receipt
logger.logWebhook({
  source: 'stripe',
  event: 'payment_intent.succeeded',
  webhookId: 'evt_xxx',
  status: 'processed',
  processingTime: 55,
  payload: event.data,
})

Logger — requestMiddleware (Express)

Automatically calls logRequest() for every inbound request when the response finishes. Captures and sanitizes both request and response bodies.

import express from 'express'

const app = express()
app.use(express.json())
app.use(logger.requestMiddleware())  // register after body-parser, before routes

The middleware:

  • Captures request body (from req.body if body-parser ran, or from raw stream)
  • Captures response body (by wrapping res.json() and res.send())
  • Redacts sensitive fields (password, token, apiKey, creditCard, cvv, etc.)
  • Reads trace-id / span-id from response headers set by otelTraceContextMiddleware
  • Calls logger.logRequest() on res.on('finish') with full context

getLogger — access the logger singleton anywhere

After initObservability() has been called, you can get the logger from any module without passing it around:

import { getLogger } from '@swirepay-developer/common-logging-nodejs'

const logger = getLogger()
logger.info('Processing payment', { orderId: 'ord_123' })

getObservabilityContext — access the full context after init

import { getObservabilityContext } from '@swirepay-developer/common-logging-nodejs'

// Anywhere after initObservability() has been awaited:
const { logger, appMetrics, tracer } = getObservabilityContext()

@AutoTrace class decorator

import { AutoTrace } from '@swirepay-developer/common-logging-nodejs'

@AutoTrace()
class PaymentService {
    async charge(amount: number) { }   // → INTERNAL span PaymentService.charge
    async refund(id: string) { }       // → INTERNAL span PaymentService.refund
    // getters/setters/toString are skipped automatically
}

@WithSpan decorator

import { WithSpan } from '@swirepay-developer/common-logging-nodejs'

class OrderService {
  @WithSpan('placeOrder')
  async place(order: Order): Promise<OrderResult> {
    // logs emitted here carry trace_id/span_id
    return await this.db.save(order)
  }
}

@Traced decorator

@Traced is an alias for @WithSpan that auto-names the span from the class and method name — no string argument needed.

import { Traced } from '@swirepay-developer/common-logging-nodejs'

class OrderService {
  @Traced()
  async place(order: Order): Promise<OrderResult> {  // → span: "OrderService.place"
    return await this.db.save(order)
  }
}

@FireAndForget decorator

Wraps an async method in a child span but does not block the caller. Use for side-effects like sending notifications or audit logging where the caller shouldn't wait.

import { FireAndForget } from '@swirepay-developer/common-logging-nodejs'

class NotificationService {
  @FireAndForget()
  async sendReceipt(payment: Payment): Promise<void> {
    // caller is not blocked — runs as a child span under the same trace
    await this.emailClient.send(payment.email, receipt)
  }
}

@AutoTraceDeep class decorator

Like @AutoTrace but instruments the full prototype chain on first instantiation. Use this on abstract base classes where subclasses inherit methods.

import { AutoTraceDeep } from '@swirepay-developer/common-logging-nodejs'

@AutoTraceDeep()
abstract class BaseRepository {
  async findById(id: string) { }   // → span on every concrete subclass
  async save(entity: unknown) { }
}

class OrderRepository extends BaseRepository {
  async findByCustomer(customerId: string) { }  // also traced
}

traceProxy — wrap any existing instance

Zero-annotation tracing for service instances you don't own or can't decorate. Every prototype method call gets a child span.

import { traceProxy } from '@swirepay-developer/common-logging-nodejs'

// Awaited — span ends when the method's promise resolves
const payments = traceProxy(new PaymentService(db))

// Fire-and-forget — caller is not blocked
const notify = traceProxy(new NotificationService(), { async: 'detach' })

// Exclude specific methods
const orders = traceProxy(new OrderService(), {
  exclude: ['healthCheck', /^internal/],
})

continueTrace — stitch an upstream trace

When another service (Java, mobile, etc.) passes its traceId + spanId to your Node.js service, use continueTrace to make the full end-to-end flow appear as a single trace in Tempo.

import { continueTrace, continueTraceSync } from '@swirepay-developer/common-logging-nodejs'

// Async handler — e.g. REST endpoint receiving trace context from a Java service
app.post('/api/orders', async (req, res) => {
  const result = await continueTrace(
    {
      traceId: req.headers['x-trace-id'] as string,
      spanId:  req.headers['x-span-id']  as string,
    },
    async () => {
      logger.info('Processing order')   // trace_id matches the upstream Java service
      return orderService.place(req.body)
    }
  )
  res.json(result)
})

// Kafka consumer — traceId comes from message headers
await continueTrace(
  { traceId: message.headers['x-trace-id'], spanId: message.headers['x-span-id'] },
  () => handleOrder(message.value)
)

// Synchronous variant
const result = continueTraceSync({ traceId, spanId }, () => processSync(data))

withAsyncSpan — child span for concurrent async work

Runs async work as a new child span under the current trace — same traceId, new spanId. Use when the async work is causally related to the current request but runs after the response is sent.

import { withAsyncSpan } from '@swirepay-developer/common-logging-nodejs'

app.post('/payments', async (req, res) => {
  const payment = await paymentService.charge(req.body)
  res.status(201).json(payment)

  // Runs after response — visible in Tempo under the same traceId
  withAsyncSpan('notifications.sendReceipt', async () => {
    await notificationService.sendReceipt(payment)
  })
})

Context propagation for async tasks

import { withCurrentContext } from '@swirepay-developer/common-logging-nodejs'

// Captures the current OTel context at submission time
const task = withCurrentContext(async () => {
  await doWork()  // runs under the original request's trace
})
setImmediate(task)

TracedService class decorator

import { TracedService } from '@swirepay-developer/common-logging-nodejs'

class PaymentService extends TracedService {
    
    async charge(amount: number) { }   // → INTERNAL span PaymentService.charge
    async refund(id: string) { }       // → INTERNAL span PaymentService.refund
    // getters/setters/toString are skipped automatically
}

DetachedService class

import { DetachedService } from '@swirepay-developer/common-logging-nodejs'

class PaymentService extends DetachedService {
    async charge(amount: number) { }   // → INTERNAL span PaymentService.charge
    async refund(id: string) { }       // → INTERNAL span PaymentService.refund
    // getters/setters/toString are skipped automatically
}

otelTraceContextMiddleware for trace management

Continue an upstream trace or start a new one on each API request

Use this in SDK mode (the default). It works alongside OTel auto-instrumentation — it does not create its own span, it enriches the one the SDK already created.

import { otelTraceContextMiddleware } from '@swirepay-developer/common-logging-nodejs'

const app = express()
// Register before your routes — reads serviceName/environment from initObservability() automatically
app.use(otelTraceContextMiddleware())

Override service name or environment if needed (rare):

app.use(otelTraceContextMiddleware({
  serviceName: 'payments-service-v2',
  environment: 'staging',
}))

otelHttpServerMiddleware for trace management

Continue an upstream trace or start a new one on each API request (auto-instrumentation disabled)

Use this only when OTel auto-instrumentation is disabled — i.e. mode: 'agent' or instrumentation.httpEnabled: false. It creates its own SERVER span from scratch instead of enriching an existing one.

import { otelHttpServerMiddleware } from '@swirepay-developer/common-logging-nodejs'

const app = express()
// Register before your routes
app.use(otelHttpServerMiddleware())

Axios interceptor

import axios from 'axios'
import { createAxiosInterceptor } from '@swirepay-developer/common-logging-nodejs'

const { request, response } = createAxiosInterceptor()
axios.interceptors.request.use(request)
axios.interceptors.response.use(response.onFulfilled, response.onRejected)

Which one should I use?

  • Default SDK mode → otelTraceContextMiddleware (works alongside auto-instrumentation)
  • Agent mode or instrumentation.httpEnabled: falseotelHttpServerMiddleware (creates its own span)

AppMetrics

const { appMetrics } = await initObservability({ serviceName: 'my-service' })

// Counter
appMetrics.count('orders.placed', { payment_method: 'card' })
appMetrics.countBy('orders.amount', 150.00)

// Timer
await appMetrics.time('orders.lookup.duration', async () => {
  return await db.findOrder(id)
})

// Histogram
appMetrics.recordValue('orders.amount_cents', order.amountCents)

// Gauge
appMetrics.gauge('queue.depth', () => queue.size())

// Native OTel meter (async counters, exemplars, etc.)
const meter = appMetrics.otelMeter()
meter.createCounter('my.counter').add(1)

@Scheduled decorator

import { Scheduled } from '@swirepay-developer/common-logging-nodejs'

class DirectoryPushJob {
  @Scheduled()
  async run() {
    // Each execution becomes a root INTERNAL span
    await this.pushPendingEntries()
  }
}

wrapScheduledFn (setInterval / node-cron)

import cron from 'node-cron'
import { wrapScheduledFn } from '@swirepay-developer/common-logging-nodejs'

cron.schedule('*/5 * * * *', wrapScheduledFn('CleanupJob.run', async () => {
  await cleanup()
}))

Kafka

import { Kafka } from 'kafkajs'
import { wrapKafkaProducer, wrapKafkaConsumerHandler } from '@swirepay-developer/common-logging-nodejs'

const kafka = new Kafka({ brokers: ['localhost:9092'] })

// Producer — send() emits PRODUCER spans + injects traceparent header
const producer = wrapKafkaProducer(kafka.producer())
await producer.connect()
await producer.send({ topic: 'orders', messages: [{ value: 'hello' }] })

// Consumer — eachMessage handler runs under a CONSUMER span
const consumer = kafka.consumer({ groupId: 'my-group' })
await consumer.run({
  eachMessage: wrapKafkaConsumerHandler(async ({ topic, message }) => {
    // upstream trace context is restored from message headers
    logger.info('Processing message', { topic })
  }),
})

Database (pg)

import { Pool } from 'pg'
import { wrapPgPool } from '@swirepay-developer/common-logging-nodejs'

const pool = wrapPgPool(new Pool({ connectionString: process.env.DATABASE_URL }), 'mydb')
// Every pool.query() now produces a CLIENT span with db.statement attribute
const result = await pool.query('SELECT * FROM orders WHERE id = $1', [id])

Database (MySQL2)

import mysql from 'mysql2/promise'
import { wrapMysqlPool } from '@swirepay-developer/common-logging-nodejs'

const pool = wrapMysqlPool(mysql.createPool({ ... }), 'mydb')
// Every pool.query() now produces a CLIENT span with db.statement attribute
const result = await pool.query('SELECT * FROM orders WHERE id = 1', [id])

Database (mongoose)

import mongoose from 'mongoose'
import { wrapMongoose } from '@swirepay-developer/common-logging-nodejs'

await mongoose.connect(process.env.MONGO_URI!)
wrapMongoose(mongoose)           // pass the mongoose instance

// DocumentDB (same driver, different URI):
await mongoose.connect('mongodb://user:[email protected]:27017/mydb?tls=true&...')
wrapMongoose(mongoose, 'mongodb') // 'mongodb' | 'documentdb' based on env set it up

Database (native driver)

import { MongoClient } from 'mongodb'
import { wrapMongoCollection } from '@swirepay-developer/common-logging-nodejs'

const client = new MongoClient(process.env.MONGO_URI!)
await client.connect()
const raw = client.db('mydb').collection('orders')
const ordersA = wrapMongoCollection(raw)          // MongoDB

// DocumentDB:
const ordersB = wrapMongoCollection(raw, 'documentdb')

Database (DynamoDB)

import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
import { wrapDynamoDbClient } from '@swirepay-developer/common-logging-nodejs'

// Plain client
const dynamo = wrapDynamoDbClient(new DynamoDBClient({ region: 'us-east-1' }))

// Document client
const docClient = wrapDynamoDbClient(
    DynamoDBDocumentClient.from(new DynamoDBClient({ region: 'us-east-1' }))
)

What initObservability() does on startup

  1. Resolves deployment environment (explicit → NODE_ENVlocal) and computes effective OTLP endpoint + sampling ratio.
  2. Builds NodeSDK with OTLPTraceExporter, OTLPMetricExporter, OTLPLogExporter, and getNodeAutoInstrumentations().
  3. Starts the SDK (registers global TracerProvider, MeterProvider, LoggerProvider).
  4. Wraps global fetch with CLIENT span creation.
  5. Creates a Winston logger wired to OtlpWinstonTransport (console + OTLP).
  6. Exposes AppMetrics, MetricsService, Tracer, Meter, OtelLogger beans.
  7. Registers SIGTERM / SIGINT handlers for graceful SDK flush + shutdown.

Compatibility

| Component | Versions | |---|---| | Node.js | 18+ | | TypeScript | 5.x | | Express | 4.x | | OpenTelemetry SDK | 0.209.x / API 1.9.x | | kafkajs | 2.x | | pg | 8.x | | mysql2 | 3.x |

Production

  • Set NODE_ENV=production (or environment: 'production') to get 10% head sampling automatically.
  • Point endpoint.production at your OTel Collector service.
  • Use traces.samplingRatio to override sampling per service.
  • Disable unused signals: traces: { enabled: false } / metrics: { enabled: false }.