@sports-os/sl-logger
v1.0.4
Published
Shared structured logger (pino) for SportsOS backend services with PII scrubbing, child-logger API, and Express/NestJS integrations
Maintainers
Readme
@sports-os/sl-logger
Shared structured logger for SportsOS backend services.
Why
Services historically reached for console.log/.warn/.error. That gives us
unstructured text streams, no log levels, no correlation IDs, and no
PII scrubbing. This package wraps pino with sane
defaults and the integrations every service needs:
- env-aware level (debug in dev, info in staging, warn+ in prod)
- request-scoped child loggers carrying
traceId,userId,orgId - redaction of tokens, passwords, Stripe keys, cookies, etc.
- one-line setup for both Express and NestJS services
Install
The package is published to the SportsOS Azure Artifacts (@zain-sportslive)
feed. Inside any service:
npm install @sports-os/sl-loggerQuick start — Express
import express from 'express';
import {
createLogger,
createRequestLogger,
installProcessErrorHandlers,
} from '@sports-os/sl-logger';
const logger = createLogger({ serviceName: 'identity-service' });
installProcessErrorHandlers(logger);
const app = express();
app.use(
createRequestLogger({
logger,
bindings: (req) => ({
userId: req.auth?.payload?.sub,
orgId: req.auth?.payload?.orgId,
}),
}),
);
app.get('/health', (_req, res) => res.json({ ok: true }));
app.get('/me', (req, res) => {
// `req.log` is the per-request child logger. Use it inside handlers so the
// traceId is carried automatically.
req.log?.info('me.fetch', { actor: req.auth?.payload?.sub });
res.json({ /* ... */ });
});
app.listen(3000, () => logger.info('identity-service listening', { port: 3000 }));Quick start — NestJS
import { NestFactory } from '@nestjs/core';
import { createLogger } from '@sports-os/sl-logger';
import { createNestLoggerService } from '@sports-os/sl-logger/nestjs';
import { AppModule } from './app.module';
const rootLogger = createLogger({ serviceName: 'booking-ms' });
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: createNestLoggerService(rootLogger),
});
await app.listen(1122);
rootLogger.info('booking-ms listening', { port: 1122 });
}
bootstrap();Inside Nest providers, prefer Logger from @nestjs/common:
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class BookingsService {
private readonly log = new Logger(BookingsService.name);
cancel(id: string) {
this.log.warn(`cancelling booking ${id}`);
}
}The Nest Logger routes through our adapter, so output goes through pino with
all the redaction and env-aware formatting.
Log levels
Resolution order:
LOG_LEVELenv var (one offatal,error,warn,info,debug,trace,silent)levelargument passed tocreateLoggerNODE_ENV-based default:production→warn,staging/test→info, otherwisedebug
PII scrubbing
The default redact list covers the most common offenders:
- HTTP headers:
authorization,cookie,set-cookie,x-api-key,x-service-api-key,x-internal-secret - credentials:
password,*.password,currentPassword,token,accessToken,refreshToken,apiKey,secret,clientSecret - 2FA:
otp,totpSecret - payments:
stripeSecretKey,card.*,cardNumber,cvc,cvv - identity:
ssn,dob,idNumber
Add service-specific paths via redactPaths on createLogger. Always extend
the defaults — never skip them — even if the field "looks safe".
Trace IDs
createRequestLogger reads x-request-id and falls back to a generated id.
The id is attached to every req.log call and echoed back as x-trace-id so
clients (and other services) can correlate. Forward the header on outbound
HTTP calls between services to keep the trace stitched together.
Tests
The package is runtime-only — there are no tests yet. Behaviour is covered by each service's own tests once integration is complete.
Publishing
./publish-to-npm.sh