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

@vvlad1973/logger-tree

v1.4.1

Published

Hierarchical logger tree system with configuration cascading for TypeScript/JavaScript applications

Readme

@vvlad1973/logger-tree

npm version npm downloads License TypeScript Build Status Tests Coverage

Иерархическая система логеров с каскадной конфигурацией для TypeScript/JavaScript. Позволяет собирать логирование в единую древовидную структуру, задавать базовые правила на уровне корня и переопределять их точечно для нужных веток.

English version: README.md

Возможности

  • Иерархия логеров по dot-путям, чтобы каждый модуль имел свой контекст.
  • Наследование конфигурации: дети получают настройки родителя и могут их переопределять.
  • Ленивое создание логеров и кеширование, чтобы не тратить ресурсы до первого обращения.
  • Гибкие обновления параметров с force, cascade и restrict для контроля распространения изменений.
  • Автоматическая привязка логеров к объектам через Binder с последующими обновлениями.
  • Полная поддержка TypeScript и готовые типы.
  • Внешних зависимостей нет, кроме @vvlad1973/simple-logger.

Установка

npm install @vvlad1973/logger-tree

Быстрый старт

Ниже — минимальный пример. Создаем дерево, поднимаем логеры для разных веток, задаем общий уровень и точечное переопределение для API.

import { LoggerTree, LoggerBinder } from '@vvlad1973/logger-tree';
import SimpleLogger from '@vvlad1973/simple-logger';

// Создаем дерево логеров
const tree = new LoggerTree(() => new SimpleLogger());

// Логеры на разные пути
const appLogger = tree.createLogger('app');
const apiLogger = tree.createLogger('app.api');
const dbLogger = tree.createLogger('app.database');

// Базовый уровень для всего приложения
tree.updateParam('app', 'level', 'debug');

// Переопределение для сервиса
tree.updateParam('app.api', 'level', 'info');

// Использование
appLogger.debug('Application started');
apiLogger.info('API server listening on port 3000');
dbLogger.debug('Database connection established');

Ключевые идеи

Иерархические пути

Каждый логер живет на пути вида app.service.worker. Дерево позволяет легко задавать общие правила на уровне родителя и локально менять поведение для конкретной ветки.

app
├── app.api
│   ├── app.api.routes
│   └── app.api.middleware
└── app.database
    ├── app.database.postgres
    └── app.database.redis

Каскадирование конфигурации

Настройки на родителе автоматически наследуются детьми:

// Базовая конфигурация
tree.updateParam('app', 'level', 'warn');

// Дети наследуют WARN
const service = tree.createLogger('app.service'); // level: warn

// Переопределение для конкретного узла
tree.updateParam('app.debug', 'level', 'debug');
const debug = tree.createLogger('app.debug'); // level: debug

Базовый/root-логер с явной конфигурацией

  • Узлы появляются только после createLogger(path). Вызов updateParam для несуществующего пути ничего не меняет.
  • Предварительную конфигурацию удобно задать через initialConfig в конструкторе. Она применяется лениво при первом создании узла. Если нужно задать только префикс сообщения, можно передать строку — это шорткат для msgPrefix.
  • В initialConfig должен быть один корневой узел. Для нескольких корней создайте отдельные экземпляры LoggerTree.
  • Если initialConfig не используется, сначала создайте узел через createLogger, затем вызывайте updateParam.
// Предопределенный root-конфиг (ленивое применение)
const tree = new LoggerTree(() => new SimpleLogger(), undefined, {
  app: {
    options: { level: 'warn' },
    children: {
      worker: '[Worker] ', // shorthand для { msgPrefix: '[Worker] ' }
      service: {
        options: { level: 'info' },
        children: {
          api: { options: { level: 'debug' } },
          worker: { options: { msgPrefix: '[SvcWorker] ' } }
        }
      }
    }
  }
});

// Конфиг применится при первом создании узла
const worker = tree.createLogger('app.worker.job'); // msgPrefix: [Worker]
const api = tree.createLogger('app.api'); // level: warn

// Создаем корень
tree.createLogger('app');

// Базовая конфигурация через updateParam
tree.updateParam('app', 'level', 'warn');
tree.updateParam('app', 'transports', {
  targets: [{ tag: 'console', level: 'warn' }]
});

// Наследование для детей
const apiAgain = tree.createLogger('app.api'); // inherits warn

Root alias (префикс пути)

Если у вас есть единый корень (например, app), передайте его как alias. Тогда во всех вызовах можно указывать короткие пути, а дерево само добавит префикс. Это упрощает использование и сохраняет консистентность имен.

const tree = new LoggerTree(() => new SimpleLogger(), undefined, undefined, 'app');

// Фактически создает/обновляет app.worker.email
const workerLogger = tree.createLogger('worker.email');
tree.updateParam('worker', 'level', 'error', { force: true }); // применяется к app.worker.*

Автосоздание промежуточных узлов

При первом вызове createLogger по новому пути дерево достроит все недостающие сегменты (app, app.agents, app.agents.some_agent) и создаст логеры на каждом уровне. Это гарантирует, что последующие обновления уровня или транспорта смогут нормально каскадировать. Все новые узлы сразу получают агрегированный конфиг родителей (включая initialConfig), поэтому стартуют с согласованными уровнями и префиксами.

const tree = new LoggerTree(() => new SimpleLogger(), undefined, {
  app: { options: { level: 'warn' } }
});

// Создаст loggers: app, app.agents, app.agents.some_agent с level: warn
const agentLogger = tree.createLogger('app.agents.some_agent');

// Родитель уже есть, обновления каскадируют сразу
tree.updateParam('app.agents', 'level', 'error', { force: true });
agentLogger.error('will log at error');

Расширенные опции каскадирования уровней:

// Force: перезаписать уровни у потомков
tree.updateParam('app', 'level', 'error', { force: true });

// Cascade off: только текущий узел
tree.updateParam('app.worker', 'level', 'fatal', { cascade: false });

// Restrict (one-time clamp): поднять потомков до WARN, если они болтливее
tree.updateParam('app', 'level', 'warn', { restrict: true });

// Попытка задать более болтливый уровень будет ограничена до WARN
tree.updateParam('app.worker', 'level', 'debug');
const workerLogger2 = tree.createLogger('app.worker'); // level: warn

Использование с pino и per-child transports

LoggerTree передает конфигурацию в SimpleLogger, а тот — в Pino (фабрика или .child()) с transport/transports. Это позволяет раздавать разные транспорты по веткам, сохраняя общую структуру дерева.

import pino from 'pino';
import { LoggerTree } from '@vvlad1973/logger-tree';

const tree = new LoggerTree(pino);

// Базовый транспорт
tree.updateParam('app', 'transport', {
  target: 'pino-pretty',
  options: { colorize: true }
});

// Специальный транспорт для ребенка
tree.updateParam('app.worker', 'transport', {
  target: 'pino/file',
  options: { destination: './worker.log' }
});

const worker = tree.createLogger('app.worker'); // логирует в worker.log

С Pino v8 можно использовать несколько таргетов через transports.targets:

tree.updateParam('app.worker', 'transports', {
  targets: [
    { target: 'pino/file', options: { destination: './worker.log' } },
    { target: 'pino-pretty', options: { colorize: false } }
  ]
});

Обновление логеров (совместимость с logrotate)

При ротации файлов (например, через logrotate) пересоздайте логеры, чтобы они заново открыли файлы. Можно обновлять точку или целую ветку; для больших поддеревьев есть батчевое обновление binder.

// Только ветка app.worker и потомки
tree.refreshLoggers('app.worker', { recursive: true });

// Только конкретный узел (без детей)
tree.refreshLoggers('app.audit');

// Батчевое обновление binder для больших поддеревьев
tree.refreshLoggers('app', { recursive: true, batchedBinder: true });

refreshLoggers очищает кеш и создает новые экземпляры с текущей конфигурацией. При batchedBinder: true обновление binder выполняется один раз за вызов.

Ленивое создание

Логер создается только при первом обращении и затем возвращается из кеша. Это избавляет от лишних аллокаций, если путь никогда не используется.

const logger1 = tree.createLogger('app.service');
const logger2 = tree.createLogger('app.service');

console.log(logger1 === logger2); // true

API

LoggerTree

Класс для управления иерархией логеров.

Usage guide

  • createLogger(path: string)
    Создает или возвращает кешированный логер с наследованной конфигурацией.

  • updateParam(path, key, value, options?)

    • Обновление параметра узла (level, transport, transports и т.д.).
    • force: true — перезаписать значения у потомков.
    • cascade: false — применить только к этому узлу.
    • restrict: true с level — одноразово поднять уровни потомков до уровня родителя.
      Типовые сценарии:
    // Базовый уровень
    tree.updateParam('app', 'level', 'info');
    // Поднять поддерево
    tree.updateParam('app.api', 'level', 'warn', { force: true });
    // Локальное переопределение
    tree.updateParam('app.api.worker', 'level', 'fatal', { cascade: false });
    // Ограничить чрезмерно болтливых потомков
    tree.updateParam('app', 'level', 'warn', { restrict: true });
  • updateTargetParam(path, param, value, { tag, force? })
    Обновить таргеты транспорта по тегу (в т.ч. включить/выключить) с опциональным каскадом. Работает и с transport, и с transports.targets.

    // Выключить консоль на поддереве
    tree.updateTargetParam('app', 'enabled', false, { tag: 'console', force: true });
    
    // Поднять уровень для file-транспорта только на узле
    tree.updateTargetParam('app.worker', 'level', 'error', { tag: 'file' });
    
    // Изменить формат для console на конкретном ребенке
    tree.updateTargetParam('app.worker', 'format', 'json', { tag: 'console' });
  • refreshLoggers(path, { recursive?, batchedBinder? })
    Пересоздать логеры (например, после ротации). recursive: true захватывает потомков; batchedBinder: true батчит обновление binder.

  • LoggerBinder.bind(path, target) / unbind / refresh / refreshAll / refreshMany
    Биндинг объектов (LoggerAware) к путям и их автообновление при изменении конфигурации. refreshMany — батчевое обновление.

    Цель должна иметь свойство logger (реализовать LoggerAware), чтобы binder мог присвоить логер.

Конструктор

constructor(
  rootLogger?: ExternalLogger | LoggerFactory | null,
  binder?: LoggerBinder,
  initialConfig?: Record<string, LoggerOptions | string | { children?: Record<string, unknown> }>
)

Методы

createLogger(path: string, inlineConfig?: LoggerOptions, options?: CreateLoggerOptions): SimpleLogger

Создает или возвращает кешированный логер по пути.

const logger = tree.createLogger('app.service.api');

// Переопределить агрегированный конфиг при первом создании
const debugLogger = tree.createLogger('app.debug', { level: 'debug' });

// Глубокое слияние inline-конфига вместо поверхностной замены
const patchedLogger = tree.createLogger('app.service',
  { transports: { targets: [{ tag: 'file', level: 'debug' }] } },
  { patch: true }
);

// Автогенерация msgPrefix из сегментов пути
const autoLogger = tree.createLogger('app.worker.queue'); // msgPrefix: [queue]

// Полное отключение msgPrefix
const noPrefix = tree.createLogger('app.clean', {}, { useMsgPrefix: false });

Опции:

  • patch: boolean - При значении true выполняется глубокое слияние inline-конфига с агрегированным конфигом вместо поверхностной замены. Полезно для слияния вложенных объектов типа transports.
  • autoMsgPrefix: boolean - Автоматически генерировать msgPrefix из последнего сегмента пути, если не указан явно. По умолчанию true.
  • useMsgPrefix: boolean - Включить/отключить msgPrefix полностью. При false префикс не будет добавлен, даже если он есть в агрегированном конфиге.

Примечания:

  • Если путь (или родительские сегменты) не существует, цепочка создается автоматически, а логеры — для каждого нового сегмента с унаследованным конфигом.
  • Повторные вызовы возвращают кешированный логер, пока не передан inlineConfig.
updateParam(path: string, key: string, value: unknown, options?: { force?: boolean; restrict?: boolean; cascade?: boolean }): void

Обновляет параметр конфигурации узла.

// Только этот узел
tree.updateParam('app.service', 'level', 'debug');

// Каскадом на детей без явной конфигурации
tree.updateParam('app', 'level', 'error', { force: true });

// Только этот узел (дети не меняются)
tree.updateParam('app.worker', 'level', 'fatal', { cascade: false });

// Запретить детям быть болтливее WARN
tree.updateParam('app', 'level', 'warn', { restrict: true });
updateTargetParam(path: string, param: string, value: unknown, options?: { tag: string; force?: boolean; restrict?: boolean }): void

Обновляет параметры транспортного таргета по тегу. Работает с transport и transports.targets.

tree.updateTargetParam('app', 'enabled', false, {
  tag: 'console',
  force: true
});

// Поднять уровень только для file-таргета
tree.updateTargetParam('app.worker', 'level', 'error', { tag: 'file' });

// Изменить формат для console
tree.updateTargetParam('app.worker', 'format', 'json', { tag: 'console' });

// Ограничить дочерние логеры от более подробного логирования, чем родительский уровень
tree.updateTargetParam('app', 'level', 'warn', { tag: 'console', restrict: true });

Опции:

  • tag: string - Тег таргета для обновления (требуется при обновлении конкретных транспортных таргетов)
  • force?: boolean - Применить изменение ко всем дочерним узлам
  • restrict?: boolean - При обновлении уровня ограничить подробные дочерние логеры до родительского уровня

Пример с тегами:

// Базовые транспорты с тегами
tree.updateParam('app', 'transports', {
  targets: [
    { tag: 'console', target: 'pino-pretty', level: 'info' }
  ]
});

// Собственный файл для сервиса A
tree.updateParam('app.serviceA', 'transports', {
  targets: [
    { tag: 'console', target: 'pino-pretty', level: 'info' },
    { tag: 'file', target: 'pino/file', options: { destination: './serviceA.log' }, level: 'debug' }
  ]
});

// Другой файл для сервиса B
tree.updateParam('app.serviceB', 'transports', {
  targets: [
    { tag: 'console', target: 'pino-pretty', level: 'info' },
    { tag: 'file', target: 'pino/file', options: { destination: './serviceB.log' }, level: 'warn' }
  ]
});

// Отключить консоль для всех сервисов
tree.updateTargetParam('app', 'enabled', false, { tag: 'console', force: true });

// Поднять уровень файла только для serviceA
tree.updateTargetParam('app.serviceA', 'level', 'error', { tag: 'file' });

// Вернуть консоль только для serviceB
tree.updateTargetParam('app.serviceB', 'enabled', true, { tag: 'console' });

LoggerBinder

Управляет автоматической инъекцией и обновлением логеров в объектах. Позволяет навесить логер на объект один раз и быть уверенным, что при изменении конфигурации он обновится без ручных действий.

Цели должны реализовывать LoggerAware (иметь свойство logger), чтобы binder мог присваивать/обновлять логер.

Конструктор

constructor(tree: LoggerTree)

Методы

bind(path: string, target: LoggerAware): void

Привязывает объект к пути логера.

import type { LoggerAware } from '@vvlad1973/logger-tree';

class MyService implements LoggerAware {
  logger!: SimpleLogger;
}

const service = new MyService();
binder.bind('app.service', service);
// service.logger заполнен автоматически
unbind(path: string, target: LoggerAware): void

Отвязывает объект от обновлений логера.

binder.unbind('app.service', service);
refresh(path: string): void

Обновляет логеры для всех объектов, привязанных к пути.

tree.updateParam('app', 'level', 'debug');
binder.refresh('app'); // обновляет все привязанные объекты
refreshAll(): void

Обновляет все привязанные логеры за один проход.

binder.refreshAll();
refreshMany(paths: string[]): void

Батчевое обновление без рекурсивного fan-out (если нужен контроль).

binder.refreshMany(['app', 'app.worker']);

Advanced Usage

Автоматический биндинг логеров

import { LoggerTree, LoggerBinder, LoggerAware } from '@botapp/logger-tree';

const tree = new LoggerTree();
const binder = new LoggerBinder(tree);

class DatabaseService implements LoggerAware {
  logger!: SimpleLogger; // Проставляется binder'ом

  async connect() {
    this.logger.info('Connecting to database...');
    // ...
    this.logger.info('Connected successfully');
  }
}

const dbService = new DatabaseService();
binder.bind('app.database', dbService);

// Логер обновится при изменении конфигурации
tree.updateParam('app.database', 'level', 'debug');
binder.refresh('app.database');

Binder с alias

import { LoggerTree, LoggerBinder, LoggerAware } from '@botapp/logger-tree';
import SimpleLogger from '@vvlad1973/simple-logger';

class Agent implements LoggerAware {
  logger!: SimpleLogger;
}

const tree = new LoggerTree(() => new SimpleLogger(), undefined, undefined, 'app');
const binder = new LoggerBinder(tree);

const agent = new Agent();
binder.bind('agents.email', agent); // фактически app.agents.email

tree.updateParam('agents', 'level', 'warn', { force: true });
binder.refresh('agents'); // обновит app.agents.* (включая email)

Настройка транспортов

Транспорты можно включать/выключать и менять параметры по тегу. Это удобно, чтобы быстро приглушить консоль в проде или изменить уровень файлового вывода.

tree.updateParam('app', 'transports', {
  targets: [
    { tag: 'console', level: 'info', enabled: true },
    { tag: 'file', level: 'debug', enabled: true }
  ]
});

// Выключить консоль в проде
tree.updateTargetParam('app', 'enabled', false, {
  tag: 'console',
  force: true
});

Интеграция с Pino

LoggerTree может использовать Pino как внешний логер через SimpleLogger. Это дает привычный API Pino, но с иерархической структурой и каскадными настройками.

import { LoggerTree } from '@vvlad1973/logger-tree';
import pino from 'pino';

const pinoLogger = pino({
  level: 'info',
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true,
      translateTime: 'HH:MM:ss Z',
      ignore: 'pid,hostname'
    }
  }
});

const tree = new LoggerTree(pinoLogger);

const appLogger = tree.createLogger('app');
const apiLogger = tree.createLogger('app.api');
const dbLogger = tree.createLogger('app.database');

tree.updateParam('app', 'level', 'debug');
tree.updateParam('app.api', 'level', 'info');

appLogger.debug('Application started');        // Pino внутри
apiLogger.info('API server ready');            // Pino внутри
dbLogger.debug('Database connected');          // Pino внутри

Преимущества с Pino:

  • Производительность Pino в сочетании с иерархией.
  • Наследование конфигурации по дереву без дублирования настроек.
  • Быстрый JSON-логгинг и богатые транспорты Pino.
  • Автобиндинг логеров к объектам.
  • Динамические обновления конфигурации без перезапуска.