CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-signale

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.

Pending
Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

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.

Capabilities

State Control

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;

Secrets Management

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;

Instance Properties

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;
}

State Control Usage

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!

Secrets Filtering

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 successfully

Interactive Mode

Enable 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');

Stream Management

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 stream

Instance Inspection

Access 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 section

Error Handling

Proper 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 context

Performance Considerations

Optimize 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);

Advanced Configuration Patterns

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 production

Install with Tessl CLI

npx tessl i tessl/npm-signale

docs

advanced-features.md

configuration.md

core-logging.md

custom-loggers.md

index.md

scoped-loggers.md

timers.md

tile.json