@vvlad1973/pino-logger-tree
v1.1.0
Published
Hierarchical logger tree for Pino with automatic child registration, configuration cascading, decorators and LoggerAware pattern support
Downloads
16
Maintainers
Readme
@vvlad1973/pino-logger-tree
Библиотека для организации иерархического логирования на базе Pino с каскадной конфигурацией и автоматической регистрацией дочерних логгеров.
Возможности
- Иерархическая структура логгеров - организация логгеров в древовидную структуру с путями вида
app.service.api - Каскадная конфигурация - автоматическое наследование настроек от родительских узлов к дочерним
- Полная совместимость с Pino - прозрачное делегирование всех методов Pino через Proxy
- Автоматическая регистрация - метод
child()автоматически создает узлы в дереве - Динамические обновления - изменение конфигурации с опциональным распространением на потомков
- Автоматическая привязка - встроенная система binding для LoggerAware объектов
- Декораторы для методов и классов -
@LogMethod,@LogSyncMethod,@LogClassс автоматическим логированием и маскированием чувствительных данных - LoggerAwareBase - базовый класс для унифицированной инициализации логгеров
- Трассировка функций - методы
logFunctionStart/Endс автоматическим определением имени функции - TypeScript first - полная типизация с strict mode
- Высокая производительность - минимальный overhead благодаря Proxy и кэшированию
- Покрытие тестами 90%+ - комплексные тесты всех компонентов
Установка
npm install @vvlad1973/pino-logger-tree pinoОпционально для красивого вывода в разработке:
npm install --save-dev pino-prettyБыстрый старт
import { LoggerTree } from '@vvlad1973/pino-logger-tree';
// Создаем дерево логгеров с базовой конфигурацией
const tree = new LoggerTree({
level: 'info',
name: 'my-app'
});
// Создаем логгер для модуля приложения
const appLogger = tree.createLogger('app');
appLogger.info('Application started');
// Создаем логгер с повышенной детализацией
const apiLogger = tree.createLogger('app.api', { level: 'debug' });
apiLogger.debug('API module initialized');
// Дочерний логгер наследует конфигурацию родителя
const dbLogger = apiLogger.child('database');
dbLogger.debug('Connected to database');
// Путь: 'app.api.database', уровень: 'debug' (унаследован)Основное использование
Создание и настройка дерева
import pino from 'pino';
import { LoggerTree } from '@vvlad1973/pino-logger-tree';
// Базовая конфигурация
const tree = new LoggerTree({
level: 'info',
name: 'my-app'
});
// С опциональным destination
const destination = pino.destination('./app.log');
const treeWithDest = new LoggerTree(
{ level: 'info' },
destination
);Каскадная конфигурация
const tree = new LoggerTree({ level: 'warn' });
// Родитель с уровнем debug
tree.createLogger('app', { level: 'debug' });
// Дочерний логгер автоматически наследует debug
const serviceLogger = tree.createLogger('app.service');
serviceLogger.debug('Service initialized'); // ✓ Видно
// Внук переопределяет уровень
const apiLogger = tree.createLogger('app.service.api', { level: 'trace' });
apiLogger.trace('Detailed trace'); // ✓ ВидноДинамическое обновление конфигурации
const logger = tree.createLogger('app');
// Обновление только для текущего узла
tree.updateParam('app', 'level', 'debug');
// Обновление с распространением на всех потомков
tree.updateParam('app', 'level', 'trace', { force: true });
// Ссылка на logger остается той же, но конфигурация обновлена
logger.trace('Now visible'); // ✓ ВидноАвтоматическая привязка к объектам
import { LoggerAware, TreeLogger } from '@vvlad1973/pino-logger-tree';
class UserService implements LoggerAware {
logger!: TreeLogger;
async getUser(id: number) {
this.logger.info('Fetching user', { userId: id });
// ... логика
}
}
const service = new UserService();
// Привязываем логгер к сервису
tree.bind('app.user-service', service);
// service.logger автоматически установлен и будет обновляться
service.getUser(42);
// При обновлении конфигурации логгер в service обновится автоматически
tree.updateParam('app.user-service', 'level', 'debug');Трассировка функций
const logger = tree.createLogger('app', { level: 'trace' });
function processData(id: number, options: object) {
// Автоматическое определение имени функции + логирование параметров
logger.logFunctionStart('processData', { id, options });
// ... обработка данных
logger.logFunctionEnd('processData');
}
processData(42, { mode: 'fast' });
// → {"level":10,"params":{"id":42,"options":{"mode":"fast"}},"msg":"Function start: processData"}
// → {"level":10,"msg":"Function end: processData"}Bindings и контекст
// Создание с bindings
const logger = tree.createLogger('app', {
bindings: {
service: 'user-service',
version: '1.0.0'
}
});
logger.info('Service ready');
// → {"level":30,"service":"user-service","version":"1.0.0","msg":"Service ready"}
// Добавление контекста через child()
const requestLogger = logger.child({ requestId: 'req-123' });
requestLogger.info('Processing request');
// → {"level":30,"service":"user-service","requestId":"req-123","msg":"Processing request"}API
LoggerTree
Constructor
constructor(baseOptions?: PinoLoggerOptions, destination?: DestinationStream)Создает новое дерево логгеров с базовой конфигурацией.
createLogger
createLogger(path: string, options?: TreeLoggerOptions): TreeLoggerСоздает или возвращает существующий логгер для указанного пути.
path- точка-разделенный путь (например,'app.service.api')options- опции конфигурации и bindings
updateParam
updateParam(path: string, key: string, value: unknown, options?: UpdateParamOptions): voidОбновляет параметр конфигурации для узла.
path- путь к узлуkey- ключ параметра (например,'level')value- новое значениеoptions.force- распространить на всех потомков
bind / unbind
bind(path: string, target: LoggerAware): void
unbind(path: string, target: LoggerAware): voidПривязывает/отвязывает LoggerAware объект к пути в дереве.
TreeLogger
TreeLogger полностью совместим с Pino Logger и предоставляет все его методы:
trace(msg, ...args)/trace(obj, msg, ...args)debug(msg, ...args)/debug(obj, msg, ...args)info(msg, ...args)/info(obj, msg, ...args)warn(msg, ...args)/warn(obj, msg, ...args)error(msg, ...args)/error(obj, msg, ...args)fatal(msg, ...args)/fatal(obj, msg, ...args)level- getter/setter для уровня логированияbindings()- получение текущих bindingsflush()- сброс буферов
Дополнительные методы TreeLogger
child
child(path: string, bindings?: Record<string, unknown>): TreeLogger
child(bindings: Record<string, unknown>): TreeLoggerСоздает дочерний логгер с автоматической регистрацией в дереве.
getPath
getPath(): stringВозвращает путь логгера в дереве.
getTree
getTree(): LoggerTreeВозвращает экземпляр LoggerTree, управляющий этим логгером.
logFunctionStart
logFunctionStart(name?: string, params?: Record<string, unknown>): voidЛогирует начало функции на уровне trace. Автоматически определяет имя функции, если не указано.
logFunctionEnd
logFunctionEnd(name?: string): voidЛогирует конец функции на уровне trace.
Вспомогательные классы
LoggerAwareBase
Абстрактный базовый класс для унифицированной инициализации логгеров через наследование.
abstract class LoggerAwareBase implements LoggerAware {
logger!: TreeLogger;
protected initializeLogger(
options: BaseLoggerOptions | undefined,
defaultPath: string
): void;
protected cleanupLogger(): void;
protected getChildLogger(childPath: string): TreeLogger;
}initializeLogger
Инициализирует логгер с тремя режимами работы:
- Прямой логгер - использует
options.loggerнапрямую - Привязка к дереву - использует
options.loggerTreeдля автоматической привязки - Собственное дерево - создает изолированное дерево логгеров
class MyService extends LoggerAwareBase {
constructor(options?: BaseLoggerOptions) {
super();
this.initializeLogger(options, 'my-service');
}
}cleanupLogger
Отвязывает объект от дерева логгеров при уничтожении.
getChildLogger
Создает дочерний логгер с иерархическим путем.
LoggerManager
Менеджер логгеров для композиционного подхода (когда наследование невозможно).
class LoggerManager {
logger: TreeLogger;
constructor(
options: BaseLoggerOptions | undefined,
defaultPath: string,
target?: LoggerAware
);
cleanup(): void;
getChildLogger(childPath: string): TreeLogger;
}Особенности
Предоставляет ту же функциональность, что и LoggerAwareBase, но через композицию:
- Те же три режима инициализации: прямой логгер, привязка к дереву, собственное дерево
- Опциональная привязка к target: автоматически устанавливает
target.logger - Управление жизненным циклом: метод
cleanup()для корректной отвязки
class MyService {
private loggerManager: LoggerManager;
constructor(options?: BaseLoggerOptions) {
this.loggerManager = new LoggerManager(options, 'my-service');
}
get logger(): TreeLogger {
return this.loggerManager.logger;
}
doWork() {
this.logger.info('Working...');
}
destroy() {
this.loggerManager.cleanup();
}
}
// С автоматической привязкой к target
class ServiceWithBinding implements LoggerAware {
logger!: TreeLogger;
private loggerManager: LoggerManager;
constructor(options?: BaseLoggerOptions) {
this.loggerManager = new LoggerManager(options, 'service', this);
// this.logger уже установлен автоматически
}
destroy() {
this.loggerManager.cleanup();
}
}Декораторы
@LogMethod
Декоратор для автоматического логирования асинхронных методов.
interface LogMethodOptions extends ParameterLoggingOptions {
level?: LoggerLevel; // Уровень логирования (default: 'trace')
startMessage?: string; // Кастомное сообщение старта
endMessage?: string; // Кастомное сообщение завершения
errorMessage?: string; // Кастомное сообщение ошибки
logResult?: boolean; // Логировать результат (default: false)
rethrow?: boolean; // Пробрасывать ошибки (default: true)
}Особенности:
- Автоматически логирует начало, завершение и ошибки метода
- Извлекает имена параметров из сигнатуры функции
- Маскирует чувствительные данные (password, apiKey, token, secret, и др.)
- Поддерживает кастомные сообщения и контекстные поля
class ApiService extends LoggerAwareBase {
@LogMethod({
level: 'info',
logResult: true,
contextFields: ['userId']
})
async fetchUser(id: number): Promise<User> {
// ...
}
}@LogSyncMethod
Декоратор для автоматического логирования синхронных методов.
interface LogSyncMethodOptions extends ParameterLoggingOptions {
level?: LoggerLevel; // Уровень логирования (default: 'trace')
startMessage?: string; // Кастомное сообщение старта
endMessage?: string; // Кастомное сообщение завершения
errorMessage?: string; // Кастомное сообщение ошибки
logResult?: boolean; // Логировать результат (default: false)
rethrow?: boolean; // Пробрасывать ошибки (default: true)
}Идентичен @LogMethod, но для синхронных методов.
class Calculator extends LoggerAwareBase {
@LogSyncMethod({ level: 'debug', logResult: true })
add(a: number, b: number): number {
return a + b;
}
}@LogClass
Декоратор класса для автоматического применения логирования ко всем методам.
interface LogClassOptions {
asyncMethodOptions?: LogMethodOptions; // Опции для async методов
syncMethodOptions?: LogSyncMethodOptions; // Опции для sync методов
exclude?: string[]; // Исключить методы
include?: string[]; // Включить только эти методы
logConstructor?: boolean; // Логировать конструктор
}Особенности:
- Автоматически определяет async/sync методы
- Исключает стандартные методы:
constructor,toString,toJSON,valueOf - Поддерживает белые (
include) и черные (exclude) списки методов - Применяет разные опции для async и sync методов
@LogClass({
asyncMethodOptions: { level: 'info', logResult: true },
syncMethodOptions: { level: 'debug' },
exclude: ['internalHelper']
})
class UserRepository extends LoggerAwareBase {
async findById(id: number) { /* ... */ }
validateId(id: number) { /* ... */ }
internalHelper() { /* не логируется */ }
}ParameterLoggingOptions
Базовые опции для логирования параметров (используются всеми декораторами).
interface ParameterLoggingOptions {
logParams?: boolean; // Логировать параметры (default: true)
maskSensitive?: boolean; // Маскировать чувствительные данные (default: true)
sensitivePatterns?: string[]; // Кастомные паттерны для маскирования
excludeParams?: string[]; // Исключить определенные параметры
contextFields?: string[]; // Добавить поля из контекста this
}Автоматическое маскирование:
По умолчанию маскируются поля со следующими паттернами (регистронезависимо):
passwordsecrettokenapikey/api_keyaccesstoken/access_tokenrefreshtoken/refresh_tokenprivatekey/private_keycredentialsauthorization
@LogMethod({
excludeParams: ['internalFlag'],
contextFields: ['requestId', 'userId'],
sensitivePatterns: ['ssn', 'creditCard']
})
async processPayment(
amount: number,
creditCard: string,
internalFlag: boolean
) {
// Логирует: { amount: 100, creditCard: '***MASKED***', requestId: '...', userId: '...' }
// internalFlag не логируется
}Типы
LoggerAware
interface LoggerAware {
logger: TreeLogger;
}Интерфейс для объектов с автоматической инъекцией логгера.
TreeLoggerOptions
interface TreeLoggerOptions extends PinoLoggerOptions {
bindings?: Record<string, unknown>;
}Опции для создания TreeLogger.
BaseLoggerOptions
interface BaseLoggerOptions {
logger?: TreeLogger; // Прямой логгер
loggerTree?: LoggerTree; // Дерево для привязки
loggerPath?: string; // Путь в дереве
logLevel?: LoggerLevel; // Уровень логирования
loggerOptions?: PinoLoggerOptions; // Опции Pino
}Опции для инициализации логгера в LoggerAwareBase.
UpdateParamOptions
interface UpdateParamOptions {
force?: boolean;
}Опции для обновления параметров конфигурации.
LoggerLevel
type LoggerLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent';Доступные уровни логирования.
LoggerNode
interface LoggerNode {
config: PinoLoggerOptions;
logger?: TreeLogger;
children: Map<string, LoggerNode>;
}Узел в дереве логгеров (используется внутри LoggerTree).
Утилиты
getCallerName
function getCallerName(level?: number): stringПолучает имя вызывающей функции из stack trace.
Параметры:
level- уровень стека (опционально, для автоматического режима не указывать)
Возвращает: имя функции или 'anonymous'
function myFunction() {
const caller = getCallerName();
console.log(caller); // 'myFunction'
}LoggerLevels
const LoggerLevels = {
TRACE: 'trace',
DEBUG: 'debug',
INFO: 'info',
WARN: 'warn',
ERROR: 'error',
FATAL: 'fatal',
SILENT: 'silent',
} as const;Константа с уровнями логирования для type-safe использования.
import { LoggerLevels } from '@vvlad1973/pino-logger-tree';
const logger = tree.createLogger('app', { level: LoggerLevels.DEBUG });extractParameterNames
function extractParameterNames(fn: Function): string[]Извлекает имена параметров из сигнатуры функции.
function example(userId: number, email: string) {}
const names = extractParameterNames(example);
// ['userId', 'email']mapArgumentsToParams
function mapArgumentsToParams(
fn: Function,
args: unknown[],
context?: object,
options?: ParameterLoggingOptions
): Record<string, unknown> | undefinedПреобразует аргументы функции в объект параметров с учетом опций маскирования.
isSensitiveField
function isSensitiveField(
key: string,
sensitivePatterns?: string[]
): booleanПроверяет, является ли поле чувствительным для маскирования.
maskSensitiveData
function maskSensitiveData(
data: unknown,
sensitivePatterns?: string[]
): unknownРекурсивно маскирует чувствительные данные в объекте.
DEFAULT_SENSITIVE_PATTERNS
const DEFAULT_SENSITIVE_PATTERNS: string[]Стандартные паттерны для поиска чувствительных полей:
password,secret,token,apikey,api_keyaccesstoken,access_token,refreshtoken,refresh_tokenprivatekey,private_key,credentials,authorization
prepareParamsForLogging
function prepareParamsForLogging(
fn: Function,
args: unknown[],
context?: object,
options?: ParameterLoggingOptions
): Record<string, unknown> | undefinedПодготавливает параметры для логирования с применением всех правил (извлечение имен, маскирование, контекстные поля).
Примеры использования
Express/Fastify интеграция
import express from 'express';
import { v4 as uuid } from 'uuid';
import { loggerTree } from './logger';
const app = express();
app.use((req, res, next) => {
req.logger = loggerTree
.createLogger('app.api')
.child({
requestId: uuid(),
method: req.method,
url: req.url
});
req.logger.info('Request started');
next();
});
app.get('/users', (req, res) => {
req.logger.debug('Fetching users');
// ...
});Класс с автоматической привязкой
import { LoggerAware, TreeLogger } from '@vvlad1973/pino-logger-tree';
class DatabaseService implements LoggerAware {
logger!: TreeLogger;
async connect() {
this.logger.info('Connecting to database');
// ...
}
async query(sql: string) {
this.logger.debug('Executing query', { sql });
// ...
}
}
const db = new DatabaseService();
tree.bind('app.database', db);
// db.logger автоматически установлен и управляется деревом
db.connect();Использование LoggerAwareBase
import { LoggerAwareBase } from '@vvlad1973/pino-logger-tree';
class UserService extends LoggerAwareBase {
constructor(options?: BaseLoggerOptions) {
super();
// Автоматическая инициализация логгера с тремя режимами:
// 1. Если передан logger - использовать его
// 2. Если передан loggerTree - привязаться к дереву
// 3. Иначе создать собственное дерево
this.initializeLogger(options, 'user-service');
}
async createUser(username: string, email: string) {
this.logger.info('Creating user', { username, email });
// ...
}
destroy() {
// Очистка при уничтожении сервиса
this.cleanupLogger();
}
}
// Режим 1: С существующим деревом
const service1 = new UserService({ loggerTree: tree });
// Режим 2: С прямым логгером
const service2 = new UserService({ logger: tree.createLogger('custom') });
// Режим 3: Создаст собственное дерево
const service3 = new UserService({ logLevel: 'debug' });Использование декораторов
import { LoggerAwareBase, LogMethod, LogSyncMethod, LogClass } from '@vvlad1973/pino-logger-tree';
class PaymentService extends LoggerAwareBase {
constructor() {
super();
this.initializeLogger({}, 'payment-service');
}
// Декоратор для асинхронных методов
@LogMethod({ level: 'info', logResult: true })
async processPayment(amount: number, currency: string): Promise<PaymentResult> {
// Автоматически логирует:
// INFO: Method processPayment started { amount: 100, currency: 'USD' }
// INFO: Method processPayment completed { result: {...} }
return { transactionId: '123', status: 'completed' };
}
// Декоратор для синхронных методов
@LogSyncMethod({ level: 'debug' })
validateAmount(amount: number): boolean {
// Автоматически логирует:
// DEBUG: Method validateAmount started { amount: 100 }
// DEBUG: Method validateAmount completed
return amount > 0;
}
// Автоматическое маскирование чувствительных данных
@LogMethod()
async authenticate(apiKey: string, secret: string): Promise<string> {
// Логирует: { apiKey: '***MASKED***', secret: '***MASKED***' }
return 'token-123';
}
}
// Декоратор для всего класса
@LogClass({
asyncMethodOptions: { level: 'info', logResult: true },
syncMethodOptions: { level: 'debug' },
exclude: ['internalMethod']
})
class AutoLoggedService extends LoggerAwareBase {
constructor() {
super();
this.initializeLogger({}, 'auto-logged');
}
// Автоматически логируется (async)
async fetchData(id: number) {
return { id, data: 'example' };
}
// Автоматически логируется (sync)
processData(data: string) {
return data.toUpperCase();
}
// Не логируется (в exclude)
internalMethod() {
// ...
}
}Модульная структура приложения
// logger.ts
export const loggerTree = new LoggerTree({
level: process.env.LOG_LEVEL || 'info',
name: 'my-app'
});
// services/user.service.ts
import { loggerTree } from '../logger';
const logger = loggerTree.createLogger('app.services.user');
export class UserService {
async createUser(data: UserData) {
logger.info('Creating user', { email: data.email });
// ...
}
}
// services/auth.service.ts
import { loggerTree } from '../logger';
const logger = loggerTree.createLogger('app.services.auth', { level: 'debug' });
export class AuthService {
async login(credentials: Credentials) {
logger.debug('Login attempt', { username: credentials.username });
// ...
}
}Продакшен конфигурация
import pino from 'pino';
import { LoggerTree } from '@vvlad1973/pino-logger-tree';
const tree = new LoggerTree({
level: process.env.LOG_LEVEL || 'info',
name: process.env.APP_NAME,
// Structured timestamps
timestamp: pino.stdTimeFunctions.isoTime,
// Редактирование чувствительных данных
redact: {
paths: ['password', 'secret', 'token', '*.password', 'headers.authorization'],
remove: true
},
// Сериализаторы для ошибок
serializers: {
err: pino.stdSerializers.err,
req: pino.stdSerializers.req,
res: pino.stdSerializers.res
}
});Производительность
- Минимальный overhead: Proxy напрямую делегирует методы Pino
- Кэширование: Каждый логгер создается один раз и переиспользуется
- Lazy initialization: Узлы создаются только при необходимости
- Эффективная агрегация: Конфигурация собирается только при создании логгера
Бенчмарки показывают, что overhead библиотеки составляет менее 1% по сравнению с прямым использованием Pino.
Требования
- Node.js >= 18.0
- TypeScript >= 5.0 (для разработки)
- Pino >= 10.0
Разработка
# Установка зависимостей
npm install
# Сборка
npm run build
# Тесты
npm test
# Тесты с покрытием
npm run test:coverage
# Линтинг
npm run lint
# Генерация документации
npm run docТестирование
Библиотека имеет комплексное покрытие тестами:
npm testРезультаты:
- 125 тестов полностью пройдены в 5 тестовых файлах:
- 31 тест для LoggerTree
- 33 теста для TreeLogger
- 46 тестов для декораторов (@LogMethod, @LogSyncMethod, @LogClass)
- 9 тестов для LoggerAwareBase
- 6 тестов для LoggerManager
- Покрытие кода: соответствует требуемым стандартам (80%+)
- Все критические компоненты покрыты тестами
Тестовые наборы:
- LoggerTree - создание, каскадная конфигурация, динамические обновления, binding
- TreeLogger - методы логирования, child логгеры, bindings, функциональная трассировка
- Декораторы - автоматическое логирование, маскирование параметров, обработка ошибок
- LoggerAwareBase - три режима инициализации, управление жизненным циклом
- LoggerManager - композиционный подход, привязка к target объектам
Документация
- Быстрый старт - руководство по началу работы
- Архитектура - детальное описание архитектуры
- API документация - автоматически сгенерированная TypeDoc документация
Лицензия
MIT
Автор
Vladislav Vnukovskiy [email protected]
Связанные проекты
- Pino - Fast JSON logger for Node.js
- Pino-pretty - Beautiful logs for development
