Putnami
DocsGitHub

Licensed under FSL-1.1-MIT

Getting Started
Concepts
How To
Build A Web App
Build An Api Service
Share Code Between Projects
Configure Your App
Add Persistence
Add Authentication
Add Background Jobs
Principles
Tooling & Workspace
Workspace Overview
Cli
Jobs & Commands
SDK
Error Handling
Extensions
Typescript
Go
Python
Docker
Ci
Frameworks
Typescript
OverviewWebReact RoutingForms And ActionsStatic FilesApiErrors And ResponsesConfigurationLoggingHttp And MiddlewareDependency InjectionPlugins And LifecycleSessionsAuthPersistenceEventsStorageCachingWebsocketsTestingHealth ChecksTelemetryProto GrpcSmart Client
Go
OverviewHttpDependency InjectionPlugins And LifecycleConfigurationSecurityPersistenceErrorsEventsStorageCachingLoggingTelemetryGrpcService ClientsValidationOpenapiTesting
Platform
  1. DocsSeparator
  2. FrameworksSeparator
  3. TypescriptSeparator
  4. Logging

Logging

Putnami provides structured, context-aware logging via @putnami/runtime. Logs automatically include trace IDs, request context, and structured data — no manual wiring required.

Basic usage

import { useLogger } from '@putnami/runtime';

const logger = useLogger('orders');

logger.info('Order created', { orderId: '123' });
logger.warn('Low stock', { sku: 'WIDGET-01', remaining: 3 });
logger.error('Payment failed', error);

useLogger() returns a context-aware logger. When called inside an HTTP request, the logger automatically includes the request's trace ID.

Log levels

Four levels, in order of severity:

Level Use for
debug Development-only detail: variable values, branching decisions
info Normal operations: request handled, job completed, record created
warn Recoverable problems: deprecated usage, slow query, retry triggered
error Failures: unhandled exceptions, external service down, data corruption

The default level is info. Messages below the configured level are filtered out.

Named loggers

Create child loggers to organize output by concern:

const logger = useLogger('app');
const authLogger = logger.named('auth');
const dbLogger = logger.named('db');

authLogger.info('Token validated');  // logger: "app.auth"
dbLogger.warn('Slow query');         // logger: "app.db"

Adding context

Use with() to attach structured data to all subsequent log entries:

const logger = useLogger('api');
logger.with('userId', 'usr_123');
logger.with('tenantId', 'acme');

logger.info('Request handled');
// → includes userId and tenantId in the log entry

Error logging

Pass Error objects directly — stack traces are extracted automatically:

try {
  await processPayment(order);
} catch (error) {
  logger.error('Payment processing failed', error);
}

The log entry includes error.name, error.message, and error.stack as structured fields.

HTTP integration

The logger() plugin adds automatic request logging with trace ID propagation:

import { application, http, logger } from '@putnami/application';

const app = application()
  .use(http({ port: 3000 }))
  .use(logger({ exclude: ['/health'] }));

Every request logs method, route, status code, and duration. Trace IDs are extracted from X-Cloud-Trace-Context or X-Correlation-ID headers, or generated automatically.

Output formats

JSON (default)

Structured JSON compatible with Google Cloud Logging. Each entry is a single JSON line with severity, message, timestamp, and context fields:

{"severity":"INFO","message":"Order created","timestamp":"2025-01-15T10:30:00.000Z","logger":"orders","traceId":"abc-123","orderId":"123"}

Console

Human-readable text for local development:

[abc-123] [INFO] [orders] Order created { orderId: '123' }

Configuration

Via environment variables:

LOG_LEVEL=debug          # debug | info | warn | error (default: info)
LOGGER_JSON=false        # true for JSON, false for console (default: true)

Via YAML (conf/.env.local.yaml):

logger:
  level: debug
  json: false

Buffered logging

Group logs by request and flush together. Useful for high-throughput services:

logger:
  buffer: true
  bufferMaxSize: 100       # Entries per context before flush
  bufferFlushInterval: 5000 # ms between flushes

Error-level entries flush the entire request context immediately.

Testing

Use MemoryLogger to capture and assert on log output:

import { MemoryLogger } from '@putnami/runtime';

const logger = new MemoryLogger('test');
logger.info('hello');
logger.error('failed', new Error('boom'));

expect(logger.entries).toHaveLength(2);
expect(logger.entries[0].message).toBe('hello');
expect(logger.entries[1].error?.name).toBe('Error');

Global exception handling

Unhandled exceptions and rejected promises are captured automatically when the application starts. They log through the same logger with full context (trace ID, request data) when they occur inside a request.

Go

The Go logger follows the same patterns and output formats as the TypeScript runtime.

Basic usage

import "go.putnami.dev/logger"

log := logger.Default().Named("orders")

log.Info("Order created", slog.String("orderId", "123"))
log.Warn("Low stock", slog.String("sku", "WIDGET-01"), slog.Int("remaining", 3))
log.Error("Payment failed", err)

Default() returns a shared logger configured from environment variables. Named() creates a child logger with an appended name.

Output formats

JSON output by default (same structure as TypeScript):

{"severity":"INFO","message":"Order created","timestamp":"2025-01-15T10:30:00.000Z","logger":"orders","orderId":"123"}

Console output (same format as TypeScript):

[INFO] [orders] Order created orderId=123

Configuration

Via environment variables:

LOG_LEVEL=debug          # debug | info | warn | error (default: info)

Context integration

log := logger.Default().Named("api")
reqLog := log.With("userId", "usr_123")
reqLog.Info("Request handled")  // includes userId in log entry

// Attach to context
ctx = logger.WithLogger(ctx, reqLog)
logger.FromContext(ctx).Info("from context")

Testing

sink := logger.NewMemorySink()
log := logger.New("test", logger.LevelDebug, sink)

log.Info("hello")
log.Error("failed", errors.New("boom"))

// sink.Len() == 2
// sink.Last().Message == "failed"
// sink.Last().Error.Message == "boom"

On this page

  • Logging
  • Basic usage
  • Log levels
  • Named loggers
  • Adding context
  • Error logging
  • HTTP integration
  • Output formats
  • JSON (default)
  • Console
  • Configuration
  • Buffered logging
  • Testing
  • Global exception handling
  • Go
  • Basic usage
  • Output formats
  • Configuration
  • Context integration
  • Testing