npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kyrasource/logger

v1.0.1

Published

Production-ready Winston logger module for NestJS with colorized dev output, structured JSON/ECS in production, and file logging

Downloads

22

Readme

@kyrasource/logger

Production-ready structured logging for NestJS applications using Winston. Ships with colorized development output, ECS-compliant JSON in production, automatic file logging, and optional Logstash integration.

✨ Features

  • Colorized Development Logs - Human-readable console output with colors, badges, and context
  • Structured JSON/ECS Production - Elasticsearch-compatible logs for Logstash and Kibana
  • File Logging - Automatic rotation with separate error and combined logs
  • Error Serialization - Full stack traces and error context
  • Request Tracking - Request IDs and user context across logs
  • Metadata Support - Flexible key-value metadata attachment
  • Logstash Integration - Optional HTTP transport to centralized logging
  • Sensitive Data Masking - Automatic masking of passwords, tokens, API keys
  • TypeScript Support - Full type safety with interfaces
  • Zero External Dependencies - Uses only Winston (peer dependency)

Installation

npm install @kyrasource/logger

Quick Start

1. Import in Your App Module

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { LoggerModule } from '@kyrasource/logger';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
    LoggerModule.register(), // Register with env-based config
  ],
})
export class AppModule {}

2. Configure via .env

# Logger Configuration
LOGGER_ENABLED=true
NODE_ENV=development
LOG_LEVEL=info
LOG_DIR=./logs

# File Logging
LOG_FILE_ENABLED=true
LOG_FILE_MAX_SIZE=20m
LOG_FILE_MAX_FILES=14
LOG_ERROR_FILE=error.log
LOG_COMBINED_FILE=combined.log

# Logstash (optional)
LOGSTASH_ENABLED=false
LOGSTASH_HOST=localhost
LOGSTASH_PORT=5000

3. Inject and Use

import { Injectable } from '@nestjs/common';
import { LoggerService } from '@kyrasource/logger';

@Injectable()
export class UserService {
  constructor(private readonly logger: LoggerService) {
    this.logger.setContext('UserService');
  }

  async createUser(dto: CreateUserDto) {
    try {
      this.logger.info(`Creating user: ${dto.email}`);
      
      const user = await this.userRepository.create(dto);
      
      this.logger.info(`User created`, {
        metadata: { userId: user.id, email: user.email },
      });
      
      return user;
    } catch (error) {
      this.logger.error('Failed to create user', error, {
        metadata: { email: dto.email },
      });
      throw error;
    }
  }
}

Output Examples

Development (Colorized)

14:32:45.123 [INFO] [UserService] ✓ User created (req: abc123)
14:32:46.456 [ERROR] [AuthService] ✖ Invalid credentials - ▶ Error: Wrong password
  Error: Wrong password
    at AuthService.validatePassword (auth.service.ts:45:11)
    at AuthService.login (auth.service.ts:20:5)
  {
    "email": "[email protected]",
    "attempt": 2
  }

Production (ECS JSON)

{
  "@timestamp": "2026-01-20T14:32:45.123Z",
  "log.level": "ERROR",
  "log.logger": "AuthService",
  "message": "Invalid credentials",
  "service.name": "api",
  "trace.id": "abc123",
  "user.id": "user-456",
  "error.message": "Wrong password",
  "error.stack_trace": "Error: Wrong password\n    at AuthService.validatePassword...",
  "error.type": "Error",
  "labels": {
    "email": "[email protected]",
    "attempt": 2
  }
}

API Reference

LoggerService Methods

Basic Logging

// Error (red in dev, ECS in prod)
logger.error('Operation failed', error, options?);

// Warning (yellow in dev)
logger.warn('Retry attempt 3 of 5', options?);

// Info (cyan in dev)
logger.info('User logged in', options?);

// Debug (blue in dev)
logger.debug('Query executed', options?);

// Verbose (magenta in dev)
logger.verbose('Cache invalidated', options?);

// Silly (gray in dev, most verbose)
logger.silly('Detailed trace', options?);

Context Management

logger.setContext('UserController');
logger.getContext(); // Returns: "UserController"

Specialized Logging

// HTTP request/response
logger.logHttpRequest(
  'GET',
  '/api/users/123',
  200,
  145, // duration in ms
  { requestId: 'req-789' }
);

// Database operations
logger.logDatabaseOperation(
  'findById',
  'users',
  23, // duration in ms
  true, // success
  null, // error
  { requestId: 'req-789' }
);

// External API calls
logger.logExternalCall(
  'stripe-api',
  '/v1/customers',
  567, // duration in ms
  200, // status code
  null, // error
  { userId: 'user-123' }
);

LoggerOptions Interface

interface LoggerOptions {
  level?: LogLevel;           // Override log level
  context?: string;           // Override context
  requestId?: string;         // Distributed trace ID
  userId?: string;            // User context
  metadata?: LogMetadata;     // Additional data
}

Example: Full-Featured Logging

import { LoggerService } from '@kyrasource/logger';

@Controller('users')
export class UsersController {
  constructor(private readonly logger: LoggerService) {
    this.logger.setContext('UsersController');
  }

  @Post()
  async create(@Body() dto: CreateUserDto, @Headers('x-request-id') requestId: string) {
    const startTime = Date.now();

    try {
      this.logger.info('Creating user', {
        requestId,
        metadata: { email: dto.email },
      });

      const user = await this.userService.create(dto);

      const duration = Date.now() - startTime;
      this.logger.info('User created successfully', {
        requestId,
        metadata: {
          userId: user.id,
          email: user.email,
          duration,
        },
      });

      return user;
    } catch (error) {
      this.logger.error('Failed to create user', error, {
        requestId,
        metadata: { email: dto.email },
      });
      throw new BadRequestException('Failed to create user');
    }
  }
}

Configuration

Environment Variables

# Enable/disable logging
LOGGER_ENABLED=true

# Log level (error, warn, info, debug, verbose, silly)
LOG_LEVEL=info

# Environment (development, production, test)
NODE_ENV=development

# Date format in logs
LOG_DATE_FORMAT=YYYY-MM-DD HH:mm:ss.SSS

# File logging
LOG_DIR=./logs
LOG_FILE_ENABLED=true
LOG_FILE_MAX_SIZE=20m           # Max file size before rotation
LOG_FILE_MAX_FILES=14           # Max days to keep logs
LOG_ERROR_FILE=error.log        # Error-only log file
LOG_COMBINED_FILE=combined.log  # All levels

# Console output
LOG_CONSOLE_ENABLED=true

# Logstash HTTP transport
LOGSTASH_ENABLED=false
LOGSTASH_HOST=localhost:5044
LOGSTASH_PORT=5000
LOGSTASH_USERNAME=admin
LOGSTASH_PASSWORD=secret123

Programmatic Configuration

import { LoggerModule } from '@kyrasource/logger';

@Module({
  imports: [
    LoggerModule.registerCustom({
      enabled: true,
      environment: 'production',
      defaultLevel: 'info',
      dateFormat: 'YYYY-MM-DD HH:mm:ss.SSS',
      fileMaxSize: '20m',
      fileMaxFiles: 14,
      logDir: './logs',
      errorLogFile: 'error.log',
      combinedLogFile: 'combined.log',
      consoleEnabled: false,
      fileEnabled: true,
      logstashEnabled: true,
      logstashHost: 'logstash.example.com',
      logstashPort: 5044,
    }),
  ],
})
export class AppModule {}

Sensitive Data Protection

The logger automatically masks sensitive information:

const sensitiveData = {
  email: '[email protected]',
  password: 'super-secret',
  apiKey: 'sk-1234567890',
  token: 'jwt-token-xyz',
};

logger.info('Login attempt', {
  metadata: sensitiveData,
});

// Output (password/apiKey/token masked):
// {
//   "email": "[email protected]",
//   "password": "***MASKED***",
//   "apiKey": "***MASKED***",
//   "token": "***MASKED***"
// }

Custom sensitive keys:

// Access the logger's internal method (advanced)
import { maskSensitiveData } from '@kyrasource/logger';

const masked = maskSensitiveData(
  { customSecret: 'value', data: 'public' },
  ['customSecret', 'password', 'token']
);

Integration Patterns

Global Request Logging Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { LoggerService } from '@kyrasource/logger';

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  constructor(private readonly logger: LoggerService) {}

  use(req: Request, res: Response, next: NextFunction) {
    const startTime = Date.now();
    const { method, url, headers } = req;
    const requestId = headers['x-request-id'] as string || Math.random().toString(36).substr(2, 9);

    res.on('finish', () => {
      const duration = Date.now() - startTime;
      this.logger.logHttpRequest(method, url, res.statusCode, duration, {
        requestId,
      });
    });

    next();
  }
}

// Apply in AppModule
@Module({
  imports: [LoggerModule.register()],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware).forRoutes('*');
  }
}

Database Query Logging

import { Injectable } from '@nestjs/common';
import { LoggerService } from '@kyrasource/logger';

@Injectable()
export class DatabaseInterceptor {
  constructor(private readonly logger: LoggerService) {}

  async intercept(query: string, params: any[], options?: any) {
    const startTime = Date.now();

    try {
      const result = await this.executeQuery(query, params);
      const duration = Date.now() - startTime;

      this.logger.logDatabaseOperation(
        'query',
        options?.collection || 'unknown',
        duration,
        true,
        null,
        {
          metadata: {
            query: query.substring(0, 100), // First 100 chars
            paramCount: params.length,
          },
        }
      );

      return result;
    } catch (error) {
      const duration = Date.now() - startTime;
      this.logger.logDatabaseOperation(
        'query',
        options?.collection || 'unknown',
        duration,
        false,
        error
      );
      throw error;
    }
  }
}

Error Handling with Logging

import { Catch, ExceptionFilter, HttpException } from '@nestjs/common';
import { LoggerService } from '@kyrasource/logger';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  constructor(private readonly logger: LoggerService) {
    this.logger.setContext('GlobalExceptionFilter');
  }

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();

    const requestId = request.headers['x-request-id'] as string;
    const status = exception instanceof HttpException ? exception.getStatus() : 500;

    this.logger.error(
      `${request.method} ${request.url} - ${status}`,
      exception as Error,
      {
        requestId,
        metadata: {
          method: request.method,
          path: request.url,
          status,
        },
      }
    );

    response.status(status).json({
      statusCode: status,
      message: exception instanceof HttpException ? exception.message : 'Internal server error',
      requestId,
      timestamp: new Date().toISOString(),
    });
  }
}

File Organization

logs/
├── error.log-2026-01-20         # Error-only, rotated daily
├── error.log-2026-01-19
├── combined.log-2026-01-20      # All levels, rotated daily
├── combined.log-2026-01-19
├── exceptions.log               # Unhandled exceptions
└── rejections.log               # Unhandled promise rejections

Rotation Policy

  • Size: Rotates when file exceeds LOG_FILE_MAX_SIZE (default: 20MB)
  • Time: Rotates daily (suffix format: YYYY-MM-DD)
  • Retention: Keeps last LOG_FILE_MAX_FILES days (default: 14)

Advanced Usage

Custom Formatters

import { createJsonFormatter, createECSFormatter } from '@kyrasource/logger';

// Use JSON formatter for all logs
const customFormatter = createJsonFormatter('my-service');

// Use ECS formatter for Elasticsearch
const ecsFormatter = createECSFormatter('my-service');

Direct Winston Access

const logger = loggerService.getWinstonLogger();

// Access underlying Winston logger if needed
logger.info('Direct message', { custom: 'field' });

Error Utilities

import { serializeError, maskSensitiveData, stripAnsiCodes } from '@kyrasource/logger';

const error = new Error('Something went wrong');
const serialized = serializeError(error);

const masked = maskSensitiveData({ password: 'secret' });

const clean = stripAnsiCodes('\x1b[31mRed text\x1b[0m');

Troubleshooting

No logs appearing in console

  • Check LOG_CONSOLE_ENABLED is true (default)
  • Check LOGGER_ENABLED is true (default)
  • Check LOG_LEVEL includes your log level

Logs not rotating

  • Ensure LOG_DIR directory exists and is writable
  • Check LOG_FILE_ENABLED is true
  • Verify LOG_FILE_MAX_FILES is a valid number (default: 14)

Logstash not receiving logs

  • Verify LOGSTASH_ENABLED=true
  • Check LOGSTASH_HOST is correct and reachable
  • Confirm firewall allows connection to LOGSTASH_PORT
  • Verify Logstash HTTP input is configured to accept JSON

High memory usage

  • Reduce LOG_FILE_MAX_SIZE (default: 20m)
  • Increase LOG_FILE_MAX_FILES retention period
  • Reduce log level (use 'warn' instead of 'silly' in production)
  • Disable console logging in production with LOG_CONSOLE_ENABLED=false

Sensitive data not masked

  • Check field names match known sensitive keywords
  • Custom masking: use maskSensitiveData() explicitly

Performance Considerations

| Feature | Impact | Recommendation | |---------|--------|-----------------| | Colorized output | Low (~2ms/log) | OK in dev, disable in production | | File logging | Medium (~10ms/log) | Use in production, configure rotation | | Logstash HTTP | High (~50-200ms/log) | Buffer logs, use async | | ECS JSON formatting | Low (~5ms/log) | Use in production | | Metadata serialization | Medium (~15ms/log) | Keep metadata small |

Exports

From @kyrasource/logger you can import:

// Module & Service
export { LoggerModule, LoggerService }

// Configuration
export { loggerConfig }

// Types
export type { LoggerConfig, LogLevel, LogMetadata, LoggerOptions, FormattedLogEntry, ECSLog }

// Formatters
export { createColorizedFormatter, createSimpleColorizedFormatter, createFileFormatter }
export { createJsonFormatter, createECSFormatter, createCompactJsonFormatter }

// Utilities
export { serializeError, getErrorStack, isError, stringifyMetadata, mergeMetadata, stripAnsiCodes, maskSensitiveData }

License

MIT - See LICENSE file

Support

For issues, feature requests, or documentation: