Pino Logger
Introduction
Section titled “Introduction”Xeno.JS provides a Pino integration through its application logger.
You do not replace the Xeno.JS logger with a Pino-specific API. Instead, you enable Pino as a logging provider and continue to use the Xeno.JS ILogger abstraction.
The basic configuration is:
import { AppBuilder } from '@xeno-js/core'
const app = new AppBuilder()
app.addLogger((options) => { options.pino.config = { destination: 'stdout', }})
const container = await app.build()After the application is built, resolve TOKENS.LOGGER and use the returned logger.
Prerequisites
Section titled “Prerequisites”Install Xeno.JS and Pino:
npm install @xeno-js/core @xeno-js/shared pinopino is an optional peer dependency of @xeno-js/core.
If you enable pretty printing, Xeno.JS configures Pino to use the pino-pretty transport. Install it as well:
npm install pino-prettyYou only need pino-pretty when pretty printing is enabled.
Enable Pino
Section titled “Enable Pino”Pino is enabled when options.pino.config is defined.
For example:
import { AppBuilder } from '@xeno-js/core'
const app = new AppBuilder()
app.addLogger((options) => { options.pino.config = {}})
const container = await app.build()The empty configuration uses the Xeno.JS defaults.
Pino is not enabled by merely installing the pino package. It becomes a Xeno.JS logging provider when pino.config is configured.
Configure the Log Level
Section titled “Configure the Log Level”The minimum log level is configured through the Xeno.JS logger configuration:
import { AppBuilder } from '@xeno-js/core'import { LOG_LEVEL } from '@xeno-js/shared'
const app = new AppBuilder()
app.addLogger((options) => { options.level = LOG_LEVEL.INFO
options.pino.config = { destination: 'stdout', }})Xeno.JS supports:
LOG_LEVEL.DEBUGLOG_LEVEL.INFOLOG_LEVEL.WARNLOG_LEVEL.ERROR
The configured level is the minimum level accepted by the Xeno.JS logger.
For example:
options.level = LOG_LEVEL.WARNallows:
WARNERRORand filters:
DEBUGINFOThe Pino adapter applies the same minimum level before calling the corresponding Pino method.
Configure the Destination
Section titled “Configure the Destination”Xeno.JS supports two Pino destinations:
type Destination = 'stdout' | 'file'Write to stdout
Section titled “Write to stdout”This is the default:
app.addLogger((options) => { options.pino.config = { destination: 'stdout', }})If destination is omitted, Xeno.JS uses stdout.
Write to a file
Section titled “Write to a file”Configure:
app.addLogger((options) => { options.pino.config = { destination: 'file', filePath: 'logs/app.log', }})If destination is file and filePath is omitted, Xeno.JS uses:
logs/app.logThe file path is passed to Pino’s destination stream.
Make sure the application process has permission to create and write to the configured path.
Configure the Environment
Section titled “Configure the Environment”Pino has an optional env configuration:
app.addLogger((options) => { options.pino.config = { env: 'production', destination: 'stdout', }})If env is not specified, Xeno.JS uses:
process.env.NODE_ENV, when defined;- otherwise
development.
The environment affects the default value of prettyPrint.
Pretty Printing
Section titled “Pretty Printing”Xeno.JS enables pretty printing by default when the configured environment is development.
For example:
app.addLogger((options) => { options.pino.config = { env: 'development', }})is equivalent to using:
app.addLogger((options) => { options.pino.config = { env: 'development', prettyPrint: true, }})To explicitly disable it:
app.addLogger((options) => { options.pino.config = { env: 'development', prettyPrint: false, }})To explicitly enable it:
app.addLogger((options) => { options.pino.config = { env: 'production', prettyPrint: true, }})When pretty printing is enabled, Xeno.JS configures the Pino pino-pretty transport with:
- colorized output;
- standard translated timestamps;
pidandhostnameomitted from the pretty output.
If you enable pretty printing, make sure pino-pretty is installed.
Resolve and Use the Logger
Section titled “Resolve and Use the Logger”Pino is not exposed as a separate DI service token.
Xeno.JS registers the application logger under TOKENS.LOGGER.
Resolve it after building the application:
import { AppBuilder } from '@xeno-js/core'import { TOKENS } from '@xeno-js/shared'
const app = new AppBuilder()
app.addLogger((options) => { options.pino.config = { destination: 'stdout', }})
const container = await app.build()
const logger = container.resolve(TOKENS.LOGGER)The returned value implements the Xeno.JS ILogger interface.
Use the normal Xeno.JS logging methods:
logger.debug('Loading configuration')logger.info('Application started')logger.warn('Cache entry was not available')logger.error('Unable to process request', error)You do not need to call Pino’s debug(), info(), warn(), or error() methods directly in application code.
Xeno.JS forwards each accepted log event to the configured Pino provider.
Use Pino Together With Other Providers
Section titled “Use Pino Together With Other Providers”Pino does not have to be the only provider.
For example, you can enable Console and Pino together:
import { AppBuilder } from '@xeno-js/core'
const app = new AppBuilder()
app.addLogger((options) => { options.console = true
options.pino.config = { destination: 'stdout', }})You can also configure Pino and Sentry:
app.addLogger((options) => { options.console = false
options.pino.config = { destination: 'stdout', }
options.sentry.config = { dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, }})The Xeno.JS logger sends accepted events to all configured logger clients.
This allows the same application logging API to feed different observability systems.
Structured Pino Output
Section titled “Structured Pino Output”Xeno.JS passes the log context to Pino as structured data.
The Pino adapter maps Xeno.JS levels to Pino methods:
| Xeno.JS level | Pino method |
|---|---|
DEBUG |
debug() |
INFO |
info() |
WARN |
warn() |
ERROR |
error() |
For example, an informational event is passed to Pino conceptually as:
pinoLogger.info( { ...context, }, message,)An error event includes the error in the structured payload:
pinoLogger.error( { ...context, error, }, message,)Pino is configured by Xeno.JS with ISO timestamps and its standard error serializer.
A structured log therefore contains information such as:
{ "level": 30, "time": "2026-10-03T16:00:00.000Z", "msg": "[INFO] Application started", "requestId": "...", "correlationId": "..."}The exact JSON representation is produced by Pino.
Safe Context
Section titled “Safe Context”Xeno.JS does not pass the complete request context directly to Pino.
The Xeno.JS logger builds a safe logging context before sending it to logger clients.
Depending on the active request context, this can contain information such as:
identity├── userId└── tenantId
network├── requestId├── path├── origin└── masked clientIp
tracing├── correlationId├── spanId├── startTime└── parentSpanIdThe purpose is to provide useful correlation and diagnostic information without serializing the complete request context.
For example, request-specific information such as authentication state, tracing identifiers, and the request path can be available to Pino without automatically copying arbitrary request data.
The client IP is masked before being included in the safe logging context.
Application Data Is Still Your Responsibility
Section titled “Application Data Is Still Your Responsibility”Safe context does not make arbitrary application logging safe.
If application code explicitly logs:
logger.info(`Password: ${password}`)the password is part of the message.
Likewise, application-provided structured data or error objects can contain sensitive information.
Avoid logging:
- passwords;
- authentication tokens;
- secrets;
- credentials;
- cookies;
- authorization headers;
- complete request bodies;
- other sensitive application data.
Pino Redaction
Section titled “Pino Redaction”Xeno.JS configures Pino redaction for the following paths:
passwordtokensecretauthorizationheaders.authorizationThese values are censored as:
***For example, a payload containing:
{ token: 'secret-token', userId: 'user-123',}is redacted by Pino before output.
Redaction is an additional protection layer. It does not replace careful logging practices.
Do not deliberately include sensitive information in log messages simply because Pino has redaction configured.
Error Logging
Section titled “Error Logging”Use the Xeno.JS error API:
try { await processOrder()} catch (error) { logger.error('Unable to process order', error)}Xeno.JS passes the error to the Pino adapter.
The adapter adds the error to the structured payload and calls Pino’s error() method.
Pino is configured with its standard error serializer, so error information is serialized using Pino’s error serialization behavior.
Complete Example
Section titled “Complete Example”A complete application configuration can look like this:
import { AppBuilder } from '@xeno-js/core'import { LOG_LEVEL, TOKENS } from '@xeno-js/shared'
const app = new AppBuilder()
app.addLogger((options, config) => { options.level = LOG_LEVEL.INFO
options.console = false
options.pino.config = { destination: 'file', filePath: config.get('LOG_FILE_PATH', 'logs/app.log'), env: 'production', prettyPrint: false, }})
const container = await app.build()
const logger = container.resolve(TOKENS.LOGGER)
logger.info('Application started')
logger.warn('Example warning')
try { throw new Error('Example failure')} catch (error) { logger.error('Application operation failed', error)}This configuration:
- filters out
DEBUG; - accepts
INFO,WARN, andERROR; - disables Console;
- writes Pino logs to
logs/app.log; - uses structured JSON output;
- uses the Xeno.JS safe context;
- applies Pino redaction;
- exposes the logger through
TOKENS.LOGGER.
Development Configuration
Section titled “Development Configuration”For local development, pretty printing can make Pino output easier to read:
import { AppBuilder } from '@xeno-js/core'import { LOG_LEVEL } from '@xeno-js/shared'
const app = new AppBuilder()
app.addLogger((options) => { options.level = LOG_LEVEL.DEBUG
options.pino.config = { env: 'development', destination: 'stdout', prettyPrint: true, }})
await app.build()Make sure pino-pretty is installed when using this configuration.
Production Configuration
Section titled “Production Configuration”For structured logs, disable pretty printing:
import { AppBuilder } from '@xeno-js/core'import { LOG_LEVEL } from '@xeno-js/shared'
const app = new AppBuilder()
app.addLogger((options) => { options.level = LOG_LEVEL.INFO
options.pino.config = { env: 'production', destination: 'stdout', prettyPrint: false, }})
await app.build()This keeps the output in Pino’s structured format, which can then be consumed by the application’s log collection infrastructure.
Common Problems
Section titled “Common Problems”Pino Does Not Produce Any Logs
Section titled “Pino Does Not Produce Any Logs”Make sure pino.config is defined:
app.addLogger((options) => { options.pino.config = {}})Installing Pino alone does not enable the Xeno.JS Pino provider.
Also check the configured minimum level:
options.level = LOG_LEVEL.INFOA DEBUG event will not be emitted with that configuration.
Pino Cannot Be Imported
Section titled “Pino Cannot Be Imported”Make sure the provider dependency is installed:
npm install pinopino is an optional peer dependency of @xeno-js/core.
Pretty Printing Fails
Section titled “Pretty Printing Fails”If prettyPrint is enabled, install:
npm install pino-prettyXeno.JS configures the Pino transport with pino-pretty when pretty printing is enabled.
Logs Are Not Written to the Expected File
Section titled “Logs Are Not Written to the Expected File”Check:
options.pino.config = { destination: 'file', filePath: 'logs/app.log',}If filePath is omitted, Xeno.JS uses:
logs/app.logAlso make sure the Node.js process has permission to write to the selected directory.
I Cannot Resolve a Pino Logger Directly
Section titled “I Cannot Resolve a Pino Logger Directly”This is expected.
Xeno.JS registers the application ILogger, not the Pino instance, under the DI token:
TOKENS.LOGGERResolve:
const logger = container.resolve(TOKENS.LOGGER)and use the Xeno.JS logging API.
Pino remains an implementation behind the Xeno.JS logger abstraction.
Related Docs
Section titled “Related Docs”Support Us
Section titled “Support Us”Xeno.JS is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you’d like to join them, please read more at support section
