npm.io
0.0.9 • Published 10h ago

tpaga-logger

Licence
Version
0.0.9
Deps
3
Size
78 kB
Vulns
0
Weekly
0

tpaga-logger

Structured logging SDK for Tpaga Node.js microservices. Emits wide-event JSON (canonical log line) to stdout for AWS CloudWatch Logs Insights, following loggingsucks.com.

Installation

pnpm add tpaga-logger

Usage

AWS Lambda

Use withLogger to wrap a Lambda handler. It automatically handles timing, correlationId extraction from headers, and emits the terminal log event on success or error.

import { createLogger, withLogger } from 'tpaga-logger';

const logger = createLogger({
  service: 'my-service',
  environment: process.env.STAGE ?? 'dev',
});

export const handler = withLogger(logger, 'createCharge', async (event, log) => {
  log.with({ orderId: event.body.orderId, amount: event.body.amount });
  const result = await chargeService.create(event.body);
  return { statusCode: 200, body: JSON.stringify(result) };
});

What withLogger does automatically:

  • Extracts correlationId from x-correlation-id or x-request-id headers (generates a UUID if absent)
  • Starts a timer and calculates durationMs
  • Emits outcome: "success" with statusCode on completion
  • Emits outcome: "error" with serialized error on failure, then re-throws

Express

Register two middlewares from the library — one before routes, one after. Both are zero-config.

import express from 'express';
import { createLogger, withTpagaExpressLogger, tpagaExpressErrorLogger } from 'tpaga-logger';
import { errorHandler } from './middleware/errorHandler';

const logger = createLogger({ service: 'url-signer-service', environment: process.env.NODE_ENV ?? 'dev' });

const app = express();
app.use(express.json());
app.use(withTpagaExpressLogger(logger)); // before routes: timing, correlationId, req.log

app.use('/api/v1/sign', signRouter);

app.use(tpagaExpressErrorLogger);        // after routes: serialize + log any error
app.use(errorHandler);                   // after routes: map error → HTTP response (your code)

Express error middleware must be registered after routes — this is an Express constraint.

What withTpagaExpressLogger does automatically:

  • Extracts correlationId from x-correlation-id / x-request-id headers (generates UUID if absent)
  • Attaches a typed WideEventBuilder to req.log and res.log
  • Emits outcome: "success" for statusCode < 400, outcome: "error" otherwise
  • Calculates durationMs — always matches the actual response time

What tpagaExpressErrorLogger does automatically:

  • Serializes the error via serializeError — no stack, structured errorDetails for Zod-like errors
  • Adds error to the log context via req.log.with({ error })
  • Calls next(err) to pass the error to your errorHandler

req.log / res.log in controllers:

req.log is available anywhere in the request lifecycle — no import needed:

export const signUrl = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const input = signUrlSchema.parse(req.body);
    req.log.with({ url: input.url, ttlSeconds: input.ttlSeconds });
    res.status(200).json(generateSignedUrl(input));
  } catch (err) {
    req.log.with({ attemptedUrl: req.body?.url, stage: 'signing' }); // extra context on error
    next(err);
  }
};

res.log works the same — useful in error handler middleware where req params are prefixed with _.

statusCode in the log always matches the HTTP response — it's read from res.statusCode after the response is sent.

serializeError output:

Error type type message errorDetails
ZodError ZodError Validation failed array of Zod issues
AppError / Error Error original message
Unknown UnknownError String(err)

NestJS + Fastify v0.0.9+

Import from tpaga-logger/fastify. Register the plugin and the global interceptor — both are zero-config.

// app.module.ts
import { Module } from '@nestjs/common';
import { TpagaLoggerModule } from 'tpaga-logger/fastify';

@Module({
  imports: [
    TpagaLoggerModule.forRoot({
      service: process.env.SERVICE_NAME ?? 'my-service',
      environment: process.env.NODE_ENV ?? 'dev',
    }),
  ],
})
export class AppModule {}
// main.ts
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { withTpagaFastifyLogger, TpagaLoggerInterceptor, TPAGA_LOGGER } from 'tpaga-logger/fastify';
import type { Logger } from 'tpaga-logger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());

  const logger = app.get<Logger>(TPAGA_LOGGER);
  await app.register(withTpagaFastifyLogger(logger)); // before routes: timing, correlationId, request.tpagaLog
  app.useGlobalInterceptors(new TpagaLoggerInterceptor()); // catch errors before exception filters

  await app.listen(3000, '0.0.0.0');
}
bootstrap();

What withTpagaFastifyLogger does automatically:

  • Extracts correlationId from x-correlation-id / x-request-id headers (generates UUID if absent)
  • Attaches a typed WideEventBuilder to request.tpagaLog
  • Emits outcome: "success" for statusCode < 400, outcome: "error" otherwise
  • Calculates durationMs — always matches the actual response time

What TpagaLoggerInterceptor does automatically:

  • Wraps every route handler and catches thrown exceptions before NestJS exception filters run
  • Serializes the error via serializeError and adds it to the log context via request.tpagaLog.with({ error })
  • Re-throws so NestJS exception handling continues normally

Use request.tpagaLog (not request.log) — Fastify already attaches its own pino logger to request.log.

request.tpagaLog in controllers:

import { Controller, Get, Param, Req } from '@nestjs/common';
import type { FastifyRequest } from 'fastify';

@Controller('charges')
export class ChargesController {
  @Get(':id')
  async getCharge(@Req() req: FastifyRequest, @Param('id') id: string) {
    req.tpagaLog.with({ chargeId: id });
    const charge = await this.chargesService.findById(id);
    req.tpagaLog.with({ merchantId: charge.merchantId });
    return charge;
  }
}

API

tpaga-logger (Express + Lambda)

Export Description
createLogger(config) Creates a logger instance for a service
withLogger(logger, name, handler) Lambda handler wrapper
withTpagaExpressLogger(logger) Express middleware (register with app.use)
tpagaExpressErrorLogger Express error middleware (register after routes)
getLog(res) Gets the WideEventBuilder from an Express response
serializeError(err) Serializes an unknown error to a structured object
LOG_LEVELS ['info', 'warn', 'error', 'debug']
OUTCOMES ['success', 'error', 'validation_failed', ...]

tpaga-logger/fastify (NestJS + Fastify)

Export Description
withTpagaFastifyLogger(logger) Fastify plugin — register with app.register()
TpagaLoggerInterceptor NestJS interceptor — register with app.useGlobalInterceptors()
TpagaLoggerModule NestJS dynamic module — import in AppModule
TPAGA_LOGGER Injection token to retrieve the Logger instance via DI
LoggerConfig
type LoggerConfig = {
  service: string;
  environment?: string;
  redactKeys?: readonly string[]; // paths redacted with '[REDACTED]'
};
WideEventBuilder (log)
type WideEventBuilder = {
  with: (fields: Record<string, unknown>) => void;   // add context fields
  emit: (level: LogLevel, message: string, terminal: TerminalFields) => void; // send the log line
};

emit is called automatically by withLogger and withTpagaExpressLogger. Call it manually only when using startOperation directly.


Local development (pretty logs)

pino-pretty is bundled in the library — no extra install needed in your service. Just start with LOG_PRETTY=true:

LOG_PRETTY=true node dist/index.js
# or for Lambda local testing:
LOG_PRETTY=true npx ts-node src/handler.ts

Output in the console:

[09:11:00.142] INFO: createCharge completed
    service: "cash-in-manager"
    correlationId: "abc-123"
    orderId: "ORD-456"
    amount: 50000
    outcome: "success"
    durationMs: 142

In production, leave LOG_PRETTY unset — raw JSON goes to stdout and CloudWatch Logs Insights queries it natively.


Development

pnpm install
pnpm typecheck
pnpm lint
pnpm test
pnpm build

Docs

Repo

https://bitbucket.org/tpaga/tpaga-logger/src/main/