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

@kangjuhyup/rvlog

v0.2.0

Published

Framework-agnostic TypeScript logging library with decorators, field masking, and error notifications

Readme

rvlog

Framework-agnostic TypeScript logging library with decorators, field masking, and error notifications.

Features

  • @Logging decorator for entry/exit/error/duration logging
  • withLogging() for standalone functions and browser-friendly code
  • @MaskLog for masking sensitive fields like name, email, phone, and passwords
  • @NoLog to exclude hot paths or health checks
  • NotificationManager with route-based fan-out, lazy notification resources, cooldowns, and thresholds
  • FileTransport for Node.js file logging with rotation

Install

Minimum requirements for the core package:

  • Node.js 18+
  • TypeScript 5.7+
npm install @kangjuhyup/rvlog reflect-metadata
pnpm add @kangjuhyup/rvlog reflect-metadata
yarn add @kangjuhyup/rvlog reflect-metadata

Optional notification channel packages:

npm install @kangjuhyup/rvlog-slack @kangjuhyup/rvlog-discord @kangjuhyup/rvlog-webhook @kangjuhyup/rvlog-sentry @kangjuhyup/rvlog-email
pnpm add @kangjuhyup/rvlog-slack @kangjuhyup/rvlog-discord @kangjuhyup/rvlog-webhook @kangjuhyup/rvlog-sentry @kangjuhyup/rvlog-email
yarn add @kangjuhyup/rvlog-slack @kangjuhyup/rvlog-discord @kangjuhyup/rvlog-webhook @kangjuhyup/rvlog-sentry @kangjuhyup/rvlog-email

Related packages:

npm install @kangjuhyup/rvlog-react @kangjuhyup/rvlog-nest
pnpm add @kangjuhyup/rvlog-react @kangjuhyup/rvlog-nest
yarn add @kangjuhyup/rvlog-react @kangjuhyup/rvlog-nest

Notes:

  • reflect-metadata is required when using metadata-driven features such as @Logging, @MaskLog, and rvlog-nest.
  • Framework adapters may have their own runtime requirements in addition to the core rvlog minimums.

Basic Usage

Import reflect-metadata once at app startup before using decorators.

import 'reflect-metadata';
import { Logger, Logging, LogLevel, MaskLog, NoLog } from '@kangjuhyup/rvlog';

Logger.configure({
  minLevel: LogLevel.INFO,
  pretty: true,
  serialize: {
    maxStringLength: 200,
    maxArrayLength: 20,
    maxObjectKeys: 30,
    maxDepth: 4,
  },
});

class CreateUserDto {
  @MaskLog({ type: 'name' })
  name!: string;

  @MaskLog({ type: 'email' })
  email!: string;
}

@Logging
class UserService {
  declare logger: Logger;

  async create(dto: CreateUserDto) {
    this.logger.info('manual log before create');
    return { id: 1, ...dto };
  }

  @NoLog
  healthCheck() {
    return 'ok';
  }
}

Automatic entry and completion logs are emitted at LogLevel.INFO by default. Pass level when a service should log those lifecycle messages at another level; failures are still logged as ERROR.

@Logging({ level: LogLevel.WARN })
class BillingService {
  charge() {
    return 'ok';
  }
}

Pretty Output

Use pretty: true for the built-in compact console format, or pass an object when you only want to adjust a few parts without writing a full custom formatter.

import { defineLoggerOptions, Logger, LogLevel } from '@kangjuhyup/rvlog';

const loggerOptions = defineLoggerOptions({
  pretty: {
    separator: '->',
    showTimestamp: false,
    levelLabels: {
      [LogLevel.INFO]: 'info',
      [LogLevel.ERROR]: 'error',
    },
    levelColors: {
      [LogLevel.INFO]: 'cyan',
      [LogLevel.WARN]: 'yellow',
      [LogLevel.ERROR]: 'brightRed',
    },
  },
});

Logger.configure(loggerOptions);

For complete control over the output string, pass formatter.

Function-First Usage

Use withLogging() when you want the same automatic logging behavior without class decorators.

import { LogLevel, MaskLog, withLogging } from '@kangjuhyup/rvlog';

class SignupInput {
  @MaskLog({ type: 'email' })
  email!: string;

  @MaskLog({ type: 'full' })
  password!: string;
}

async function signupImpl(input: SignupInput) {
  return { id: crypto.randomUUID() };
}

export const signup = withLogging(signupImpl, {
  context: 'signup',
  level: LogLevel.DEBUG,
});

Node.js File Logging

Use @kangjuhyup/rvlog/node only in Node runtimes.

import 'reflect-metadata';
import { Logger, LogLevel } from '@kangjuhyup/rvlog';
import { FileTransport } from '@kangjuhyup/rvlog/node';

Logger.configure({
  minLevel: LogLevel.INFO,
  pretty: true,
  transports: [
    new FileTransport({
      enabled: true,
      dirPath: 'logs',
      fileName: 'rvlog.log',
      rotate: {
        type: 'size',
        maxSizeBytes: 1024 * 1024,
        maxFiles: 3,
      },
    }),
  ],
});

Payload Truncation

Large payloads can make logs noisy and expensive. rvlog can truncate strings, arrays, objects, and deeply nested values before they are written.

Logger.configure({
  pretty: true,
  serialize: {
    maxStringLength: 200,
    maxArrayLength: 20,
    maxObjectKeys: 30,
    maxDepth: 4,
    truncateSuffix: '...<truncated>',
  },
});

The same serialization policy is shared by:

  • Logger.info(...)
  • @Logging
  • withLogging()

Notifications

NotificationManager owns the core routing policy. Actual delivery channels live in optional packages so apps only install and load the resources they enable.

import { Logger, LogLevel, NotificationManager } from '@kangjuhyup/rvlog';

const notification = new NotificationManager()
  .addLazyResource('slack', async () => {
    const { SlackChannel } = await import('@kangjuhyup/rvlog-slack');
    return new SlackChannel(process.env.SLACK_WEBHOOK_URL ?? '');
  })
  .addLazyResource('email', async () => {
    const { EmailChannel, createNodemailerAdapter } = await import('@kangjuhyup/rvlog-email');
    return new EmailChannel({
      to: '[email protected]',
      transport: createNodemailerAdapter(mailer),
    });
  })
  .addRoute({
    resources: ['slack', 'email'],
    levels: [LogLevel.ERROR],
    when: {
      threshold: { count: 10, windowMs: 60_000 },
      cooldownMs: 60_000,
    },
  });

Logger.configure({ notification });

Channel packages:

  • @kangjuhyup/rvlog-slack
  • @kangjuhyup/rvlog-discord
  • @kangjuhyup/rvlog-webhook
  • @kangjuhyup/rvlog-sentry
  • @kangjuhyup/rvlog-email

The legacy addRule({ channel, levels, cooldownMs }) API remains available for existing code.

See related package docs for integration patterns:

Examples

  • example/README.md
  • example/basic for plain TypeScript + decorators
  • example/express for Express
  • example/nestjs for NestJS
  • example/react for React + hooks
  • example/vanilla for browser TypeScript + withLogging()

Troubleshooting:

Benchmark Report

Performance results are documented here:

Isolated Logger Systems

Logger.configure(...) is still the default global setup for simple apps.

When you need isolated logger state per app, test, tenant, or integration boundary, use createLoggerSystem(...) instead.

import {
  createLoggerSystem,
  LogLevel,
  Logger,
  withLogging,
} from 'rvlog';

const system = createLoggerSystem({
  minLevel: LogLevel.INFO,
});

const logger = system.createLogger('UserService');
logger.info('hello from isolated runtime');

const wrapped = withLogging(
  async (userId: string) => userId,
  {
    context: 'user-flow',
    system,
  },
);

Use the global Logger when:

  • one process-wide configuration is enough
  • you want the simplest setup

Use LoggerSystem when:

  • tests must not mutate global logger state
  • multiple apps or tenants share the same runtime
  • framework adapters should use isolated transports or notification channels