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

@ossy/observability

v1.8.3

Published

Structured logger with pluggable backends for the Ossy platform

Readme

@ossy/observability

Structured logger with pluggable backends for the Ossy platform. The integration files in this package (console.integration.js, sentry.integration.js, grafana-loki.integration.js, grafana-metrics.integration.js) are platform primitives auto-discovered by @ossy/app build. See PRIMITIVES.md for how integrations work.

createLogger(namespace)

Returns a logger bound to a namespace string. All log methods accept an optional context object that is merged into the output.

import { createLogger } from '@ossy/observability'

const log = createLogger('my-service')

log.info('Server started', { port: 3000 })
log.warn('Deprecated config key', { key: 'FOO' })
log.error('Request failed', { url: '/api/foo' }, error)
log.debug('Processing item', { id: '123' })

Methods

| Method | Signature | |--------|-----------| | log.info | (message: string, context?: object) => void | | log.warn | (message: string, context?: object) => void | | log.error | (message: string, context?: object, error?: Error) => void | | log.debug | (message: string, context?: object) => void |

Output format

  • Development (NODE_ENV !== 'production'): pretty-printed lines with a [namespace] prefix.
  • Production (NODE_ENV === 'production'): newline-delimited JSON:
    { "level": "info", "namespace": "my-service", "message": "Server started", "port": 3000, "timestamp": "2026-01-01T00:00:00.000Z" }

Adding a custom backend

A backend is an object with four functions. Register it with registerBackend:

import { registerBackend } from '@ossy/observability'

registerBackend({
  info(namespace, message, context) { /* ... */ },
  warn(namespace, message, context) { /* ... */ },
  error(namespace, message, context, error) { /* ... */ },
  debug(namespace, message, context) { /* ... */ },
})

All registered backends receive every log call (fan-out). The built-in console backend is always active unless you call clearBackends() first.

Built-in integrations

console.integration.js

| Field | Value | |-------|-------| | id | logger-console | | credentials | (none) |

Provides the same pretty/JSON output as the default backend, but as a discoverable *.integration.js primitive. Use it when you want the platform's integration loader to manage the console backend explicitly:

import { consoleIntegration } from '@ossy/observability'
import { registerBackend, clearBackends } from '@ossy/observability'

clearBackends()
registerBackend(await consoleIntegration.connect({ env: process.env }))

sentry.integration.js

| Field | Value | |-------|-------| | id | logger-sentry | | credentials | SENTRY_DSN |

Forwards error-level calls to Sentry via captureException. info, warn, and debug are no-ops in this backend.

Add the integration file to your app's manifest (or import it directly) and ensure SENTRY_DSN is set in the environment:

import { sentryIntegration } from '@ossy/observability'
import { registerBackend } from '@ossy/observability'

registerBackend(await sentryIntegration.connect({ env: process.env }))

grafana-loki.integration.js

| Field | Value | |-------|-------| | id | logger-grafana-loki | | credentials | GRAFANA_LOKI_URL, GRAFANA_LOKI_USER, GRAFANA_LOKI_API_KEY |

Forwards every log call to a Grafana Loki instance as a structured JSON stream. Calls are fire-and-forget — Loki outages never block application code.

Each log entry is pushed as a Loki stream labelled with level and namespace, making it queryable in Grafana Explore:

{namespace="my-service"} | json
{level="error"}

Setup:

import { grafanaLokiIntegration, registerBackend } from '@ossy/observability'

registerBackend(await grafanaLokiIntegration.connect({ env: process.env }))

Required environment variables:

| Variable | Description | |----------|-------------| | GRAFANA_LOKI_URL | Full Loki push endpoint, e.g. https://logs-prod-eu-west-0.grafana.net/loki/api/v1/push | | GRAFANA_LOKI_USER | Numeric user ID from the Grafana Cloud Loki data source page | | GRAFANA_LOKI_API_KEY | Grafana Cloud API key with the MetricsPublisher role |


grafana-metrics.integration.js

| Field | Value | |-------|-------| | id | metrics-grafana | | credentials | GRAFANA_METRICS_URL, GRAFANA_METRICS_USER, GRAFANA_METRICS_API_KEY |

Batches counter, gauge, and histogram observations in memory and flushes them every 10 seconds as a Prometheus text-format body to the configured push endpoint. No extra npm dependencies — uses native fetch only.

Setup:

import { grafanaMetricsIntegration } from '@ossy/observability'

await grafanaMetricsIntegration.connect({ env: process.env })

Call connect() once at startup. This wires up the metrics singleton exported from the package and starts the flush interval.

Using the metrics client:

import { metrics } from '@ossy/observability'

metrics.increment('task.run', { task: 'resize-image' })   // counter
metrics.gauge('queue.depth', 42, { queue: 'images' })      // gauge
metrics.timing('task.duration', 1234, { task: 'resize-image' }) // histogram (ms)

Metric names use dots in application code (task.run) and are normalised to underscores (task_run) in the Prometheus output.

Required environment variables:

| Variable | Description | |----------|-------------| | GRAFANA_METRICS_URL | Prometheus-compatible push endpoint, e.g. https://prometheus-prod-13-prod-us-east-0.grafana.net/api/prom/push | | GRAFANA_METRICS_USER | Numeric user ID from the Grafana Cloud Prometheus data source page | | GRAFANA_METRICS_API_KEY | Grafana Cloud API key with the MetricsPublisher role |

What appears in Grafana:

| Prometheus metric | Type | Labels | |-------------------|------|--------| | task_run | counter | task | | task_duration_bucket / _sum / _count | histogram | task |

Histogram buckets are pre-defined at 10 ms, 50 ms, 100 ms, 250 ms, 500 ms, 1 s, 2.5 s, 5 s, 10 s, and +Inf. Use a PromQL query like the following to graph p95 task duration:

histogram_quantile(0.95, sum by (le, task) (rate(task_duration_bucket[5m]))) / 1000

How tasks receive log automatically

@ossy/platform's TaskService injects a log instance into every task's run() call, scoped to the task's metadata.id:

// your-task.js
export const metadata = { id: 'my-task', triggers: [...] }

export async function run({ event, sdk, integrations, log }) {
  log.info('Task started', { eventType: event.type })
}

No extra setup needed — the logger is created automatically by the platform before dispatching the task.