How to Configure Logging in Your Watt Application
Problem
You need to customize logging behavior in your Watt application for different environments (development, staging, production) or integrate with external logging systems
Solution Overview
Watt uses Pino for high-performance logging with extensive configuration options. You can:
- Set consistent logging across all applications via Watt configuration
- Override logging for specific applications
- Integrate with external systems (Elasticsearch, files, etc.)
- Redact sensitive information from logs
The default configuration uses level: info with pretty-printed output in development.
Quick Solutions by Use Case
Need to change log level? → Set Log Level Need to log to files? → File Logging Need to hide sensitive data? → Redact Sensitive Information Need structured production logs? → Production Logging Need OpenTelemetry integration? → External System Integration or OpenTelemetry Logging Guide
Set Log Level
Problem: You need different amounts of logging detail in different environments.
Solution: Configure the level property in your watt.json:
{
"logger": {
"level": "debug"
}
}
Available levels (most to least verbose):
trace- Very detailed debugging informationdebug- Debugging informationinfo- General information (default)warn- Warning messageserror- Error messages onlyfatal- Fatal errors onlysilent- No logging
Environment-specific example:
{
"logger": {
"level": "{LOG_LEVEL}"
}
}
Set LOG_LEVEL=error in production, LOG_LEVEL=debug in development.
File Logging
Problem: You need to persist logs to files for auditing or analysis.
Solution: Configure a file transport in your watt.json:
{
"logger": {
"transport": {
"targets": [
{
"target": "pino/file",
"options": {
"destination": "{LOG_DIR}/app.log",
"mkdir": true
}
}
]
}
}
}
Multiple destinations example:
{
"logger": {
"transport": {
"targets": [
{
"target": "pino-pretty",
"level": "info",
"options": {
"colorize": true
}
},
{
"target": "pino/file",
"level": "error",
"options": {
"destination": "{LOG_DIR}/errors.log",
"mkdir": true
}
}
]
}
}
}
This logs all messages to console with pretty formatting, and errors to a file.
External System Integration
Problem: You need to send logs to Elasticsearch, Splunk, OpenTelemetry collectors, or other logging systems.
Solution: Use specialized transport targets:
OpenTelemetry (Recommended for Observability):
{
"logger": {
"openTelemetryExporter": {
"protocol": "http",
"url": "http://localhost:4318/v1/logs"
}
},
"telemetry": {
"enabled": true,
"applicationName": "my-app",
"version": "1.0.0",
"exporter": {
"type": "otlp",
"options": {
"url": "http://otel-collector:4318/v1/traces"
}
}
}
}
This automatically:
- Exports logs to any OTLP-compatible backend
- Includes trace context (trace ID, span ID, flags)
- Adds service metadata for filtering
See the OpenTelemetry Logging Guide for detailed configuration.
Elasticsearch:
{
"logger": {
"transport": {
"targets": [
{
"target": "pino-elasticsearch",
"options": {
"node": "http://127.0.0.1:9200",
"index": "my-app-logs"
}
}
]
}
}
}
Install the transport: npm install pino-elasticsearch
Redact Sensitive Information
Problem: Your logs contain sensitive data (passwords, tokens, API keys) that shouldn't be stored.
Solution: Use the redact configuration to automatically hide sensitive fields:
{
"logger": {
"redact": {
"paths": ["req.headers.authorization", "password", "apiKey", "req.body.creditCard"],
"censor": "[REDACTED]"
}
}
}
Before redaction:
{
"level": 30,
"msg": "User login",
"password": "secret123",
"req": {
"headers": {
"authorization": "Bearer token123"
}
}
}
After redaction:
{
"level": 30,
"msg": "User login",
"password": "[REDACTED]",
"req": {
"headers": {
"authorization": "[REDACTED]"
}
}
}
Production Logging
Problem: You need structured, machine-readable logs for production monitoring.
Solution: Configure production-optimized logging:
{
"logger": {
"level": "info",
"timestamp": "isoTime",
"base": {
"application": "my-app",
"version": "1.2.0"
},
"redact": {
"paths": ["req.headers.authorization", "password"]
}
}
}
This provides:
-
ISO timestamp format for log aggregation
-
Application metadata for filtering
-
Automatic sensitive data redaction
-
base: The base object for the logs; it can be either be
nullto removepidandhostnameor a custom key/value object to add custom properties to the logs.{
"logger": {
"base": {
"application": "my-application",
"version": "1.0.0"
}
}
}
{
"logger": {
"base": null
}
}See the Pino base documentation for more details.
-
messageKey: The key to use for the log message, it defaults to
msgbut can be set to any other key.{
"logger": {
"messageKey": "message"
}
}See the Pino messageKey documentation for more details.
-
customLevels: Specify custom levels for the logger, it can be an object with the level name and the level value.
{
"logger": {
"customLevels": {
"verbose": 10
}
}
}See the Pino customLevels documentation for more details.
Note on using custom logger configuration
When using custom logger configuration that alterate the format of the output, such as messageKey, formatter.level, timestamp or customLevels, the log entry from a thread application is not recognized as a pino entry log entry, so it is treated as a json log entry.
For example, the difference between the default pino settings and a custom logger configuration that uses a custom messageKey is:
With default pino settings:
{
"level": 30,
"time": 1747988551789,
"pid": 29580,
"hostname": "work",
"name": "gateway",
"reqId": "c9f5d5b8-6ea5-4782-8c81-00ffb27386b3",
"res": { "statusCode": 500 },
"responseTime": 10.037883000448346,
"msg": "request completed"
}
With custom logger configuration, for example
{
"logger": {
"captureStdio": false,
"level": "info",
"customLevels": {
"verbose": 10
},
"base": null,
"messageKey": "message",
"timestamp": "isoTime",
"formatters": {
"path": "logger-formatters.js"
}
}
}
{
"severity": "INFO",
"time": "2025-05-23T08:20:51.464Z",
"name": "gateway",
"caller": "STDOUT",
"stdout": {
"severity": "INFO",
"time": "2025-05-23T08:20:51.464Z",
"name": "gateway",
"reqId": "420ab3ab-aa5f-42d4-9736-d941cfaaf514",
"res": {
"statusCode": 200
},
"responseTime": 10.712485999800265,
"message": "request completed"
}
}
To avoid the log entry to be wrapped in the stdout property, set the captureStdio option in wattpm to false (see Capture Thread Applications logs for more details); the result will be close to the default pino settings:
{
"severity": "INFO",
"time": "2025-05-23T08:21:49.813Z",
"name": "gateway",
"reqId": "4a8ad43d-f749-4993-a1f4-3055c55b23ba",
"res": {
"statusCode": 200
},
"responseTime": 11.091869999654591,
"message": "request completed"
}