Advanced Usage
This guide covers advanced usage patterns and integration scenarios for TypeORM Pino Logger.
Integration with Express/Fastify
Express with Request ID Correlation
import express from 'express';
import { v4 as uuidv4 } from 'uuid';
import pino from 'pino';
import { TypeOrmPinoLogger } from 'typeorm-pino-logger';
const app = express();
// Request ID middleware
app.use((req, res, next) => {
req.requestId = uuidv4();
next();
});
// Create logger with request context
app.use((req, res, next) => {
const logger = pino().child({ requestId: req.requestId });
req.logger = logger;
// Create TypeORM logger with request context
req.typeormLogger = new TypeOrmPinoLogger(logger, {
context: {
requestId: req.requestId,
userAgent: req.get('User-Agent'),
ip: req.ip
}
});
next();
});
// Use in your routes
app.get('/users/:id', async (req, res) => {
// Use the request-specific TypeORM logger
const dataSource = getDataSource();
dataSource.setOptions({
...dataSource.options,
logger: req.typeormLogger
});
const user = await dataSource.getRepository(User).findOne({
where: { id: req.params.id }
});
res.json(user);
});
Fastify Integration
import fastify from 'fastify';
import { TypeOrmPinoLogger } from 'typeorm-pino-logger';
const server = fastify({
logger: {
level: 'info',
transport: {
target: 'pino-pretty'
}
}
});
// Register TypeORM logger as a plugin
server.register(async (fastify) => {
const typeormLogger = new TypeOrmPinoLogger(fastify.log, {
context: {
service: 'api-server'
}
});
fastify.decorate('typeormLogger', typeormLogger);
});
// Use in routes
server.get('/users/:id', async (request, reply) => {
const dataSource = getDataSource();
dataSource.setOptions({
...dataSource.options,
logger: server.typeormLogger
});
const user = await dataSource.getRepository(User).findOne({
where: { id: request.params.id }
});
return user;
});
Multi-Database Logging
When working with multiple databases, you can create different logger instances:
import { TypeOrmPinoLogger } from 'typeorm-pino-logger';
import pino from 'pino';
const baseLogger = pino();
// Primary database logger
const primaryLogger = new TypeOrmPinoLogger(baseLogger, {
context: {
database: 'primary',
connection: 'main'
}
});
// Analytics database logger
const analyticsLogger = new TypeOrmPinoLogger(baseLogger, {
logQueries: false, // Disable query logging for analytics
logSlowQueries: true, // Only log slow queries
slowQueryThreshold: 5000, // 5 second threshold
context: {
database: 'analytics',
connection: 'readonly'
}
});
// Cache database logger
const cacheLogger = new TypeOrmPinoLogger(baseLogger, {
logQueries: false, // Disable all query logging for cache
logSlowQueries: false,
logQueryErrors: true, // Only log errors
context: {
database: 'cache',
connection: 'redis'
}
});
// Configure data sources
const primaryDataSource = new DataSource({
name: 'primary',
type: 'postgres',
// ... config
logger: primaryLogger
});
const analyticsDataSource = new DataSource({
name: 'analytics',
type: 'postgres',
// ... config
logger: analyticsLogger
});
Environment-Specific Configuration
Development Environment
const createDevelopmentLogger = () => {
const logger = pino({
level: 'debug',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname'
}
}
});
return new TypeOrmPinoLogger(logger, {
logQueries: true,
logSlowQueries: true,
slowQueryThreshold: 100, // Log queries > 100ms
logQueryErrors: true,
logSchemaOperations: true,
logMigrations: true,
maxQueryLength: 5000, // Longer queries for debugging
context: {
environment: 'development',
debug: true
}
});
};
Production Environment
const createProductionLogger = () => {
const logger = pino({
level: 'info',
// No transport in production - use stdout
});
return new TypeOrmPinoLogger(logger, {
logQueries: false, // Disable normal query logging
logSlowQueries: true, // Log slow queries
slowQueryThreshold: 2000, // Queries > 2 seconds
logQueryErrors: true, // Always log errors
logSchemaOperations: false, // Disable schema logging
logMigrations: true, // Keep migration logging
maxQueryLength: 500, // Shorter for production
context: {
environment: 'production',
service: process.env.SERVICE_NAME,
version: process.env.npm_package_version
}
});
};
Integration with Monitoring Systems
Prometheus Metrics
import { register, Counter, Histogram } from 'prom-client';
import pino from 'pino';
import { TypeOrmPinoLogger } from 'typeorm-pino-logger';
const queryCounter = new Counter({
name: 'typeorm_queries_total',
help: 'Total number of TypeORM queries',
labelNames: ['type', 'database']
});
const queryDuration = new Histogram({
name: 'typeorm_query_duration_seconds',
help: 'Duration of TypeORM queries',
labelNames: ['type', 'database']
});
// Custom logger that also records metrics
class MetricsTypeOrmPinoLogger extends TypeOrmPinoLogger {
logQuery(query: string, parameters?: unknown[], queryRunner?: QueryRunner) {
super.logQuery(query, parameters, queryRunner);
queryCounter.inc({ type: 'query', database: queryRunner?.connection?.name || 'default' });
}
logQuerySlow(time: number, query: string, parameters?: unknown[], queryRunner?: QueryRunner) {
super.logQuerySlow(time, query, parameters, queryRunner);
queryDuration.observe({ type: 'slow', database: queryRunner?.connection?.name || 'default' }, time / 1000);
}
logQueryError(error: string | Error, query: string, parameters?: unknown[], queryRunner?: QueryRunner) {
super.logQueryError(error, query, parameters, queryRunner);
queryCounter.inc({ type: 'error', database: queryRunner?.connection?.name || 'default' });
}
}
const logger = pino();
const typeormLogger = new MetricsTypeOrmPinoLogger(logger, {
context: {
service: 'my-api'
}
});
OpenTelemetry Integration
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
import { TypeOrmPinoLogger } from 'typeorm-pino-logger';
class TracingTypeOrmPinoLogger extends TypeOrmPinoLogger {
logQuery(query: string, parameters?: unknown[], queryRunner?: QueryRunner) {
const tracer = trace.getTracer('typeorm-pino-logger');
const span = tracer.startSpan('db.query', {
attributes: {
'db.statement': query,
'db.system': 'postgresql', // or your database type
'db.name': queryRunner?.connection?.options?.database
}
});
context.with(trace.setSpan(context.active(), span), () => {
super.logQuery(query, parameters, queryRunner);
span.setStatus({ code: SpanStatusCode.OK });
span.end();
});
}
logQueryError(error: string | Error, query: string, parameters?: unknown[], queryRunner?: QueryRunner) {
const tracer = trace.getTracer('typeorm-pino-logger');
const span = tracer.startSpan('db.query.error', {
attributes: {
'db.statement': query,
'db.system': 'postgresql',
'db.name': queryRunner?.connection?.options?.database
}
});
context.with(trace.setSpan(context.active(), span), () => {
super.logQueryError(error, query, parameters, queryRunner);
span.setStatus({
code: SpanStatusCode.ERROR,
message: typeof error === 'string' ? error : error.message
});
span.end();
});
}
}
Custom Log Processing
Log Filtering
import { Transform } from 'stream';
import pino from 'pino';
// Filter out sensitive queries
const filterSensitiveQueries = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
const log = JSON.parse(chunk);
// Filter out password-related queries
if (log.query && log.query.includes('password')) {
log.query = '[FILTERED - Contains sensitive data]';
delete log.parameters;
}
callback(null, JSON.stringify(log) + '\n');
}
});
const logger = pino({
transport: {
target: 'pino/file',
options: {
destination: process.stdout
}
}
}).child({}, {
transport: filterSensitiveQueries
});
const typeormLogger = new TypeOrmPinoLogger(logger);
Log Aggregation
// Aggregate slow queries for periodic reporting
class AggregatingTypeOrmPinoLogger extends TypeOrmPinoLogger {
private slowQueries: Array<{ query: string; time: number; timestamp: Date }> = [];
logQuerySlow(time: number, query: string, parameters?: unknown[], queryRunner?: QueryRunner) {
super.logQuerySlow(time, query, parameters, queryRunner);
this.slowQueries.push({
query: query.substring(0, 100), // Truncate for aggregation
time,
timestamp: new Date()
});
// Report aggregated data every 100 slow queries
if (this.slowQueries.length >= 100) {
this.reportSlowQueries();
}
}
private reportSlowQueries() {
const avgTime = this.slowQueries.reduce((sum, q) => sum + q.time, 0) / this.slowQueries.length;
const maxTime = Math.max(...this.slowQueries.map(q => q.time));
this.logger.info({
type: 'slow-query-report',
count: this.slowQueries.length,
avgTime,
maxTime,
timeRange: {
start: this.slowQueries[0].timestamp,
end: this.slowQueries[this.slowQueries.length - 1].timestamp
}
}, 'Slow query report');
this.slowQueries = [];
}
}
Advanced Message Filtering
The messageFilter option can be used to implement more complex filtering logic.
Filtering by Log Type
You can use the type argument to filter messages based on their category.
// Only log errors and slow queries
const typeBasedFilter: FilterFunction = (message, type) => {
return ['query-error', 'slow-query'].includes(type);
};
const logger = new TypeOrmPinoLogger(pino(), {
messageFilter: typeBasedFilter
});
Filtering Multiple Patterns
Use an array of regular expressions to filter out multiple message patterns.
const customFilter: FilterFunction = (message, type) => {
const suppressedPatterns = [
/^All classes found using provided glob pattern/,
/^Database schema loaded from/,
/^Connection established/
];
return !suppressedPatterns.some(pattern => pattern.test(message));
};
const logger = new TypeOrmPinoLogger(pino(), {
messageFilter: customFilter
});
Combining Content and Type Filtering
You can create sophisticated rules by combining both message and type.
const combinedFilter: FilterFunction = (message, type) => {
// Suppress general messages about glob patterns
if (type === 'general' && message.includes('glob pattern')) {
return false;
}
// Suppress schema build messages that are not errors
if (type === 'schema-build' && !message.toLowerCase().includes('error')) {
return false;
}
return true;
};
const logger = new TypeOrmPinoLogger(pino(), {
messageFilter: combinedFilter
});
Testing with TypeORM Pino Logger
import { TypeOrmPinoLogger } from 'typeorm-pino-logger';
import pino from 'pino';
// Create a test logger that captures logs
const createTestLogger = () => {
const logs: any[] = [];
const logger = pino({
transport: {
target: 'pino/file',
options: {
destination: {
write: (log: string) => {
logs.push(JSON.parse(log));
}
}
}
}
});
return { logger, logs };
};
// Use in tests
describe('Database Operations', () => {
it('should log slow queries', async () => {
const { logger, logs } = createTestLogger();
const typeormLogger = new TypeOrmPinoLogger(logger, {
slowQueryThreshold: 100
});
// Configure your test database with the logger
const dataSource = new DataSource({
// ... test config
logger: typeormLogger
});
// Perform slow operation
await dataSource.query('SELECT pg_sleep(0.2)');
// Assert log was created
expect(logs).toHaveLength(1);
expect(logs[0].type).toBe('slow-query');
expect(logs[0].executionTime).toBeGreaterThan(100);
});
});
These advanced patterns help you integrate TypeORM Pino Logger into complex applications with sophisticated logging, monitoring, and testing requirements.