@hectormartin42/nestjs-seq-logger
v1.0.1
Published
NestJS module for logging to Seq using structured logging with CLEF format
Maintainers
Readme
@hectormartin42/nestjs-seq-logger
A powerful NestJS module for structured logging to Seq using the CLEF (Compact Log Event Format) format. Built with resilient design principles - logging failures will never break your application.
🚀 Features
- Easy NestJS Integration - Drop-in replacement for default logger
- Structured Logging - CLEF format with rich metadata
- Automatic Batching - Efficient log transmission every 5 seconds
- Resilient Design - Silent failure handling, won't crash your app
- Configurable Levels - Filter logs by importance
- Distributed Tracing - Full support for trace/span correlation
- Environment Flexible - Works in development and production
- Zero Dependencies - Only requires axios for HTTP requests
📦 Installation & Setup
Step 1: Install the Package
# NPM
npm install @hectormartin42/nestjs-seq-logger
# Yarn
yarn add @hectormartin42/nestjs-seq-logger
# PNPM
pnpm add @hectormartin42/nestjs-seq-loggerStep 2: Install Required Peer Dependencies
# If you don't have these already in your NestJS project
npm install @nestjs/common @nestjs/core reflect-metadata rxjsStep 3: Set Up Seq Server
Option A: Docker (Recommended for Development)
# Quick start with Docker
docker run --name seq -d --restart unless-stopped \
-e ACCEPT_EULA=Y \
-p 5341:80 \
datalust/seq:latest
# Access Seq at: http://localhost:5341Option B: Docker Compose
Create docker-compose.yml:
version: '3.8'
services:
seq:
image: datalust/seq:latest
container_name: seq-logger
environment:
- ACCEPT_EULA=Y
- SEQ_FIRSTRUN_ADMINPASSWORDHASH=# Optional: set admin password
ports:
- "5341:80"
volumes:
- seq-data:/data
restart: unless-stopped
volumes:
seq-data:Run with: docker-compose up -d
Option C: Production Seq Server
For production, use Seq Cloud or install Seq on your server following official docs.
Step 4: Configure Environment Variables
Add to your .env file:
# Required
SEQ_URL=http://localhost:5341
# Optional (recommended for production)
SEQ_API_KEY=your-api-key-here
APP_NAME=my-nestjs-app
SEQ_WORKSPACE=development🔧 NestJS Integration
Basic Setup (app.module.ts)
import { Module } from '@nestjs/common';
import { SeqLoggerModule } from '@hectormartin42/nestjs-seq-logger';
@Module({
imports: [
// Basic configuration
SeqLoggerModule.forRoot({
seqUrl: 'http://localhost:5341',
apiKey: 'your-api-key', // Optional
application: 'my-app',
workspace: 'development',
minimumLevel: 'information'
}),
],
// ... your other modules
})
export class AppModule {}Environment Variables Setup (Recommended)
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { SeqLoggerModule } from '@hectormartin42/nestjs-seq-logger';
@Module({
imports: [
ConfigModule.forRoot(),
// Using environment variables
SeqLoggerModule.forRoot({
seqUrl: process.env.SEQ_URL,
apiKey: process.env.SEQ_API_KEY,
application: process.env.APP_NAME || 'my-app',
workspace: process.env.SEQ_WORKSPACE || 'development',
minimumLevel: 'information'
}),
],
})
export class AppModule {}Advanced Async Configuration
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SeqLoggerModule } from '@hectormartin42/nestjs-seq-logger';
@Module({
imports: [
ConfigModule.forRoot(),
// Async configuration with ConfigService
SeqLoggerModule.forRootAsync({
useFactory: async (configService: ConfigService) => ({
seqUrl: configService.get('SEQ_URL'),
apiKey: configService.get('SEQ_API_KEY'),
application: configService.get('APP_NAME', 'my-app'),
workspace: configService.get('SEQ_WORKSPACE', 'development'),
minimumLevel: configService.get('LOG_LEVEL', 'information'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Replace Default NestJS Logger (Optional)
In your main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SeqLoggerService } from '@hectormartin42/nestjs-seq-logger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Replace default logger with Seq logger
const seqLogger = app.get(SeqLoggerService);
app.useLogger(seqLogger);
await app.listen(3000);
// This will now log to Seq!
seqLogger.log('Application started successfully', 'Bootstrap', {
port: 3000,
environment: process.env.NODE_ENV
});
}
bootstrap();📝 Usage Examples
Basic Service Usage
import { Injectable } from '@nestjs/common';
import { SeqLoggerService } from '@hectormartin42/nestjs-seq-logger';
@Injectable()
export class UserService {
constructor(private readonly logger: SeqLoggerService) {}
async createUser(userData: CreateUserDto) {
const startTime = Date.now();
this.logger.log('Creating new user', 'UserService', {
email: userData.email,
role: userData.role,
timestamp: new Date().toISOString()
});
try {
const user = await this.userRepository.save(userData);
this.logger.log('User created successfully', 'UserService', {
userId: user.id,
email: user.email,
duration: `${Date.now() - startTime}ms`
});
return user;
} catch (error) {
this.logger.error('Failed to create user', error, 'UserService', {
email: userData.email,
errorCode: error.code,
duration: `${Date.now() - startTime}ms`
});
throw error;
}
}
}HTTP Controller Example
import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import { SeqLoggerService } from '@hectormartin42/nestjs-seq-logger';
@Controller('users')
export class UserController {
constructor(
private readonly userService: UserService,
private readonly logger: SeqLoggerService
) {}
@Post()
async createUser(@Body() userData: CreateUserDto) {
const correlationId = crypto.randomUUID();
this.logger.log('HTTP request received', 'UserController', {
method: 'POST',
endpoint: '/users',
correlationId,
userAgent: 'browser-info'
});
try {
const result = await this.userService.createUser(userData);
this.logger.log('HTTP request completed', 'UserController', {
method: 'POST',
endpoint: '/users',
correlationId,
statusCode: 201,
userId: result.id
});
return result;
} catch (error) {
this.logger.error('HTTP request failed', error, 'UserController', {
method: 'POST',
endpoint: '/users',
correlationId,
statusCode: error.status || 500
});
throw error;
}
}
}Database Service with Detailed Logging
@Injectable()
export class DatabaseService {
constructor(private readonly logger: SeqLoggerService) {}
async executeQuery(sql: string, params: any[] = []) {
const queryId = crypto.randomUUID();
const startTime = performance.now();
this.logger.debug('Executing database query', 'DatabaseService', {
queryId,
sql: sql.substring(0, 100) + '...', // Truncate for security
paramCount: params.length
});
try {
const result = await this.connection.query(sql, params);
const duration = performance.now() - startTime;
this.logger.log('Query executed successfully', 'DatabaseService', {
queryId,
rowCount: result.length,
duration: `${duration.toFixed(2)}ms`,
performanceLevel: duration > 1000 ? 'slow' : 'fast'
});
return result;
} catch (error) {
const duration = performance.now() - startTime;
this.logger.error('Query execution failed', error, 'DatabaseService', {
queryId,
duration: `${duration.toFixed(2)}ms`,
errorCode: error.code,
sqlState: error.sqlState
});
throw error;
}
}
}⚙️ Configuration Reference
Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| seqUrl | string | process.env.SEQ_URL | Seq server URL (required) |
| apiKey | string | process.env.SEQ_API_KEY | API key for authentication |
| application | string | process.env.APP_NAME \|\| 'nestjs-app' | Application name in logs |
| workspace | string | process.env.SEQ_WORKSPACE | Workspace/environment identifier |
| minimumLevel | LogLevel | 'information' | Minimum log level to send |
Log Levels (in order of severity)
| Level | Numeric Value | When to Use |
|-------|---------------|-------------|
| verbose | 0 | Extremely detailed debugging info |
| debug | 1 | Development debugging information |
| information | 2 | General application flow |
| warning | 3 | Potentially harmful situations |
| error | 4 | Error events that don't stop execution |
| fatal | 5 | Critical errors that may cause termination |
Environment Variables
# Required
SEQ_URL=http://localhost:5341 # Seq server endpoint
# Optional
SEQ_API_KEY=abcdef123456 # Authentication (recommended for production)
APP_NAME=my-nestjs-application # Application name in logs
SEQ_WORKSPACE=development # Environment identifier
LOG_LEVEL=information # Minimum log level🎯 Advanced Features
Distributed Tracing Support
@Injectable()
export class OrderService {
constructor(private readonly logger: SeqLoggerService) {}
async processOrder(orderId: string, traceId: string) {
const spanId = crypto.randomUUID();
// Log with tracing information
this.logger.traceEvent('Order processing started', 'OrderService', {
traceId,
spanId,
spanKind: 'Internal',
startTime: new Date().toISOString(),
orderId,
operation: 'processOrder'
});
try {
// Call external payment service
await this.callPaymentService(orderId, traceId, spanId);
this.logger.traceEvent('Order processed successfully', 'OrderService', {
traceId,
spanId,
orderId,
status: 'completed'
});
} catch (error) {
this.logger.error('Order processing failed', error, 'OrderService', {
traceId,
spanId,
orderId
});
throw error;
}
}
private async callPaymentService(orderId: string, parentTraceId: string, parentSpanId: string) {
const spanId = crypto.randomUUID();
this.logger.traceEvent('Calling payment service', 'OrderService', {
traceId: parentTraceId,
spanId,
parentId: parentSpanId,
spanKind: 'Client',
orderId,
service: 'payment-api'
});
// Make HTTP call...
}
}Custom Object Serialization
The logger safely handles complex objects:
this.logger.log('Complex operation completed', 'MyService', {
user: {
id: 123,
email: '[email protected]',
preferences: {
theme: 'dark',
notifications: true,
nested: {
deep: {
value: 'safely serialized'
}
}
}
},
request: req, // HTTP request objects are safely handled
response: res, // HTTP response objects are safely handled
metadata: {
timestamp: new Date(),
version: '1.0.0',
environment: process.env.NODE_ENV
}
});🛠️ Development vs Production
Development Configuration
// app.module.ts (development)
SeqLoggerModule.forRoot({
seqUrl: 'http://localhost:5341',
application: 'my-app-dev',
workspace: 'development',
minimumLevel: 'debug' // Show debug logs in development
})# .env.development
SEQ_URL=http://localhost:5341
APP_NAME=my-app-dev
SEQ_WORKSPACE=development
LOG_LEVEL=debugProduction Configuration
// app.module.ts (production)
SeqLoggerModule.forRoot({
seqUrl: process.env.SEQ_URL, // External Seq instance
apiKey: process.env.SEQ_API_KEY, // Required for production
application: process.env.APP_NAME,
workspace: 'production',
minimumLevel: 'information' // Filter out debug logs
})# .env.production
SEQ_URL=https://your-seq-server.com
SEQ_API_KEY=your-production-api-key
APP_NAME=my-app
SEQ_WORKSPACE=production
LOG_LEVEL=information🔍 Troubleshooting
Common Issues & Solutions
❌ "Cannot connect to Seq server"
Seq logger could not connect to http://localhost:5341. Logs will not be sent to Seq.Solutions:
- Verify Seq is running:
curl http://localhost:5341/api - Check Docker container:
docker ps | grep seq - Verify
SEQ_URLenvironment variable - Check firewall/network connectivity
❌ Module dependency errors
Cannot resolve dependency SeqLoggerServiceSolutions:
- Install peer dependencies:
npm install @nestjs/common @nestjs/core reflect-metadata rxjs - Ensure module is imported in
AppModule - Check TypeScript decorator configuration
❌ API authentication failed
Error sending logs to Seq: 401 UnauthorizedSolutions:
- Verify API key in Seq admin panel
- Check API key permissions
- Ensure correct
SEQ_API_KEYenvironment variable
❌ Logs not appearing in Seq
Check:
- Log level configuration (logs below
minimumLevelare filtered) - Seq ingestion endpoint accessibility
- Console for connection errors
- Seq server health and storage space
Debug Mode
Enable verbose logging to troubleshoot:
SeqLoggerModule.forRoot({
seqUrl: 'http://localhost:5341',
minimumLevel: 'verbose', // Shows all logs
// ... other options
})Health Check
Add a health check endpoint:
@Controller('health')
export class HealthController {
constructor(private readonly logger: SeqLoggerService) {}
@Get('seq')
async checkSeqConnection() {
this.logger.log('Health check performed', 'HealthController', {
endpoint: '/health/seq',
timestamp: new Date().toISOString()
});
return { status: 'ok', seqLogging: 'enabled' };
}
}📚 API Reference
SeqLoggerService Methods
// Basic logging
log(message: any, context?: string, data?: any): void
error(message: any, trace?: string | Error, context?: string, data?: any): void
warn(message: any, context?: string, data?: any): void
debug(message: any, context?: string, data?: any): void
verbose(message: any, context?: string, data?: any): void
fatal(message: any, context?: string, data?: any): void
// Distributed tracing
traceEvent(message: any, context?: string, traceData?: TraceData): void
// Utility methods
objectToJson(obj: any, maxDepth?: number): anyTraceData Interface
interface TraceData {
traceId?: string;
spanId?: string;
parentId?: string;
spanKind?: 'Client' | 'Server' | 'Internal' | 'Producer' | 'Consumer';
startTime?: string;
[key: string]: any; // Additional custom properties
}Usage Examples by Method
// Information level
logger.log('User logged in', 'AuthService', {
userId: 123,
email: '[email protected]'
});
// Warning level
logger.warn('API rate limit approaching', 'ApiService', {
currentRequests: 95,
limit: 100
});
// Error level (with stack trace)
logger.error('Database connection failed', error, 'DatabaseService', {
connectionString: 'masked',
retryAttempt: 3
});
// Debug level
logger.debug('Cache miss', 'CacheService', {
key: 'user:123',
ttl: 300
});
// Verbose level
logger.verbose('Query executed', 'DatabaseService', {
query: 'SELECT * FROM users',
executionTime: '45ms'
});
// Fatal level
logger.fatal('Application startup failed', 'Bootstrap', {
error: 'Port 3000 already in use'
});
// Distributed tracing
logger.traceEvent('HTTP request processed', 'ApiController', {
traceId: '1234567890abcdef',
spanId: 'abcdef1234567890',
parentId: '0987654321fedcba',
spanKind: 'Server',
startTime: '2024-01-01T12:00:00.000Z',
httpMethod: 'POST',
httpUrl: '/api/users',
httpStatusCode: 201,
userId: 123
});🎨 Complete Working Example
Full Application Setup
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { SeqLoggerModule } from '@hectormartin42/nestjs-seq-logger';
import { UserModule } from './user/user.module';
@Module({
imports: [
ConfigModule.forRoot(),
SeqLoggerModule.forRoot({
seqUrl: process.env.SEQ_URL,
apiKey: process.env.SEQ_API_KEY,
application: process.env.APP_NAME || 'my-app',
workspace: process.env.SEQ_WORKSPACE || 'development',
minimumLevel: 'information'
}),
UserModule,
],
})
export class AppModule {}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SeqLoggerService } from '@hectormartin42/nestjs-seq-logger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const seqLogger = app.get(SeqLoggerService);
app.useLogger(seqLogger);
await app.listen(3000);
seqLogger.log('Application started', 'Bootstrap', {
port: 3000,
environment: process.env.NODE_ENV,
timestamp: new Date().toISOString()
});
}
bootstrap();
// user.service.ts
import { Injectable } from '@nestjs/common';
import { SeqLoggerService } from '@hectormartin42/nestjs-seq-logger';
@Injectable()
export class UserService {
constructor(private readonly logger: SeqLoggerService) {}
async findAll() {
this.logger.log('Fetching all users', 'UserService');
try {
// Simulate database call
const users = await this.mockDatabaseCall();
this.logger.log('Users fetched successfully', 'UserService', {
count: users.length
});
return users;
} catch (error) {
this.logger.error('Failed to fetch users', error, 'UserService');
throw error;
}
}
private async mockDatabaseCall() {
// Simulate async operation
return new Promise(resolve =>
setTimeout(() => resolve([{ id: 1, name: 'John' }]), 100)
);
}
}🤝 Contributing
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
📄 License
MIT License - see the LICENSE file for details.
🔗 Links
Made with ❤️ for the NestJS community
