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

internal-utils

v0.1.0

Published

Internal utilities for logging and tracing with OpenTelemetry

Readme

internal-utils

Structured logging and tracing for TypeScript services, built on OpenTelemetry.

Run Example

bun run ./src/example.ts 

Quick Start

import { TracingBuilder } from 'internal-utils';

const { logger, shutdown } = await new TracingBuilder()
  .withJson(false)
  .build();

logger.info("App started", { version: "1.0.0" });

await shutdown();

Initialization

TracingBuilder configures the OpenTelemetry SDK and returns a logger and shutdown function.

import { TracingBuilder } from 'internal-utils';

const { logger, shutdown } = await new TracingBuilder()
  .withJson(false)           // pretty output (colored). true = JSON lines (default)
  .withOtel({
    serviceName: 'my-service',
    serviceVersion: '1.0.0',
    endpointTraces: 'http://localhost:4318/v1/traces',
    endpointMetrics: 'http://localhost:4318/v1/metrics',
    endpointLogs: 'http://localhost:4318/v1/logs',
  })
  .build();

Logs always print to the console. When OTLP endpoints are configured, logs/traces/metrics are also exported to your collector.

Logging

Six levels: trace, debug, info, warn, error, fatal. Second argument is structured fields.

logger.info("User logged in", { userId: 123, action: "login" });
logger.error("Connection lost", { host: "db.internal", retries: 3 });

Child Loggers

child() creates a new logger that merges persistent fields into every log.

const reqLogger = logger.child({ requestId: "abc-123", userId: "user-42" });
reqLogger.info("Handling request");     // includes requestId + userId
reqLogger.warn("Slow query", { ms: 480 }); // includes requestId + userId + ms

Global Logger Access

getLogger(name) works from any module after build() has been called. Before init, it returns a no-op logger (safe to call, does nothing).

import { getLogger } from 'internal-utils';

const logger = getLogger('payments');
logger.info("Payment processed", { amount: 99.99 });

Tracing

withSpan

Wraps an async operation in a span. Automatically ends the span, records exceptions, and sets error status. Logs inside the callback carry trace_id and span_id.

import { withSpan } from 'internal-utils';

await withSpan("order.process", async (span) => {
  span.setAttribute("orderId", "ord-123");
  await processOrder();
});

With options (SpanKind, initial attributes, custom tracer name):

import { SpanKind } from '@opentelemetry/api';

await withSpan("db.query", {
  kind: SpanKind.CLIENT,
  attributes: { "db.system": "postgres" },
}, async () => {
  await db.query(sql);
});

withSpanSync

Same as withSpan but for synchronous work.

import { withSpanSync } from 'internal-utils';

const isValid = withSpanSync("validate.input", () => {
  return input.length > 0;
});

Error Handling

Exceptions thrown inside withSpan/withSpanSync are recorded on the span, the span status is set to ERROR, and the error is re-thrown.

try {
  await withSpan("order.charge", async () => {
    throw new Error("Payment declined");
  });
} catch (err) {
  logger.error("Charge failed");
}

Nested Spans

Spans nest automatically via context propagation.

await withSpan("http.request", async () => {
  // parent span

  await withSpan("auth.validate", async () => {
    // child span
  });

  await withSpan("db.insert", async () => {
    // child span
  });
});

Metrics

createMetrics(name) returns a Metrics instance for creating instruments.

import { createMetrics } from 'internal-utils';

const m = createMetrics('my-service');

Counter

Values that only go up (requests, errors, orders).

const orderCount = m.counter("orders.total");
orderCount.add(1, { type: "new" });

UpDownCounter

Values that go up and down (active connections, queue size).

const activeJobs = m.upDownCounter("jobs.active");
activeJobs.add(1);   // job started
activeJobs.add(-1);  // job finished

Histogram

Distributions (latency, request size).

const duration = m.histogram("http.request.duration", { unit: "ms" });
duration.record(45.2, { method: "GET", route: "/orders" });

Histograms have a .time() helper that measures duration automatically:

// Async — records elapsed time in ms
await duration.time({ method: "POST", route: "/orders" }, async () => {
  await processOrder();
});

// Without attributes
const result = await duration.time(async () => {
  return await db.query(sql);
});

// Sync
const parsed = duration.timeSync(() => JSON.parse(data));

Gauge

Observes a value via callback at export time.

m.gauge("queue.size", () => queue.length);

Raw Meter Access

For advanced use cases, access the underlying OTel meter directly.

import { getMeter } from 'internal-utils';

const meter = getMeter('my-service');
const counter = meter.createCounter('custom_metric');

Output Formats

JSON (withJson(true), default):

{"timestamp":"2026-02-20T10:30:00.000Z","level":"INFO","message":"App started","version":"1.0.0"}

Pretty (withJson(false)):

2026-02-20T10:30:00.000Z  INFO   App started  version=1.0.0

Pretty mode uses colored severity levels: TRACE (magenta), DEBUG (blue), INFO (green), WARN (yellow), ERROR/FATAL (red).

Example

See src/example.ts for a runnable showcase of all features:

npx tsx src/example.ts

API Reference

TracingBuilder

| Method | Description | |--------|-------------| | .withJson(enabled) | true = JSON lines (default), false = colored pretty output | | .withOtel(params) | Configure OTLP endpoints and service identity | | .build() | Start the SDK, returns { logger, shutdown } |

OtelParams

| Field | Description | |-------|-------------| | serviceName | Service name for the logger and resource | | serviceVersion | Service version | | endpointTraces | OTLP traces endpoint URL | | endpointMetrics | OTLP metrics endpoint URL | | endpointLogs | OTLP logs endpoint URL |

Logger

| Method | Description | |--------|-------------| | .trace(message, fields?) | Log at TRACE level | | .debug(message, fields?) | Log at DEBUG level | | .info(message, fields?) | Log at INFO level | | .warn(message, fields?) | Log at WARN level | | .error(message, fields?) | Log at ERROR level | | .fatal(message, fields?) | Log at FATAL level | | .child(fields) | Create child logger with persistent fields |

Metrics

| Method | Description | |--------|-------------| | .counter(name, options?) | Create a counter (monotonically increasing) | | .histogram(name, options?) | Create a TimedHistogram (distributions + .time() helper) | | .upDownCounter(name, options?) | Create an up-down counter | | .gauge(name, callback, options?) | Create an observable gauge |

TimedHistogram

| Method | Description | |--------|-------------| | .record(value, attrs?) | Record a value manually | | .time([attrs], fn) | Run async function, record elapsed ms | | .timeSync([attrs], fn) | Run sync function, record elapsed ms |

Functions

| Function | Description | |----------|-------------| | getLogger(name) | Get a named logger (works after build()) | | createMetrics(name) | Create a Metrics instance bound to a meter | | getTracer(name) | Get an OpenTelemetry tracer | | getMeter(name) | Get an OpenTelemetry meter (raw access) | | withSpan(name, [options], fn) | Run async function inside a traced span | | withSpanSync(name, [options], fn) | Run sync function inside a traced span |

TraceOptions

Extends OpenTelemetry SpanOptions with:

| Field | Description | |-------|-------------| | tracer | Custom tracer name (default: "app") | | kind | SpanKind.SERVER, CLIENT, INTERNAL, etc. | | attributes | Initial span attributes |