
Логи там, логи здесь... Логи — везде. Каждое современное приложение нуждается в логировании. В каждом языке есть свои фреймворки и библиотеки для сбора логов с сервисов, но сегодня мы поговорим про JavaScript и Node.js.
В мире Node.js много различных библиотек для логирования, каждая со своими особенностями. Сегодня мы разберем библиотеку Pino, которая на данный момент в плане производительности и гибкости является наиболее выигрышным вариантом по сравнению с другими. Почему так, разберём далее.
Что будет рассмотренно в статье:
-
Что такое Pino и чем он отличается от других
Познакомимся с Pino — высокопроизводительным логером для Node.js. Узнаем, почему он выделяется на фоне других библиотек, таких как Winston или Bunyan, и какие преимущества делают его популярным выбором среди разработчиков.
-
Основной функционал Pino
Разберем ключевые возможности библиотеки:
-
Уровни логирования и их настройка.
-
Гибкая конфигурация логера.
-
Асинхронное логирование и его влияние на производительность.
-
Механизмы transport и multistream для обработки и маршрутизации логов.
-
Child loggers для контекстного логирования.
-
Reduct для защиты чувствительных данных
-
Поддержка плагинов и расширений.
Причём упор будет сделан на подробный разбор всех низкоуровневых моментов и «подкапотную» работу библиотеки. Для тех, кто хотел понять, как же работает Pino изнутри, рекомендую к прочтению.
-
Что такое Pino и чем он отличается от других
Pino — это очень быстрая и легковесная библиотека для логирования вашего JavaScript приложения. Она быстро завоевала популярность среди разработчиков и на данный момент имеет 15 тысяч звёзд в GitHub и 10 млн загрузок в NPM еженедельно. Выбор в пользу Pino делают за счет его скорости и гибкости — он, как минимум, в 2 раза быстрее ближайших к нему аналогов.
В разработке библиотеки принимал участие небезызвестный в Node.js‑комьюнити Маттео Коллина, который по совместительству является создателем фреймворка Fastify, библиотеки fast‑json‑stringify и других полезных инструментов.
Pino специально создавался с упором на производительность. Главная идея заключалась в том, чтобы сделать библиотеку, которая была бы максимально простой и быстрой (чтобы избежать блокировок нашего любимого Event Loop), и в то же время имела возможность расширения под нужды разработчиков.
Так и вышло — Pino получился лёгким и очень быстрым (почему, мы разберём далее) и, в то же время, сохранил возможность расширения благодаря системе подключаемых плагинов.
Ключевые особенности Pino:
-
Асинхронное логирование
-
Многопоточность
-
Один формат данных по умолчанию — ndjson
-
Child Loggers для контекстного логирования
-
Расширяемость и гибкость настройки
Основной функционал Pino
Pino — это не просто логгер, а инструмент с продуманной архитектурой, который сочетает в себе скорость, гибкость и модульность. В этом разделе мы разберем его ключевые возможности, начиная с базовой настройки и заканчивая безопасностью при логировании. Для примеров кода потребуется Node.js и пакетный менеджер npm — убедитесь, что они установлены на вашей машине.
1. Базовая конфигурация и способы вывода логов
Для начала установим Pino через npm.
npm install pino
Теперь создадим файл index.mjs
и настроим простейший логгер:
import pino from "pino";
const logger = pino();
logger.trace("this is trace log");
logger.debug("this is debug log");
logger.info("this is info log");
logger.warn("this is warn log");
logger.error("this is error log");
logger.fatal("this is error log");
Запустите скрипт командой node index.mjs, и в терминале вы увидите что‑то вроде:
{"level":30,"time":1742499404502,"pid":17948,"hostname":"MacBook-Pro-Dmitry.local","msg":"this is info log"}
{"level":40,"time":1742499404502,"pid":17948,"hostname":"MacBook-Pro-Dmitry.local","msg":"this is warn log"}
{"level":50,"time":1742499404502,"pid":17948,"hostname":"MacBook-Pro-Dmitry.local","msg":"this is error log"}
{"level":60,"time":1742499404502,"pid":17948,"hostname":"MacBook-Pro-Dmitry.local","msg":"this is error log"}
Разберем, что здесь происходит. Логи выводятся в формате NDJSON — это «потоковый» JSON, где каждая строка представляет собой самостоятельный JSON‑объект, разделенный переносом строки (n). Такой формат идеален для систем мониторинга вроде ELK, Loki или Datadog, так как позволяет обрабатывать логи построчно без необходимости держать весь файл в памяти.
По умолчанию Pino добавляет к каждому логу следующие поля:
-
level
— уровень логирования (число, соответствующее серьезности). -
time
— временная метка в формате Unix timestamp (в миллисекундах). -
pid
— ID запущенного процесса в операционной системе. -
hostname
— имя хоста, на котором запущен скрипт. -
msg
— само сообщение, которое вы передали в логгер.
Заметили, что trace
и debug
не появились в выводе? Это не баг, а особенность: по умолчанию Pino настроен на уровень info
(30), и все логи с меньшим значением (например, trace
— 10, debug
— 20) игнорируются. Давайте разберем уровни логирования подробнее.
2. Уровни логирования
Уровни логирования в Pino — это стандартный подход к классификации логов по их важности. Каждый уровень имеет числовое значение, и чем выше число, тем серьезнее сообщение. При этом настройка минимального уровня автоматически включает все более высокие уровни. Вот полный список уровней в Pino:
-
trace (10)
— детальная трассировка, полезная для отладки -
debug (20)
— отладочная информация, чуть менее подробная. -
info (30)
— общая информация о работе приложения (по умолчанию). -
warn (40)
— предупреждения о потенциальных проблемах. -
error (50)
— ошибки, требующие внимания. -
fatal (60)
— критические сбои, после которых приложение обычно завершает работу.
Чтобы изменить уровень логирования, передайте опцию level
при создании логера. Например, включим debug
и выше:
const logger = pino({
level: "debug",
});
Вывод в терминале будет начинаться с уровня debug
(20):
{"level":20,"time":1742490021840,"pid":86694,"hostname":"MacBook-Pro-Dmitry.local","msg":"This is a debug message"}
{"level":30,"time":1742490021840,"pid":86694,"hostname":"MacBook-Pro-Dmitry.local","msg":"Hello, World!"}
{"level":40,"time":1742490021840,"pid":86694,"hostname":"MacBook-Pro-Dmitry.local","msg":"This is a warning message"}
{"level":50,"time":1742490021840,"pid":86694,"hostname":"MacBook-Pro-Dmitry.local","msg":"This is an error message"}
{"level":60,"time":1742490021840,"pid":86694,"hostname":"MacBook-Pro-Dmitry.local","msg":"This is a fatal message"}
Причём мы также можем создавать и кастомные уровни логирования, управлять порядком возрастания/убывания уровней, гибко настраивать форматирование через formatters
, либо полностью запретить использовать стандартные уровни логирования:
import { log } from "console";
import pino from "pino";
const logger = pino({
level: "fix",
customLevels: {
alert: 35, // устанавливаем кастомный уровень логирования
fix: 36,
},
formatters: {
// также можем использовать форматирование для вывода названия уровня вместо числа
level(label, number) {
// label - название уровня, number - числовое значение уровня
return { level: label };
},
},
levelComparison: "DESC", // можем настроить сортировку уровней логирования по убыванию
useOnlyCustomLevels: true, // используем только кастомные уровни логирования, стандартые будут выкидывать ошибку
});
logger.alert("This is an alert message");
logger.fix("This is a fix message");
logger.debug("This is a debug message"); // будет ошибка, ибо useOnlyCustomLevels: true
Вывод в консоль:
{"level":"alert","time":1742492192609,"pid":91963,"hostname":"MacBook-Pro-Dmitry.local","msg":"This is an alert message"}
{"level":"fix","time":1742492192610,"pid":91963,"hostname":"MacBook-Pro-Dmitry.local","msg":"This is a fix message"}
Гибкость уровней позволяет настроить Pino под разные сценарии: в разработке можно включить trace для максимальной детализации, а в продакшене оставить info или warn, чтобы не захламлять логи, либо вообще использовать кастомные уровни логирования.
3. Асинхронное логирование: как это работает
Одна из главных причин производительности Pino — его асинхронная архитектура. В отличие от многих логеров, которые пишут данные синхронно и блокируют event loop, Pino обрабатывает логи асинхронно, минимизируя влияние на основной поток приложения.
В Node.js всё крутится вокруг event loop — механизма, который обрабатывает асинхронные задачи в одном потоке. Если основной поток блокируется, например, синхронной записью логов, приложение «замирает», не реагируя на запросы. Pino решает это, передавая логи в асинхронные стримы, чтобы event loop оставался свободным.

По умолчанию Pino использует встроенный модуль sonic‑boom — оптимизированную библиотеку для работы с потоками, созданную специально для Pino. Sonic‑boom заменяет стандартные стримы Node.js (например, fs.createWriteStream
), добавляя буферизацию и низкоуровневые улучшения производительности.
Этапы асинхронного логирования
-
Сериализация лога. Когда вы вызываете
logger.info()
, Pino преобразует ваше сообщение и метаданные в строку NDJSON. Это делается быстро благодаря оптимизированной сериализации (в том числе с использованием fast‑json‑stringify, если настроено). На этом этапе лог еще не записан — он только подготовлен. -
Передача в буфер. Подготовленная строка отправляется в буфер sonic‑boom. Буфер — это временное хранилище в памяти, которое накапливает данные перед записью. Вместо того чтобы сразу писать каждую строку в файл (что было бы медленно из‑за операций ввода‑вывода), Pino собирает логи в буфере, пока тот не заполнится или не сработает условие сброса.
-
Асинхронная запись. Когда буфер заполняется или достигается таймаут (если настроен
periodicFlush
), sonic‑boom вызывает системный вызов write для записи данных в destination (например, файл или stdout). Эта операция выполняется асинхронно, через event loop Node.js, что не блокирует основной поток. -
Обработка backpressure. Если destination не успевает принимать данные (например, из‑за медленного диска), возникает так называемый backpressure. Sonic‑boom управляет этим, приостанавливая накопление в буфере и сигнализируя Pino, чтобы тот временно замедлил генерацию логов. Как только destination освобождается, запись возобновляется.
Sonic‑boom — это не просто обертка над стримами, а инструмент с тонкой настройкой:
При синхронном логировании, из‑за того, что операционная система/диск могут не успеть выгрузить логи в нужный destination, буфер файлового дескриптора окажется переполнен (здесь важно, что переполнится именно файловый дескриптор, предоставленный ОС) — операционная система выбросит ошибку EAGAIN и наш stream будет ждать и повторно пытаться записать логи в дескриптор, пока буфер не освободится, тем самым блокируя Event Loop.



Sonic‑boom позволяет не блокироваться и при этом продолжать накапливать логи в буфере самого sonic‑boom, тем самым не блокируя Event Loop.

Правда, здесь есть одно но. Если буфер файлового дескриптора продолжит также постоянно забиваться, то буфер sonic‑boom продолжит расти в размерах — в итоге мы можем получить переполнение памяти и завершение приложения с ошибкой. Для контроля над этим процессом существуют параметры RetryEAGAIN
и maxLength
у pino.destination()
, которые мы разберём далее.
Также асинхронность появляется и при работе с pino.transport()
, который под капотом использует worker_threads и выносит работу с логами в отдельные потоки, но об этом позже.
4. Destination: куда отправляются логи
В основе Pino лежит понятие destination — это конечная точка, куда отправляются ваши логи. По умолчанию это process.stdout
(консоль), но вы можете перенаправить их в файл, сокет или любой другой поток записи. Destinations отвечают за физическую запись логов, и их настройка — первый шаг к гибкости. Рассмотрим на примере:
const logger = pino({
destination: './app.log'
});
logger.info('log into file');
Pino автоматически создаст файл app.log из корня рабочей директории, если его нет, и начнет писать логи в формате NDJSON. Но что, если нужно больше контроля? Для этого pino.destination
принимает объект с параметрами.
Параметры pino.destination
Когда вы передаете объект вместо строки, доступны следующие опции:
-
fd (file descriptor) — дескриптор файла, если вы хотите использовать уже открытый поток. Это уже низкоуровневая работа с библиотекой, пример разберём дальше.
-
dest — строка с путем к файлу (аналогично простому варианту).
-
sync — булево значение, включает или отключает синхронную запись (по умолчанию false).
-
append — булево значение, определяет, добавлять ли данные в конец файла или перезаписывать его (по умолчанию true).
-
mkdir — булево значение, создает ли директорию, если она не существует (по умолчанию false).
-
minLength — минимальная длина буфера перед сбросом. Если данные меньше этого значения, они могут задерживаться в буфере до накопления или таймаута.
-
maxLength — максимальная длина буфера в байтах. Если буфер достигает этого размера, данные сбрасываются в destination. Помогает контролировать объем памяти.
-
maxWrite — максимальный размер одной операции записи в байтах. Ограничивает объем данных, записываемых за раз, для оптимизации производительности на конкретных системах.
-
periodicFlush — интервал в миллисекундах для периодического сброса буфера на диск, даже если он не заполнен. Полезно для потоков с низкой интенсивностью записи.
-
RetryEAGAIN — определяет поведение при ошибке EAGAIN (ресурс временно недоступен).
-
true: повторяет попытку записи автоматически.
-
number: количество попыток повтора.
-
false: не повторяет, возвращает ошибку.
-
Пример с настройками:
const logger = pino({
destination: pino.destination({
dest: './logs/app.log',
sync: false, // Асинхронная запись
append: true, // Дописывать в конец
mkdir: true // Создать папку logs, если её нет
})
});
logger.info('log with parameters');
В этом случае Pino:
-
Создаст директорию logs, если она отсутствует.
-
Откроет файл app.log в режиме добавления (append).
-
Будет писать логи асинхронно, не блокируя event loop.
Использование file descriptor
Если у вас уже есть открытый файл (например, через fs.open), можно передать его дескриптор в качестве параметра:
import fs from "node:fs/promises";
import pino from "pino";
const file = await fs.open("./custom.log", "w");
const logger = pino(
pino.destination({
fd: file.fd,
})
);
logger.info("Write through file descriptor");
await file.close();
Это полезно, если вы работаете с файлами в рамках сложной логики и хотите интегрировать Pino с существующим потоком.
Потоки вместо путей
Pino вместо параметра destination
также может принимать любой объект, реализующий интерфейс Writable Stream из Node.js. Учитывая то, что мы разбирали выше, при такой конфигурации мы не будем иметь промежуточный буфер, который предоставлял нам sonic‑boom, следовательно такая конфигурация может заблокировать event loop:
import fs from "node:fs";
import pino from "pino";
const logger = pino(fs.createWriteStream("./app.log"));
logger.info("Hello, World!");
5. Transport и многопоточность: как обрабатываются логи
Если destination в Pino определяет, куда отправляются логи, то transport отвечает за то, как они обрабатываются перед записью. Это модульный механизм, который позволяет преобразовывать, форматировать или перенаправлять логи, сохраняя ядро Pino легким и быстрым. Transports — это своего рода «посредники», которые берут сырые данные в формате NDJSON и делают с ними всё, что вам нужно: от красивого вывода в консоль до отправки в облачные сервисы.
Как это работает?
Transports в Pino реализованы как отдельные модули, которые подключаются к логеру через опцию transport. Они работают в связке с destinations, но их задача — не просто запись, а обработка логов. Например, вы можете:
-
Преобразовать NDJSON в человекочитаемый формат.
-
Добавить метаданные или фильтры.
-
Отправить логи в внешние системы вроде Loki, Elasticsearch или Datadog.
Transports выносят обработку логов за пределы основного ядра Pino. Вместо того чтобы форматировать или отправлять данные в основном потоке, они:
-
Работают асинхронно и в отдельных потоках с помощью
worker_threads
. До версии v7 была возможность вынести транспорт только в отдельный процесс, используя UNIX pipes, либо смириться с замедлением event loop, ибо в главном потоке создавался единый pipeline для всех транспортов. Некоторые сторонние транспорты так и не перешли на новую версию, поэтому об этом стоит знать — больше информации в Legacy Transports. -
Используют буферизацию destination для минимизации операций ввода‑вывода.
-
Делегируют сложные задачи (например, HTTP‑запросы) внешним модулям.
Примеры
Рассмотрим транспорты на примере. Используем встроенный в pino транспорт pino/file
:
const logger = pino({
transport: {
target: "pino/file",
options: {
destination: "./app.log",
},
},
});
Обработка логов будет происходить в отдельном потоке, который Pino любезно создал для нас под капотом, используя библиотеку thread‑stream, также созданную специально для логера.
Множественные транспорты
Также Pino поддерживает множественные транспорты.
// транспорт можно создавать и через pino.transport()
// и затем использовать в pino,
// полезно чтобы создать один транспорт и использовать его в разных логгерах,
// либо настроить таргеты в зависимости от конфигов
const transport = pino.transport({
targets: [
{
target: "pino/file",
options: {
destination: "./error.log",
},
level: "error",
},
{
target: "pino/file",
level: "info",
options: { destination: "./info.log" },
},
{ target: "pino/file", level: "debug", destination: 1 }, // в stdout
],
});
const logger = pino(transport);
logger.error("info message");
Схематично работа транспортов представлена на следующей картинке:

Внешние транспорты
Также мы можем использовать и внешние транспорты, представленные в npm, например, pino‑pretty, который позволяет гибко форматировать логи перед записью. Причём параметров правда много, настроить логи можно на любой вкус, логируйте хоть смайликами, если хотите .
const transport = pino.transport({
targets: [
{
target: "pino-pretty",
level: "info",
destination: 1,
options: { colorize: true, singleLine: true },
},
{ target: "pino/file", level: "debug", destination: 1 }, // в stdout
],
});
const logger = pino(transport);
logger.error("info message");
В консоли увидим оба сообщения:
{"level":50,"time":1742541917351,"pid":67375,"hostname":"MacBook-Pro-Dmitry.local","msg":"info message"}
[10:25:17.351] ERROR (67375): info message
Причем с форматированием здесь может быть одна проблема, с которой я лично сталкивался. Из‑за того, что транспорты направляют стримы логов в worker_threads
, мы не можем использовать функции в параметрах транспорта (ибо не можем распарсить функцию в строку), а они иногда нам могут понадобится, например, для добавления тех же смайликов в логи pino‑pretty. Чтобы обойти эту ситуацию, мы должны обернуть существующий транспорт и вынести его в отдельный файл:
// ./pino-pretty-transport.mjs
import { PinoPretty } from "pino-pretty";
export default function pinoPrettyTransport(opts) {
return PinoPretty({ // возвращаем stream pino-pretty
...opts,
customPrettifiers: {
time: (timestamp) => `
${timestamp}`,
level: (logLevel, key, log, { labelColorized }) => {
return `
LEVEL: ${logLevel}
LABEL COLORIZED: ${labelColorized}`;
},
},
});
}
Затем мы можем использовать эту обертку в качестве транспорта:
// index.mjs
const transport = pino.transport({
targets: [
{
target: "./pino-pretty-transport.mjs", // используем нашу обертку
level: "info",
destination: 1,
options: { colorize: true, singleLine: true },
},
{ target: "pino/file", level: "debug", destination: 1 }, // в stdout
],
});
В консоли увидим следующий вывод:
10:42:02.483
LEVEL: 50
LABEL COLORIZED: ERROR (73874): info message
Также существует множество других внешних транспортов, позволяющих отсылать информацию в ваши сервисы по работе с логами: pino‑http, pino‑loki, pino‑elasticsearch и другие. Всех их можно увидеть здесь.
Pipeline для транспортов
Pino поддерживает последовательную обработку логов через transport.pipeline
для их трансформации перед записью. Каждый промежуточный этап должен быть обязательно реализован через Transform Stream (пример реализации собственного pipeline‑transport из документации):
const logger = pino({
transport: {
targets: [
{ target: 'pino/file', options: { destination: 1 } }, // Target #1: Параллельно
{
pipeline: [
{ target: './transform1.js' }, // Target #2: Transform
{ target: './transform2.js' }, // Target #3: Transform
{ target: 'pino/file', options: { destination: './final.log' } } // Target #4: Writable
]
}
]
}
});
logger.info('hello world');
Также можно увидеть работу pipeline на схеме:

Написание собственных транспортов
Pino поддерживает написание собственных транспортов. Можно написать плагин с транспортом и выгрузить его в npm, чтобы переиспользовать в своих приложениях. Мы не будем рассматривать создание собственного транспорта, так как для этого есть документация, где есть подробное описание того, как можно создавать разные транспорты под свои нужды.
6. Multistream вместо потоков: особенности работы
Также в Pino есть механизм multistream — по сути это то, как работали транспорты до появления worker_threads в 7-й версии.
pino.multistream
позволяет отправлять логи в несколько потоков параллельно, но важно понимать, как это влияет на производительность и основной поток приложения. Хотя multistream обеспечивает асинхронность при записи логов, его работа отличается от транспортов, которые выносят обработку в worker threads.
pino.multistream
распределяет логи параллельно по нескольким потокам (Writable Streams), но без транспортов вся обработка происходит в основном потоке через event loop. Запись выполняется асинхронно аналогично Promise.all()
, однако синхронные задачи, вроде форматирования, могут замедлить event loop, а переполнение буфера — вызвать backpressure и блокировки.
Пример:
const fileStream = fs.createWriteStream('./debug.log', { flags: 'a' });
const pinoDest = pino.destination('./trace.log');
const stdoutDest = pino.destination(1); // stdout
const logger = pino(
{},
pino.multistream([ // передаём вторым параметром
{ stream: pinoDest, level: 'trace' }, // Файл trace.log
{ stream: fileStream, level: 'debug' }, // Файл debug.log
{ stream: stdoutDest, level: 'info' } // stdout
])
);
И также можем рассмотреть схему работы не следующем рисунке:

7. Child Loggers для контекстного логирования
Child loggers в Pino позволяют создавать дочерние логеры, которые наследуют настройки родительского, но добавляют собственный контекст. Это удобно для модульных приложений, где нужно добавлять метаданные (например, имя модуля или ID запроса) к логам, не создавая новый логер с нуля. Дочерний логер сохраняет производительность, так как использует тот же поток записи, что и родительский.
Создать child logger можно с помощью метода child()
. Например, в приложении с несколькими модулями можно добавить контекст для каждого модуля. Все логи от дочернего логера будут включать эти дополнительные поля, а уровни логирования и destinations останутся такими же, как у родителя, если не указать иное.
const logger = pino({ level: 'info' });
const moduleLogger = logger.child({ module: 'auth' });
logger.info('Общий лог'); // {"level":30,"time":...,"msg":"Общий лог"}
moduleLogger.info('Лог модуля') // {"level":30,"time":...,"module":"auth","msg":"Лог модуля"}
Причем оверхед логирования через дочерний логер будет мизерным, если сравнивать с аналогами pino.
8. Redact: маскировка чувствительных данных
Функция redact в Pino(которая реализована благодаря библиотеке fast‑redact) позволяет маскировать или удалять чувствительные данные из логов, такие как пароли, токены или персональная информация. Это важно для соблюдения требований безопасности (например, GDPR) и предотвращения утечек. Вы указываете пути к полям, которые нужно скрыть, а Pino заменяет их на заданное значение (по умолчанию [Redacted]
).
Настройка redact задаётся в опциях логгера через массив путей (в формате a.b.c
) или объект с путём и значением для замены. Маскировка происходит на этапе сериализации, до отправки логов в потоки, что минимизирует риск утечки данных.
Пример:
const logger = pino({
level: 'info',
redact: ['password', 'user.email'] // Маскируем поля password и user.email
});
logger.info({
password: 'secret123',
user: { email: 'user@example.com', name: 'Alice' },
message: 'Данные пользователя'
});
// Вывод: {"level":30,"time":...,"password":"[Redacted]","user":{"email":"[Redacted]","name":"Alice"},"msg":"Данные пользователя"}
Заключение
Мы подробно разобрали внутренние механизмы Pino, сосредоточившись на низкоуровневых аспектах: от эволюции транспортов (legacy и v7+) до обработки с multistream
и последовательных цепочек с pipeline
. Рассмотрели, как child loggers добавляют контекст, как redact
защищает данные, и какие подводные камни могут возникнуть при использовании прямых потоков в multistream
. Моя цель была помочь вам понять принципы работы библиотеки и справиться с потенциальными сложностями, такими как выбор подходящего подхода или настройка производительности. Реальные примеры применения Pino (например, в веб‑приложениях) уже хорошо описаны в других статьях, поэтому я углубился в фундаментальные детали.
Pino — это не просто быстрый логер, но и инструмент, который даёт разработчикам гибкость и контроль над логированием. Надеюсь, что этот разбор вдохновит вас экспериментировать с Pino, находить оптимальные решения для ваших задач и получать удовольствие от процесса.
Использованные материалы
-
https://getpino.io/#/ — документация Pino.
-
https://gitnation.com/contents/multithreaded‑logging‑with‑pino.
-
https://www.nearform.com/insights/pino‑the‑fastest‑node‑js‑logger‑for‑production/ — статья про Pino от создателей.
-
https://www.nearform.com/insights/pino7–0–0-pino‑transport‑worker‑thread‑transport/ — про pino v7.
-
https://youtu.be/vETUVN‑KEgc. — интервью с Маттео Коллина про многопоточную обработку в Pino.
Автор: fozery