npm package: @hectormartin42/nestjs-seq-logger
Description: NestJS module for logging to Seq using structured logging with CLEF format
Author: Hector Martin
Latest version: 1.0.1

# @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

```bash
# NPM
npm install @hectormartin42/nestjs-seq-logger

# Yarn
yarn add @hectormartin42/nestjs-seq-logger

# PNPM
pnpm add @hectormartin42/nestjs-seq-logger
```

### Step 2: Install Required Peer Dependencies

```bash
# If you don't have these already in your NestJS project
npm install @nestjs/common @nestjs/core reflect-metadata rxjs
```

### Step 3: Set Up Seq Server

#### Option A: Docker (Recommended for Development)
```bash
# 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:5341
```

#### Option B: Docker Compose
Create `docker-compose.yml`:
```yaml
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](https://seq.app) or install Seq on your server following [official docs](https://docs.datalust.co/docs/getting-started).

### Step 4: Configure Environment Variables

Add to your `.env` file:
```env
# 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)

```typescript
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)

```typescript
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

```typescript
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`:
```typescript
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

```typescript
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

```typescript
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

```typescript
@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

```bash
# 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

```typescript
@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:

```typescript
this.logger.log('Complex operation completed', 'MyService', {
  user: {
    id: 123,
    email: 'user@example.com',
    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

```typescript
// app.module.ts (development)
SeqLoggerModule.forRoot({
  seqUrl: 'http://localhost:5341',
  application: 'my-app-dev',
  workspace: 'development',
  minimumLevel: 'debug' // Show debug logs in development
})
```

```bash
# .env.development
SEQ_URL=http://localhost:5341
APP_NAME=my-app-dev
SEQ_WORKSPACE=development
LOG_LEVEL=debug
```

### Production Configuration

```typescript
// 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
})
```

```bash
# .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:**
1. Verify Seq is running: `curl http://localhost:5341/api`
2. Check Docker container: `docker ps | grep seq`
3. Verify `SEQ_URL` environment variable
4. Check firewall/network connectivity

#### ❌ Module dependency errors
```
Cannot resolve dependency SeqLoggerService
```

**Solutions:**
1. Install peer dependencies: `npm install @nestjs/common @nestjs/core reflect-metadata rxjs`
2. Ensure module is imported in `AppModule`
3. Check TypeScript decorator configuration

#### ❌ API authentication failed
```
Error sending logs to Seq: 401 Unauthorized
```

**Solutions:**
1. Verify API key in Seq admin panel
2. Check API key permissions
3. Ensure correct `SEQ_API_KEY` environment variable

#### ❌ Logs not appearing in Seq

**Check:**
1. Log level configuration (logs below `minimumLevel` are filtered)
2. Seq ingestion endpoint accessibility
3. Console for connection errors
4. Seq server health and storage space

### Debug Mode

Enable verbose logging to troubleshoot:

```typescript
SeqLoggerModule.forRoot({
  seqUrl: 'http://localhost:5341',
  minimumLevel: 'verbose', // Shows all logs
  // ... other options
})
```

### Health Check

Add a health check endpoint:

```typescript
@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

```typescript
// 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): any
```

### TraceData Interface

```typescript
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

```typescript
// Information level
logger.log('User logged in', 'AuthService', { 
  userId: 123, 
  email: 'user@example.com' 
});

// 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

```typescript
// 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

1. Fork the repository
2. Create your feature branch: `git checkout -b feature/amazing-feature`
3. Commit your changes: `git commit -m 'Add amazing feature'`
4. Push to the branch: `git push origin feature/amazing-feature`
5. Open a Pull Request

---

## 📄 License

MIT License - see the [LICENSE](LICENSE) file for details.

---

## 🔗 Links

- 📦 [npm Package](https://www.npmjs.com/package/@hectormartin42/nestjs-seq-logger)
- 🐙 [GitHub Repository](https://github.com/hectormartin42/nestjs-seq-logger)
- 📚 [Seq Documentation](https://docs.datalust.co/docs)
- 🐛 [Report Issues](https://github.com/hectormartin42/nestjs-seq-logger/issues)
- 💬 [Discussions](https://github.com/hectormartin42/nestjs-seq-logger/discussions)

---

**Made with ❤️ for the NestJS community**