From e539c9da483c30aaab190274e31bebfb79409ee7 Mon Sep 17 00:00:00 2001 From: xtrial01 Date: Fri, 24 Jul 2026 22:25:53 +0100 Subject: [PATCH 1/3] feat(logging): add logging standards documentation and improve test coverage --- docs/LOGGING_STANDARDS.md | 388 +++++++++++++++++++++++++++++ src/logging/logger.service.spec.ts | 247 ++++++++++++++++-- 2 files changed, 617 insertions(+), 18 deletions(-) create mode 100644 docs/LOGGING_STANDARDS.md diff --git a/docs/LOGGING_STANDARDS.md b/docs/LOGGING_STANDARDS.md new file mode 100644 index 00000000..33d7a633 --- /dev/null +++ b/docs/LOGGING_STANDARDS.md @@ -0,0 +1,388 @@ +# Logging Standards Documentation + +## Overview + +This document describes the logging standards and configuration for the TeachLink Backend application. The logging system provides structured, consistent logging across all modules with proper log levels, correlation IDs, and sensitive data protection. + +## Architecture + +### Components + +1. **AppLoggerService** (`src/logging/logger.service.ts`) + - Winston-based logger service + - Implements NestJS LoggerService interface + - Provides structured JSON logging + - Supports file and console transports + +2. **Sensitive Data Masker** (`src/logging/sensitive-data.masker.ts`) + - Automatically masks sensitive fields in log output + - Recursively masks nested objects and arrays + - Case-insensitive key matching + +3. **Correlation ID Utilities** (`src/common/utils/correlation.utils.ts`) + - Generates and propagates correlation IDs across requests + - AsyncLocalStorage-based context propagation + - Supports both canonical and legacy header names + +4. **HTTP Logging Interceptor** (`src/logging/http-logging.interceptor.ts`) + - Logs all HTTP requests and responses + - Includes correlation IDs, duration, and status codes + - Masks sensitive data in request bodies + +5. **Structured Logging** (`src/logging/structured-logging.ts`) + - Console-based structured logging fallback + - Overrides console methods for consistent output + - Handles uncaught exceptions and rejections + +## Configuration + +### Environment Variables + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `LOG_LEVEL` | Minimum log level to output | `info` | No | +| `LOG_DIR` | Directory for log files | `logs` | No | +| `LOG_TO_FILE` | Enable file logging | `false` (true in production) | No | +| `SERVICE_NAME` | Service name for log identification | `teachlink-backend` | No | +| `NODE_ENV` | Environment (production/development) | - | No | + +### Log Levels + +The following log levels are supported, ordered from most to least severe: + +1. **error** - Error events that might still allow the application to continue running +2. **warn** - Potentially harmful situations +3. **info** - Informational messages that highlight the progress of the application +4. **debug** - Fine-grained informational events that are most useful to debug an application +5. **verbose** - Detailed information typically only of interest when diagnosing problems + +### Log Format + +All logs are output in structured JSON format: + +```json +{ + "timestamp": "2024-01-15T10:30:00.000Z", + "level": "info", + "service": "teachlink-backend", + "pid": 12345, + "correlationId": "cid-abc123-xyz789", + "message": "User logged in successfully", + "context": "AuthService", + "meta": { + "userId": "user_123", + "email": "user@example.com" + } +} +``` + +### File Rotation + +When file logging is enabled, logs are rotated daily: + +- **Application logs**: `logs/app-YYYY-MM-DD.log` + - Max size: 20MB per file + - Retention: 14 days + +- **Error logs**: `logs/error-YYYY-MM-DD.log` + - Max size: 20MB per file + - Retention: 30 days + - Only contains error-level logs + +## Usage + +### Injecting the Logger + +```typescript +import { Injectable, Logger } from '@nestjs/common'; +import { AppLoggerService } from '../logging/logger.service'; + +@Injectable() +export class UserService { + constructor(private readonly logger: AppLoggerService) {} + + createUser(userData: CreateUserDto) { + this.logger.log('Creating new user', 'UserService'); + // ... business logic + this.logger.debug('User created successfully', 'UserService'); + } +} +``` + +### Log Level Methods + +```typescript +// Info level - general informational messages +this.logger.log('User logged in', 'AuthService'); + +// Debug level - detailed debugging information +this.logger.debug('Processing payment with ID: payment_123', 'PaymentService'); + +// Warn level - potentially harmful situations +this.logger.warn('Rate limit approaching for user: user_123', 'RateLimitService'); + +// Error level - error events +this.logger.error('Failed to process payment', error.stack, 'PaymentService'); + +// Verbose level - very detailed information +this.logger.verbose('Cache hit for key: user_profile_123', 'CacheService'); +``` + +### Logging with Metadata + +```typescript +this.logger.log('User action completed', 'UserService'); +// Metadata is automatically included via correlation ID and context + +// For custom metadata, use the winston logger directly +this.logger.logRequest({ + method: 'POST', + url: '/api/users', + userId: 'user_123', + action: 'create_user' +}); + +this.logger.logResponse({ + statusCode: 201, + durationMs: 45, + userId: 'user_123' +}); +``` + +### Correlation IDs + +Correlation IDs are automatically propagated through the request context: + +```typescript +import { getCorrelationId, runWithCorrelationId } from '../common/utils/correlation.utils'; + +// Get current correlation ID +const correlationId = getCorrelationId(); + +// Run code with a specific correlation ID (useful for background jobs) +runWithCorrelationId(() => { + this.logger.log('Processing background job', 'JobService'); +}, 'cid-job-123'); +``` + +## Sensitive Data Protection + +### Automatically Masked Fields + +The following field names are automatically masked in log output: + +- `password`, `passwd`, `pass` +- `secret` +- `token`, `accesstoken`, `access_token`, `refreshtoken`, `refresh_token` +- `apikey`, `api_key`, `apitoken`, `api_token` +- `authorization`, `auth` +- `key`, `privatekey`, `private_key` +- `clientsecret`, `client_secret` +- `credit_card`, `creditcard`, `cardnumber`, `card_number` +- `cvv`, `cvc` +- `pin` +- `ssn`, `socialsecuritynumber`, `social_security_number` + +### Masking Behavior + +- Masked values are replaced with `***MASKED***` +- Masking is case-insensitive +- Nested objects and arrays are recursively masked +- Primitive values (strings, numbers, booleans) are passed through unchanged + +### Example + +```typescript +const userData = { + email: 'user@example.com', + password: 'secret123', + profile: { + name: 'John Doe', + ssn: '123-45-6789' + } +}; + +// In logs, this becomes: +{ + email: 'user@example.com', + password: '***MASKED***', + profile: { + name: 'John Doe', + ssn: '***MASKED***' + } +} +``` + +## HTTP Request Logging + +All HTTP requests are automatically logged with the following information: + +### Request Log +```json +{ + "event": "http_request", + "correlationId": "cid-abc123", + "method": "POST", + "url": "/api/users", + "headers": { + "content-type": "application/json", + "authorization": "***MASKED***" + }, + "remoteAddress": "192.168.1.1", + "body": { + "email": "user@example.com", + "password": "***MASKED***" + } +} +``` + +### Response Log +```json +{ + "event": "http_response", + "correlationId": "cid-abc123", + "method": "POST", + "url": "/api/users", + "statusCode": 201, + "durationMs": 45 +} +``` + +### Error Log +```json +{ + "event": "http_error", + "correlationId": "cid-abc123", + "method": "POST", + "url": "/api/users", + "statusCode": 500, + "durationMs": 120, + "error": { + "message": "Database connection failed", + "name": "DatabaseError" + } +} +``` + +## Best Practices + +### DO + +- Use appropriate log levels for each situation +- Include context information (service/module name) +- Use correlation IDs for tracing requests across services +- Log business-critical events (user actions, state changes) +- Log errors with stack traces for debugging +- Keep log messages concise and meaningful + +### DON'T + +- Log sensitive data (passwords, tokens, PII) +- Use console.log directly (use the logger service) +- Log at debug level in production (use info or warn) +- Log large objects (truncate or summarize) +- Log the same event multiple times +- Include stack traces for non-error logs + +### Log Level Guidelines + +| Situation | Log Level | Example | +|-----------|-----------|---------| +| Application startup/shutdown | info | "Application started on port 3000" | +| User actions | info | "User created account", "User logged in" | +| Business logic milestones | info | "Payment processed successfully" | +| Performance issues | warn | "Slow query detected: 2000ms" | +- Deprecated API usage | warn | "Using deprecated endpoint /api/v1/users" | +| Configuration issues | warn | "Missing env var, using default" | +| Application errors | error | "Failed to connect to database" | +- Validation failures | error | "Invalid user input" | +| Detailed debugging | debug | "Cache miss for key: user_123" | +| Function entry/exit | debug | "Entering method: processPayment" | +| Very detailed tracing | verbose | "Processing item 1 of 100" | + +## Testing + +### Unit Tests + +Run logging service tests: + +```bash +npm test -- src/logging/logger.service.spec.ts +npm test -- src/logging/sensitive-data.masker.spec.ts +npm test -- src/logging/http-logging.interceptor.spec.ts +``` + +### Integration Tests + +Verify logging integration: + +```bash +npm run test:e2e +``` + +## Troubleshooting + +### Logs Not Appearing + +1. Check `LOG_LEVEL` environment variable +2. Verify `LOG_TO_FILE` is set correctly for file logging +3. Ensure the logging module is imported in `AppModule` + +### Correlation IDs Missing + +1. Verify `CorrelationIdMiddleware` is registered in `main.ts` +2. Check that `requestIdMiddleware` is applied +3. Ensure correlation headers are passed from upstream services + +### Sensitive Data Leaking + +1. Check if the field name is in the `SENSITIVE_KEYS` set +2. Verify the masker is being called on all log output +3. Add custom field names to the masker if needed + +### Performance Issues + +1. Reduce log level from `debug` to `info` in production +2. Disable file logging if not needed +3. Check log file rotation settings +4. Avoid logging large objects + +## Migration Guide + +### Migrating from Console.log + +**Before:** +```typescript +console.log('User created:', userData); +console.error('Error:', error); +``` + +**After:** +```typescript +this.logger.log('User created', 'UserService'); +this.logger.error('Failed to create user', error.stack, 'UserService'); +``` + +### Migrating from NestJS Logger + +**Before:** +```typescript +import { Logger } from '@nestjs/common'; + +private logger = new Logger(UserService.name); +this.logger.log('User created'); +``` + +**After:** +```typescript +import { AppLoggerService } from '../logging/logger.service'; + +constructor(private readonly logger: AppLoggerService) {} +this.logger.log('User created', 'UserService'); +``` + +## Additional Resources + +- [Winston Documentation](https://github.com/winstonjs/winston) +- [NestJS Logging](https://docs.nestjs.com/techniques/logger) +- [Correlation ID Pattern](https://microservices.io/patterns/observability/correlation-id.html) diff --git a/src/logging/logger.service.spec.ts b/src/logging/logger.service.spec.ts index 5b75db54..baaa0eff 100644 --- a/src/logging/logger.service.spec.ts +++ b/src/logging/logger.service.spec.ts @@ -13,6 +13,12 @@ describe('AppLoggerService', () => { service = module.get(AppLoggerService); }); + afterEach(() => { + delete process.env.LOG_TO_FILE; + delete process.env.LOG_LEVEL; + delete process.env.LOG_DIR; + }); + it('should be defined', () => { expect(service).toBeDefined(); }); @@ -30,36 +36,241 @@ describe('AppLoggerService', () => { expect(typeof service.logResponse).toBe('function'); }); - it('calls log without throwing', () => { - expect(() => service.log('hello', 'TestContext')).not.toThrow(); + describe('Log Level Methods', () => { + it('calls log without throwing', () => { + expect(() => service.log('hello', 'TestContext')).not.toThrow(); + }); + + it('calls error without throwing', () => { + expect(() => service.error('err msg', 'stack trace', 'TestContext')).not.toThrow(); + }); + + it('calls warn without throwing', () => { + expect(() => service.warn('warning', 'TestContext')).not.toThrow(); + }); + + it('calls debug without throwing', () => { + expect(() => service.debug('debug info', 'TestContext')).not.toThrow(); + }); + + it('calls verbose without throwing', () => { + expect(() => service.verbose('verbose info', 'TestContext')).not.toThrow(); + }); }); - it('calls error without throwing', () => { - expect(() => service.error('err msg', 'stack trace', 'TestContext')).not.toThrow(); + describe('HTTP Logging Helpers', () => { + it('calls logRequest without throwing', () => { + expect(() => service.logRequest({ method: 'GET', url: '/test' })).not.toThrow(); + }); + + it('calls logResponse without throwing', () => { + expect(() => service.logResponse({ statusCode: 200, durationMs: 42 })).not.toThrow(); + }); + + it('logRequest includes method and url', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { info: jest.Mock } }).winston, + 'info', + ); + + service.logRequest({ method: 'POST', url: '/api/users', userId: '123' }); + + expect(winstonSpy).toHaveBeenCalledWith('http_request', expect.objectContaining({ + method: 'POST', + url: '/api/users', + userId: '123' + })); + }); + + it('logResponse includes statusCode and duration', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { info: jest.Mock } }).winston, + 'info', + ); + + service.logResponse({ statusCode: 201, durationMs: 123, requestId: 'abc' }); + + expect(winstonSpy).toHaveBeenCalledWith('http_response', expect.objectContaining({ + statusCode: 201, + durationMs: 123, + requestId: 'abc' + })); + }); }); - it('calls warn without throwing', () => { - expect(() => service.warn('warning', 'TestContext')).not.toThrow(); + describe('Correlation ID Support', () => { + it('includes correlation ID in log output when set', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { info: jest.Mock } }).winston, + 'info', + ); + + runWithCorrelationId(() => { + service.log('test message'); + }, 'cid-test-123'); + + expect(winstonSpy).toHaveBeenCalledWith('test message', expect.any(Object)); + }); + + it('works without correlation ID when not set', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { info: jest.Mock } }).winston, + 'info', + ); + + service.log('test message without correlation'); + + expect(winstonSpy).toHaveBeenCalledWith('test message without correlation', expect.any(Object)); + }); }); - it('calls logRequest without throwing', () => { - expect(() => service.logRequest({ method: 'GET', url: '/test' })).not.toThrow(); + describe('Configuration', () => { + it('uses default log level when not specified', () => { + delete process.env.LOG_LEVEL; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + }); + + it('respects custom log level from environment', () => { + process.env.LOG_LEVEL = 'debug'; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + }); + + it('uses default log directory when not specified', () => { + delete process.env.LOG_DIR; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + }); + + it('respects custom log directory from environment', () => { + process.env.LOG_DIR = '/custom/logs'; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + }); + + it('disables file logging by default in non-production', () => { + process.env.NODE_ENV = 'development'; + delete process.env.LOG_TO_FILE; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + }); + + it('enables file logging when LOG_TO_FILE is true', () => { + process.env.LOG_TO_FILE = 'true'; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + }); + + it('enables file logging in production', () => { + process.env.NODE_ENV = 'production'; + delete process.env.LOG_TO_FILE; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + }); }); - it('calls logResponse without throwing', () => { - expect(() => service.logResponse({ statusCode: 200, durationMs: 42 })).not.toThrow(); + describe('Error Handling', () => { + it('handles error with stack trace', () => { + const error = new Error('Test error'); + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { error: jest.Mock } }).winston, + 'error', + ); + + service.error('Error occurred', error.stack, 'TestContext'); + + expect(winstonSpy).toHaveBeenCalledWith('Error occurred', expect.objectContaining({ + context: 'TestContext', + stack: error.stack + })); + }); + + it('handles error without stack trace', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { error: jest.Mock } }).winston, + 'error', + ); + + service.error('Error occurred', undefined, 'TestContext'); + + expect(winstonSpy).toHaveBeenCalledWith('Error occurred', expect.objectContaining({ + context: 'TestContext' + })); + }); + + it('handles error without context', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { error: jest.Mock } }).winston, + 'error', + ); + + service.error('Error occurred'); + + expect(winstonSpy).toHaveBeenCalledWith('Error occurred', expect.any(Object)); + }); }); - it('includes correlation ID in log output when set', () => { - const winstonSpy = jest.spyOn( - (service as unknown as { winston: { info: jest.Mock } }).winston, - 'info', - ); + describe('Structured Output', () => { + it('includes service name in log output', () => { + process.env.SERVICE_NAME = 'test-service'; + const module: TestingModule = await Test.createTestingModule({ + providers: [AppLoggerService], + }).compile(); + const configuredService = module.get(AppLoggerService); + + expect(configuredService).toBeDefined(); + delete process.env.SERVICE_NAME; + }); + + it('includes timestamp in log output', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { info: jest.Mock } }).winston, + 'info', + ); + + service.log('test message'); + + expect(winstonSpy).toHaveBeenCalled(); + }); + + it('includes process ID in log output', () => { + const winstonSpy = jest.spyOn( + (service as unknown as { winston: { info: jest.Mock } }).winston, + 'info', + ); - runWithCorrelationId(() => { service.log('test message'); - }, 'cid-test-123'); - expect(winstonSpy).toHaveBeenCalledWith('test message', expect.any(Object)); + expect(winstonSpy).toHaveBeenCalled(); + }); }); }); From 07d6a13a8e9f4d88e96c03397ccb542a92a2f4cf Mon Sep 17 00:00:00 2001 From: xtrial01 Date: Sun, 26 Jul 2026 21:58:43 +0100 Subject: [PATCH 2/3] fix: lint/prettier issues in auth service and logger tests --- src/auth/auth.service.ts | 2 +- src/logging/logger.service.spec.ts | 78 +++++++++++++++++------------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 8729b25b..be60587e 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; import * as bcrypt from 'bcrypt'; import { User, UserStatus } from '../users/entities/user.entity'; import { TokenBlacklistService } from './services/token-blacklist.service'; -import { isRS256Configured, loadPEMKey } from './config/jwt-config.factory'; +import { loadPEMKey } from './config/jwt-config.factory'; @Injectable() export class AuthService { diff --git a/src/logging/logger.service.spec.ts b/src/logging/logger.service.spec.ts index baaa0eff..e236282f 100644 --- a/src/logging/logger.service.spec.ts +++ b/src/logging/logger.service.spec.ts @@ -75,11 +75,14 @@ describe('AppLoggerService', () => { service.logRequest({ method: 'POST', url: '/api/users', userId: '123' }); - expect(winstonSpy).toHaveBeenCalledWith('http_request', expect.objectContaining({ - method: 'POST', - url: '/api/users', - userId: '123' - })); + expect(winstonSpy).toHaveBeenCalledWith( + 'http_request', + expect.objectContaining({ + method: 'POST', + url: '/api/users', + userId: '123', + }), + ); }); it('logResponse includes statusCode and duration', () => { @@ -90,11 +93,14 @@ describe('AppLoggerService', () => { service.logResponse({ statusCode: 201, durationMs: 123, requestId: 'abc' }); - expect(winstonSpy).toHaveBeenCalledWith('http_response', expect.objectContaining({ - statusCode: 201, - durationMs: 123, - requestId: 'abc' - })); + expect(winstonSpy).toHaveBeenCalledWith( + 'http_response', + expect.objectContaining({ + statusCode: 201, + durationMs: 123, + requestId: 'abc', + }), + ); }); }); @@ -125,75 +131,75 @@ describe('AppLoggerService', () => { }); describe('Configuration', () => { - it('uses default log level when not specified', () => { + it('uses default log level when not specified', async () => { delete process.env.LOG_LEVEL; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); }); - it('respects custom log level from environment', () => { + it('respects custom log level from environment', async () => { process.env.LOG_LEVEL = 'debug'; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); }); - it('uses default log directory when not specified', () => { + it('uses default log directory when not specified', async () => { delete process.env.LOG_DIR; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); }); - it('respects custom log directory from environment', () => { + it('respects custom log directory from environment', async () => { process.env.LOG_DIR = '/custom/logs'; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); }); - it('disables file logging by default in non-production', () => { + it('disables file logging by default in non-production', async () => { process.env.NODE_ENV = 'development'; delete process.env.LOG_TO_FILE; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); }); - it('enables file logging when LOG_TO_FILE is true', () => { + it('enables file logging when LOG_TO_FILE is true', async () => { process.env.LOG_TO_FILE = 'true'; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); }); - it('enables file logging in production', () => { + it('enables file logging in production', async () => { process.env.NODE_ENV = 'production'; delete process.env.LOG_TO_FILE; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); }); }); @@ -208,10 +214,13 @@ describe('AppLoggerService', () => { service.error('Error occurred', error.stack, 'TestContext'); - expect(winstonSpy).toHaveBeenCalledWith('Error occurred', expect.objectContaining({ - context: 'TestContext', - stack: error.stack - })); + expect(winstonSpy).toHaveBeenCalledWith( + 'Error occurred', + expect.objectContaining({ + context: 'TestContext', + stack: error.stack, + }), + ); }); it('handles error without stack trace', () => { @@ -222,9 +231,12 @@ describe('AppLoggerService', () => { service.error('Error occurred', undefined, 'TestContext'); - expect(winstonSpy).toHaveBeenCalledWith('Error occurred', expect.objectContaining({ - context: 'TestContext' - })); + expect(winstonSpy).toHaveBeenCalledWith( + 'Error occurred', + expect.objectContaining({ + context: 'TestContext', + }), + ); }); it('handles error without context', () => { @@ -240,13 +252,13 @@ describe('AppLoggerService', () => { }); describe('Structured Output', () => { - it('includes service name in log output', () => { + it('includes service name in log output', async () => { process.env.SERVICE_NAME = 'test-service'; const module: TestingModule = await Test.createTestingModule({ providers: [AppLoggerService], }).compile(); const configuredService = module.get(AppLoggerService); - + expect(configuredService).toBeDefined(); delete process.env.SERVICE_NAME; }); From 4b129e5265b8d2dc857d19b4245af9d66861cd69 Mon Sep 17 00:00:00 2001 From: xtrial01 Date: Mon, 27 Jul 2026 21:40:10 +0100 Subject: [PATCH 3/3] style: fix prettier formatting in logger.service.spec.ts --- src/logging/logger.service.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/logging/logger.service.spec.ts b/src/logging/logger.service.spec.ts index e236282f..cbc6c877 100644 --- a/src/logging/logger.service.spec.ts +++ b/src/logging/logger.service.spec.ts @@ -126,7 +126,10 @@ describe('AppLoggerService', () => { service.log('test message without correlation'); - expect(winstonSpy).toHaveBeenCalledWith('test message without correlation', expect.any(Object)); + expect(winstonSpy).toHaveBeenCalledWith( + 'test message without correlation', + expect.any(Object), + ); }); });