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

@twin-digital/observability-lib

v0.0.1

Published

AWS Lambda observability utilities with Powertools integration (logging, tracing, metrics)

Readme

@twin-digital/observability-lib

AWS Lambda observability utilities wrapping AWS Lambda Powertools for structured logging, metrics, and distributed tracing.

Installation

pnpm add @twin-digital/observability-lib

Quick Start

import { withObservability, MetricUnit } from '@twin-digital/observability-lib'

export const handler = withObservability(
  async (event, context, { internal }) => {
    const { logger, metrics } = internal

    // Log business events (NOT Lambda invocations)
    logger.info('Processing order', { orderId: event.orderId })

    // Record business metrics
    metrics.addMetric('OrderProcessed', MetricUnit.Count, 1)

    return { statusCode: 200, body: 'Success' }
  },
  { serviceName: 'order-service' },
)

Core Concepts

Service Name Scope

Service names should be scoped per microservice/bounded context, NOT per Lambda function.

Good Practice:

// All Lambdas in the same service share a service name
serviceName: 'bookify-render' // Used by authorizer, render-html, version Lambdas
serviceName: 'payment-service' // Used by all payment-related Lambdas
serviceName: 'user-management' // Used by all user Lambdas

Bad Practice:

// Don't use function-specific names
serviceName: 'bookify-authorizer' // Too granular
serviceName: 'bookify-render-html' // Too granular

This allows you to aggregate metrics and traces across related functions.

Logging Best Practices

⚠️ DO NOT log Lambda invocation start/end events - AWS already tracks invocations, duration, and errors automatically in CloudWatch metrics.

Good - Log business events:

logger.info('Order validated', { orderId, userId })
logger.warn('Payment retry required', { attempt: 3, reason })
logger.error('Order processing failed', { error, orderId })

Bad - Logging framework events (too noisy/expensive):

logger.info('Lambda invocation started') // ❌ Redundant
logger.info('Lambda completed') // ❌ Redundant
logger.debug('Processing request') // ❌ Too generic

Accessing Logger and Metrics

The middleware injects logger, metrics, and tracer into the third parameter of your handler:

;async (event, context, { internal }) => {
  const { logger, metrics, tracer } = internal

  logger.info('User authenticated', { userId: event.userId })
  metrics.addMetric('AuthSuccess', MetricUnit.Count, 1)

  if (tracer) {
    tracer.putMetadata('requestDetails', event.body)
  }
}

Note: when you use the middleware it sets a per-invocation logger into the async context. You can call getLogger() (from @twin-digital/logger-lib) anywhere in your code to obtain the contextual logger without passing it through function arguments. For scoped or test use-cases, wrap operations with runWithLogger(logger, callback) to run code with an isolated logger context that automatically reverts after the callback completes.

API Reference

withObservability(handler, options)

Wraps a Lambda handler with observability middleware.

Options

| Option | Type | Default | Description | | ------------------ | --------- | ------------------------- | ----------------------------------------------------------------- | | serviceName | string | POWERTOOLS_SERVICE_NAME | Service name for observability (per microservice, not per Lambda) | | logEvent | boolean | false | Log incoming events (⚠️ may log sensitive data) | | captureResponse | boolean | true | Capture HTTP responses in X-Ray traces | | skipTracing | boolean | auto-detect | Skip X-Ray tracing (auto-disabled for containers) | | captureColdStart | boolean | true | Record cold start metrics automatically |

Example

import { withObservability, MetricUnit } from '@twin-digital/observability-lib'

export const handler = withObservability(
  async (event, context, { internal }) => {
    const { logger, metrics } = internal

    logger.info('Processing payment', { amount: event.amount })
    metrics.addMetric('PaymentProcessed', MetricUnit.Count, 1)
    metrics.addDimension('Currency', event.currency)

    return { statusCode: 200, body: 'Payment processed' }
  },
  {
    serviceName: 'payment-service',
    captureColdStart: true,
  },
)

createLogger(options)

Creates a standalone logger instance (use when not using middleware).

import { createLogger } from '@twin-digital/observability-lib'

const logger = createLogger({ serviceName: 'my-service', logLevel: 'INFO' })

logger.info('User signed up', { userId: '123' })
logger.error('Database connection failed', { error })

// Add persistent context
logger.appendKeys({ tenantId: 'acme-corp' })

createMetrics(options)

Creates a standalone metrics instance (use when not using middleware).

import { createMetrics, MetricUnit } from '@twin-digital/observability-lib'

const metrics = createMetrics({
  namespace: 'MyApp/Orders',
  serviceName: 'order-service',
})

metrics.addMetric('OrdersProcessed', MetricUnit.Count, 1)
metrics.addDimension('OrderType', 'subscription')
metrics.publishStoredMetrics() // Required if not using middleware

createTracer(options)

Creates a standalone X-Ray tracer instance (use when not using middleware).

import { createTracer } from '@twin-digital/observability-lib'

const tracer = createTracer({ serviceName: 'my-service' })

// Add annotations (indexed, searchable)
tracer.putAnnotation('userId', '123')

// Add metadata (visible but not indexed)
tracer.putMetadata('requestPayload', event.body)

Serverless Framework Configuration

Add these environment variables to your serverless.yml:

provider:
  environment:
    POWERTOOLS_SERVICE_NAME: ${self:service}
    POWERTOOLS_LOG_LEVEL: ${self:custom.logLevel.${self:provider.stage}, 'INFO'}
    POWERTOOLS_METRICS_NAMESPACE: MyApp/${self:service}

  # Enable X-Ray tracing
  tracing:
    lambda: true

custom:
  logLevel:
    dev: DEBUG
    prod: INFO

Log Output Format

Logs are JSON-formatted for CloudWatch Logs Insights:

{
  "level": "INFO",
  "message": "Order validated",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "service": "order-service",
  "requestId": "abc-123",
  "correlationId": "req-789",
  "userId": "user-456",
  "data": [{ "orderId": "order-123" }]
}

Metrics Output Format

Metrics use CloudWatch Embedded Metric Format (EMF):

{
  "_aws": {
    "Timestamp": 1705318200000,
    "CloudWatchMetrics": [
      {
        "Namespace": "MyApp/OrderService",
        "Dimensions": [["service"]],
        "Metrics": [
          { "Name": "OrderProcessed", "Unit": "Count" },
          { "Name": "ColdStart", "Unit": "Count" }
        ]
      }
    ]
  },
  "service": "order-service",
  "OrderProcessed": 1,
  "ColdStart": 1
}

Migration Guide

From Console Logging

Before:

export const handler = async (event) => {
  console.log('Processing request', JSON.stringify(event))
  console.error('Error occurred:', error)
  return { statusCode: 200 }
}

After:

import { withObservability } from '@twin-digital/observability-lib'

export const handler = withObservability(
  async (event, context, { internal }) => {
    const { logger } = internal
    logger.info('Processing order', { orderId: event.orderId })
    return { statusCode: 200 }
  },
  { serviceName: 'order-service' },
)

From Manual Powertools Setup

Before:

import { Logger } from '@aws-lambda-powertools/logger'
import { Metrics } from '@aws-lambda-powertools/metrics'

const logger = new Logger()
const metrics = new Metrics()

export const handler = async (event) => {
  logger.info('Processing')
  metrics.addMetric('Processed', MetricUnit.Count, 1)
  metrics.publishStoredMetrics()
  return { statusCode: 200 }
}

After:

import { withObservability, MetricUnit } from '@twin-digital/observability-lib'

export const handler = withObservability(
  async (event, context, { internal }) => {
    const { logger, metrics } = internal
    logger.info('Order processed', { orderId: event.orderId })
    metrics.addMetric('Processed', MetricUnit.Count, 1)
    // No need to call publishStoredMetrics() - handled by middleware
    return { statusCode: 200 }
  },
  { serviceName: 'order-service' },
)

Advanced Usage

Middleware Composition

import middy from '@middy/core'
import { observabilityMiddleware } from '@twin-digital/observability-lib'
import httpErrorHandler from '@middy/http-error-handler'

const handler = middy(async (event, context, { internal }) => {
  const { logger } = internal
  logger.info('Processing HTTP request')
  return { statusCode: 200, body: 'OK' }
})
  .use(observabilityMiddleware({ serviceName: 'api-service' }))
  .use(httpErrorHandler())

export { handler }

Custom Metric Dimensions

export const handler = withObservability(
  async (event, context, { internal }) => {
    const { metrics } = internal

    // Add dimensions for filtering/grouping
    metrics.addDimension('Environment', process.env.STAGE)
    metrics.addDimension('Region', process.env.AWS_REGION)
    metrics.addDimension('OrderType', event.orderType)

    metrics.addMetric('OrderCreated', MetricUnit.Count, 1)

    return { statusCode: 200 }
  },
  { serviceName: 'order-service' },
)

Conditional Tracing

export const handler = withObservability(
  async (event, context, { internal }) => {
    const { tracer } = internal

    if (tracer) {
      // Only add traces when X-Ray is available
      tracer.putAnnotation('orderId', event.orderId)
      tracer.putMetadata('orderDetails', event.items)
    }

    return { statusCode: 200 }
  },
  {
    serviceName: 'order-service',
    skipTracing: false, // Enable tracing explicitly
  },
)

Troubleshooting

Logger Context Not Appearing

Problem: Logger context (requestId, userId) not showing in logs.

Solution: Ensure you're using the logger from internal, not creating a new instance:

// ✅ Correct
;async (event, context, { internal }) => {
  const { logger } = internal
  logger.info('Message')
}

// ❌ Wrong
import { createLogger } from '@twin-digital/observability-lib'
const logger = createLogger() // Creates new instance without context

Metrics Not Publishing

Problem: Metrics not appearing in CloudWatch.

Solution: The middleware automatically publishes metrics. Don't call publishStoredMetrics() manually when using the middleware.

X-Ray Traces Missing

Problem: No traces in X-Ray console.

Solution:

  1. Ensure X-Ray is enabled in serverless.yml: tracing.lambda: true
  2. Check if running in container - tracing auto-disables for containers without X-Ray daemon
  3. Override with skipTracing: false if needed

License

MIT