Hackable console logger for Node.js applications with 17 out-of-the-box logger types and advanced features including integrated timers, scoped loggers, secrets filtering, and custom pluggable loggers.
—
Interactive mode, secrets filtering, stream management, and state control for advanced logging scenarios. These features provide sophisticated capabilities for production applications and complex logging requirements.
Enable, disable, and check the logging state of Signale instances.
/**
* Disable logging functionality for this instance
*/
function disable(): void;
/**
* Enable logging functionality for this instance
*/
function enable(): void;
/**
* Check if logging is currently enabled
* @returns true if logging is enabled, false if disabled
*/
function isEnabled(): boolean;Add and remove sensitive information filtering from log output.
/**
* Add secrets/sensitive information to be filtered from logs
* @param secrets - Array of strings or numbers to filter out
* @throws TypeError if secrets parameter is not an array
*/
function addSecrets(secrets: (string | number)[]): void;
/**
* Remove all secrets from the instance
*/
function clearSecrets(): void;Access current instance state and metadata.
/**
* Get current instance configuration and options
*/
readonly currentOptions: SignaleOptions;
/**
* Get current date string
*/
readonly date: string;
/**
* Get current timestamp string
*/
readonly timestamp: string;
/**
* Get the filename of the calling code
*/
readonly filename: string;
/**
* Get package.json configuration
*/
readonly packageConfiguration: object;
interface SignaleOptions {
config: ConfigOptions;
disabled: boolean;
types: Record<string, LoggerType>;
interactive: boolean;
timers: Map<string, number>;
stream: NodeJS.WritableStream | NodeJS.WritableStream[];
secrets: (string | number)[];
logLevel: LogLevel;
}Control logging functionality dynamically.
Usage Examples:
const signale = require('signale');
// Normal logging
signale.info('Logging is enabled');
// Output: ℹ info Logging is enabled
console.log(signale.isEnabled()); // true
// Disable logging
signale.disable();
console.log(signale.isEnabled()); // false
signale.success('This will not appear');
// No output
signale.error('This will not appear either');
// No output
// Re-enable logging
signale.enable();
console.log(signale.isEnabled()); // true
signale.success('Logging is back!');
// Output: ✔ success Logging is back!Filter sensitive information from log messages and metadata.
Usage Examples:
const { Signale } = require('signale');
// Initialize with secrets
const logger = new Signale({
secrets: ['password123', 'secret-api-key']
});
logger.info('User login with password123');
// Output: ℹ info User login with [secure]
logger.warn('API key secret-api-key is expiring');
// Output: ⚠ warning API key [secure] is expiring
// Add more secrets at runtime
logger.addSecrets(['session-token-abc', 12345]);
logger.error('Session session-token-abc invalid for user 12345');
// Output: ✖ error Session [secure] invalid for user [secure]
// Clear all secrets
logger.clearSecrets();
logger.info('Now showing password123 and secret-api-key');
// Output: ℹ info Now showing password123 and secret-api-key
// Secrets in scope names are also filtered
const secureLogger = new Signale({
secrets: ['production']
});
const scopedLogger = secureLogger.scope('production', 'database');
scopedLogger.success('Connected successfully');
// Output: [[secure]] [database] › ✔ success Connected successfullyEnable interactive mode for overriding previous log messages.
Usage Examples:
const { Signale } = require('signale');
const interactive = new Signale({
interactive: true,
scope: 'progress'
});
// Interactive loggers override previous interactive messages
interactive.await('Processing step 1/4');
// Output: [progress] › ⋯ awaiting Processing step 1/4
setTimeout(() => {
interactive.await('Processing step 2/4');
// Previous message is overwritten
// Output: [progress] › ⋯ awaiting Processing step 2/4
setTimeout(() => {
interactive.success('Processing step 3/4');
// Output: [progress] › ✔ success Processing step 3/4
setTimeout(() => {
interactive.complete('All steps completed!');
// Output: [progress] › ☑ complete All steps completed!
}, 1000);
}, 1000);
}, 1000);
// Regular (non-interactive) messages are not overridden
const regular = require('signale');
regular.info('This message stays visible');
interactive.pending('This overwrites previous interactive message');
regular.success('This message also stays visible');Configure multiple output streams for flexible logging.
Usage Examples:
const { Signale } = require('signale');
const fs = require('fs');
// Create file streams
const errorLog = fs.createWriteStream('error.log');
const accessLog = fs.createWriteStream('access.log');
// Global stream configuration
const fileLogger = new Signale({
stream: [process.stdout, accessLog] // All logs go to console AND file
});
fileLogger.info('This appears in console and access.log');
fileLogger.success('This also appears in both places');
// Per-logger-type stream configuration
const mixedLogger = new Signale({
stream: process.stdout, // Default stream
types: {
error: {
stream: [process.stderr, errorLog] // Errors to stderr AND error.log
},
info: {
stream: accessLog // Info only to access.log
}
}
});
mixedLogger.success('To console only'); // Uses default stream
mixedLogger.info('To access.log only'); // Uses custom stream
mixedLogger.error('To stderr AND error.log'); // Uses custom streams
// Stream inheritance in scoped loggers
const parent = new Signale({
stream: fs.createWriteStream('parent.log')
});
const child = parent.scope('child');
child.info('This goes to parent.log'); // Inherits parent streamAccess and inspect instance properties and configuration.
Usage Examples:
const { Signale } = require('signale');
const logger = new Signale({
scope: 'api',
secrets: ['token123'],
logLevel: 'warn',
types: {
custom: { badge: '🔧', color: 'blue', label: 'custom' }
}
});
// Inspect current options
console.log(logger.currentOptions);
// {
// config: { displayScope: true, displayBadge: true, ... },
// disabled: false,
// types: { await: {...}, complete: {...}, custom: {...}, ... },
// interactive: false,
// timers: Map(0) {},
// stream: [object Object],
// secrets: ['token123'],
// logLevel: 'warn'
// }
// Access metadata properties
console.log(logger.date); // "12/7/2023"
console.log(logger.timestamp); // "2:30:25 PM"
console.log(logger.filename); // "advanced-demo.js"
console.log(logger.scopeName); // "api"
// Package configuration
console.log(logger.packageConfiguration);
// Configuration loaded from package.json signale sectionProper error handling for advanced features.
Usage Examples:
const { Signale } = require('signale');
// addSecrets error handling
const logger = new Signale();
try {
logger.addSecrets('not-an-array'); // TypeError
} catch (error) {
console.error(error.message); // "Argument must be an array."
}
// Valid secrets addition
logger.addSecrets(['valid', 'secrets']);
logger.info('These valid secrets are filtered');
// Stream error handling
const invalidStream = { write: 'not-a-function' };
try {
const badLogger = new Signale({ stream: invalidStream });
badLogger.info('This may cause issues');
} catch (error) {
console.error('Stream configuration error:', error.message);
}
// Filename access with anonymous execution
const anonymousLogger = new Signale();
console.log(anonymousLogger.filename); // "anonymous" if no file contextOptimize logging performance for production use.
Usage Examples:
const { Signale } = require('signale');
// Disable logging in production for performance
const logger = new Signale({
disabled: process.env.NODE_ENV === 'production'
});
// Or use log levels to filter
const prodLogger = new Signale({
logLevel: process.env.NODE_ENV === 'production' ? 'error' : 'info'
});
// Lazy evaluation with isEnabled check
if (logger.isEnabled()) {
const expensiveData = computeExpensiveLogData();
logger.debug('Expensive debug data: %j', expensiveData);
}
// Stream buffering for high-volume logging
const bufferedStream = new (require('stream').PassThrough)();
const highVolumeLogger = new Signale({
stream: bufferedStream
});
// Batch write to file periodically
setInterval(() => {
const fs = require('fs');
if (bufferedStream.readable) {
const data = bufferedStream.read();
if (data) {
fs.appendFileSync('high-volume.log', data);
}
}
}, 1000);Combine multiple advanced features for sophisticated logging setups.
Usage Examples:
const { Signale } = require('signale');
const fs = require('fs');
// Production-ready logger with all advanced features
class ProductionLogger {
constructor(options = {}) {
const {
environment = 'development',
logLevel = 'info',
secrets = [],
logFile = null
} = options;
const streams = [process.stdout];
if (logFile) {
streams.push(fs.createWriteStream(logFile, { flags: 'a' }));
}
this.logger = new Signale({
disabled: environment === 'test',
logLevel: environment === 'production' ? 'warn' : logLevel,
secrets: [...secrets, process.env.API_SECRET].filter(Boolean),
stream: streams,
config: {
displayTimestamp: true,
displayDate: environment === 'production',
displayFilename: environment === 'development'
}
});
// Add environment-specific secrets
if (environment === 'production') {
this.logger.addSecrets([
process.env.DATABASE_URL,
process.env.JWT_SECRET
].filter(Boolean));
}
}
// Expose logger methods
get info() { return this.logger.info.bind(this.logger); }
get warn() { return this.logger.warn.bind(this.logger); }
get error() { return this.logger.error.bind(this.logger); }
get success() { return this.logger.success.bind(this.logger); }
// Advanced methods
scope(...names) { return this.logger.scope(...names); }
time(label) { return this.logger.time(label); }
timeEnd(label) { return this.logger.timeEnd(label); }
}
// Usage
const prodLogger = new ProductionLogger({
environment: 'production',
logLevel: 'warn',
secrets: ['user-session-key'],
logFile: 'production.log'
});
prodLogger.warn('Production warning'); // Appears in console and file
prodLogger.info('Development info'); // Filtered out in productionInstall with Tessl CLI
npx tessl i tessl/npm-signale