1.0.24 • Published 9 months ago

@bradbunce/launchdarkly-lambda-logger v1.0.24

Weekly downloads
-
License
MIT
Repository
-
Last release
9 months ago

LaunchDarkly Lambda Logger

A feature flag-controlled logging utility for AWS Lambda functions that integrates with LaunchDarkly and Winston. This logger enables dynamic control over log levels through LaunchDarkly feature flags, allowing you to adjust logging verbosity in real-time without deploying code changes. Built on Winston for robust logging capabilities with timestamp support and customizable formatting. Note: This utility is specifically designed for logging with dynamic log levels - it does not handle or display LaunchDarkly SDK flag evaluation events.

Features

  • 🎯 Dynamic Log Level Control: Adjust log levels in real-time using LaunchDarkly feature flags
  • 🎨 Emoji-Enhanced Logging: Visual distinction between log levels using emojis
  • 📊 Multiple Log Levels: Support for FATAL, ERROR, WARN, INFO, DEBUG, and TRACE levels
  • AWS Lambda Optimized: Designed for use in AWS Lambda functions
  • 🔧 Configurable SDK Logging: Control LaunchDarkly SDK's own logging behavior via feature flags
  • 🔄 Flexible Client Integration: Works with either a new LaunchDarkly client or an existing one from your application
  • Timestamp Support: Each log entry includes a timestamp for better tracking
  • 📝 Winston Integration: Built on Winston for robust logging capabilities and customizable formatting

Log Levels

The logger supports the following levels (in order of increasing verbosity):

  • FATAL (💀): Unrecoverable errors that require immediate attention
  • ERROR (🔴): Severe errors that don't prevent the system from running
  • WARN (🟡): Potentially harmful situations
  • INFO (🔵): General operational messages
  • DEBUG (⚪): Detailed information for debugging purposes
  • TRACE (🟣): Very detailed debugging information

Each level includes all levels above it in the hierarchy. For example, if the log level is set to INFO, all FATAL, ERROR, and WARN messages will also be logged.

Installation

npm install @bradbunce/launchdarkly-lambda-logger

Usage

Environment Variables

  • LD_LOG_LEVEL_FLAG_KEY: (Required) The LaunchDarkly feature flag key used to control log levels
  • LD_SDK_LOG_LEVEL_FLAG_KEY: (Optional) The LaunchDarkly feature flag key used to control the SDK's own logging level

Initialization Options

The logger requires a multi-context with both service and user contexts. The service context is used for evaluating log level flags, while the full multi-context is available for your application's use.

  1. With a LaunchDarkly SDK key (creates a new client):

    await logger.initialize('YOUR_SDK_KEY', {
      kind: 'multi',
      service: {
        kind: 'service',
        key: 'weather-app-websocket-lambda',
        name: 'Weather App WebSocket Lambda',
        environment: process.env.NODE_ENV || 'development'
      },
      user: {
        kind: 'user',
        key: 'user-123',
        name: 'John Doe'
      }
    }, {
      logLevelFlagKey: 'your-flag-key', // Optional: overrides LD_LOG_LEVEL_FLAG_KEY env var
      sdkLogLevelFlagKey: 'your-sdk-log-level-flag' // Optional: overrides LD_SDK_LOG_LEVEL_FLAG_KEY env var
    });
  2. With an existing LaunchDarkly client (recommended if your app already has one):

    const ldClient = LaunchDarkly.init('YOUR_SDK_KEY');
    await logger.initialize(ldClient, {
      kind: 'multi',
      service: {
        kind: 'service',
        key: 'weather-app-websocket-lambda',
        name: 'Weather App WebSocket Lambda',
        environment: process.env.NODE_ENV || 'development'
      },
      user: {
        kind: 'user',
        key: 'user-123',
        name: 'John Doe'
      }
    }, {
      logLevelFlagKey: 'your-flag-key' // Optional: overrides LD_LOG_LEVEL_FLAG_KEY env var
    });

Using an existing client is recommended when your application already has a LaunchDarkly client instance, as it prevents creating duplicate connections and reduces resource usage.

Important Note

This utility uses Winston for robust logging with dynamic log levels controlled by LaunchDarkly. Each log entry includes a timestamp and proper formatting for both simple messages and complex objects. It does not log or display LaunchDarkly SDK flag evaluation events. If you need to monitor flag evaluations, you should set up event listeners directly on your LaunchDarkly client:

ldClient.on('update', (settings) => {
  console.log('Flag update received:', settings);
});

ldClient.on('change', (settings) => {
  console.log('Flag change detected:', settings);
});

Example Usage

const { logger } = require('@bradbunce/launchdarkly-lambda-logger');
const LaunchDarkly = require('@launchdarkly/node-server-sdk');

// Helper to create service context
const createServiceContext = () => ({
  kind: 'service',
  key: 'weather-app-websocket-lambda',
  name: 'Weather App WebSocket Lambda',
  environment: process.env.NODE_ENV || 'development'
});

// Helper to create user context from token
const createUserContext = (token, verifyToken) => {
  if (!token) {
    return {
      kind: 'user',
      key: 'anonymous',
      anonymous: true
    };
  }

  try {
    const decoded = verifyToken(token);
    return {
      kind: 'user',
      key: decoded.username || String(decoded.userId),
      name: decoded.name,
      userId: decoded.userId,
      anonymous: false
    };
  } catch (error) {
    return {
      kind: 'user',
      key: 'anonymous',
      anonymous: true
    };
  }
};

// Helper to create multi-context
const createMultiContext = (token, verifyToken) => ({
  kind: 'multi',
  user: createUserContext(token, verifyToken),
  service: createServiceContext()
});

exports.handler = async (event, context) => {
  // Set the flag keys via environment variables
  process.env.LD_LOG_LEVEL_FLAG_KEY = 'your-log-level-flag';
  process.env.LD_SDK_LOG_LEVEL_FLAG_KEY = 'your-sdk-log-level-flag';
  
  // Create LaunchDarkly client with SDK log level from flag
  const ldClient = await initializeLDClient();
  
  // Initialize logger with multi-context
  const multiContext = createMultiContext(event.token, verifyToken);
  await logger.initialize(ldClient, multiContext, {
    logLevelFlagKey: process.env.LD_LOG_LEVEL_FLAG_KEY
  });

  try {
    // Use different log levels as needed
    await logger.info('Lambda function started');
    await logger.debug('Processing event:', event); // Objects are automatically stringified
    
    // Your lambda function logic here
    
    // Logs will include timestamps and proper formatting
    await logger.info('Lambda function completed successfully');
    return { statusCode: 200 };
  } catch (error) {
    await logger.error('Lambda function failed:', error);
    throw error;
  } finally {
    // Always close the logger to clean up resources
    await logger.close();
  }
};

Configuration

LaunchDarkly Feature Flags

Application Log Level Flag

The logger uses a feature flag to control the application log level. The flag key must be set via the LD_LOG_LEVEL_FLAG_KEY environment variable or the logLevelFlagKey initialization option.

Create this flag in your LaunchDarkly project with the following configuration:

  • Key: Set via LD_LOG_LEVEL_FLAG_KEY environment variable or logLevelFlagKey option
  • Type: Number
  • Default value: 3 (INFO level)
  • Possible values:
    • 0: FATAL only
    • 1: ERROR and above
    • 2: WARN and above
    • 3: INFO and above
    • 4: DEBUG and above
    • 5: TRACE and above

SDK Log Level Flag

You can control the LaunchDarkly SDK's own logging level using a feature flag. The flag key must be set via the LD_SDK_LOG_LEVEL_FLAG_KEY environment variable or the sdkLogLevelFlagKey initialization option.

Create this flag in your LaunchDarkly project with the following configuration:

  • Key: Set via LD_SDK_LOG_LEVEL_FLAG_KEY environment variable or sdkLogLevelFlagKey option
  • Type: String
  • Default value: 'info'
  • Possible values:
    • 'debug': Most verbose logging (includes all levels)
    • 'info': Info and above (includes info, warn, error)
    • 'warn': Warning and above (includes warn, error)
    • 'error': Error messages only
    • 'none': No SDK logging

The SDK log level filtering is hierarchical, meaning each level includes all levels above it. For example, if the SDK log level is set to 'warn', both warning and error messages will be logged, but info and debug messages will be filtered out.

Log Output Format

Logs are formatted using Winston with the following features:

  • Timestamps for each log entry
  • Log level displayed in uppercase
  • Emoji indicators for visual distinction
  • Proper JSON formatting for object arguments
  • Color-coded output based on log level

Example output:

2025-01-29T14:25:30.123Z 🔵 INFO: Lambda function started
2025-01-29T14:25:30.124Z ⚪ DEBUG: Processing event: {
  "version": "2.0",
  "routeKey": "$default",
  "rawPath": "/path"
}
2025-01-29T14:25:30.125Z 🔵 INFO: Lambda function completed successfully

API Reference

Logger Methods

  • initialize(sdkKeyOrClient: string | Object, context: Object, options?: Object): Promise<void>

    • Initializes the logger with either a LaunchDarkly SDK key or an existing LaunchDarkly client instance
    • When using a SDK key, a new client will be created
    • When using an existing client, the logger will use that client instead of creating a new one
    • The context must be a multi-context with both service and user contexts
    • Options:
      • logLevelFlagKey: Override the LD_LOG_LEVEL_FLAG_KEY environment variable
      • sdkLogLevelFlagKey: Override the LD_SDK_LOG_LEVEL_FLAG_KEY environment variable
    • Must be called before using any logging methods
  • fatal(...args: any[]): Promise<void>

    • Logs a fatal error message (💀)
    • Use for unrecoverable errors requiring immediate attention
  • error(...args: any[]): Promise<void>

    • Logs an error message (🔴)
    • Use for severe but non-fatal errors
  • warn(...args: any[]): Promise<void>

    • Logs a warning message (🟡)
    • Use for potentially harmful situations
  • info(...args: any[]): Promise<void>

    • Logs an informational message (🔵)
    • Use for general operational information
  • debug(...args: any[]): Promise<void>

    • Logs a debug message (⚪)
    • Use for detailed debugging information
  • trace(...args: any[]): Promise<void>

    • Logs a trace message (🟣)
    • Use for very detailed debugging information
  • close(): Promise<void>

    • Closes the LaunchDarkly client connection
    • Should be called when the logger is no longer needed

Testing

Mocking LaunchDarkly Client

When writing tests, you can mock the LaunchDarkly client to control flag values and verify logging behavior. Here's an example:

const { Logger, LogLevel } = require('@bradbunce/launchdarkly-lambda-logger');
const LaunchDarkly = require('@launchdarkly/node-server-sdk');

// Mock LaunchDarkly client
const mockLDClient = {
  waitForInitialization: async () => {},
  variation: async (flagKey, context, defaultValue) => {
    // Return different values based on flag key
    if (flagKey === 'app-log-level') {
      return LogLevel.DEBUG; // Control application logging
    }
    if (flagKey === 'sdk-log-level') {
      return 'info'; // Control SDK logging
    }
    return defaultValue;
  },
  close: async () => {}
};

// Replace LaunchDarkly.init with mock
const originalInit = LaunchDarkly.init;
LaunchDarkly.init = () => mockLDClient;

// Test your logging
const logger = new Logger();
await logger.initialize('fake-key', {
  kind: 'multi',
  service: {
    kind: 'service',
    key: 'test-service'
  },
  user: {
    kind: 'user',
    key: 'test-user'
  }
}, {
  logLevelFlagKey: 'app-log-level',
  sdkLogLevelFlagKey: 'sdk-log-level'
});

// Restore original after tests
LaunchDarkly.init = originalInit;

Mocking Winston Logger

You can also mock the Winston logger to capture and verify log output:

const winston = require('winston');

// Create a mock logger with message capture
const createMockLogger = (callback) => {
  const logger = {
    levels: {
      fatal: 0,
      error: 1,
      warn: 2,
      info: 3,
      debug: 4,
      trace: 5
    },
    format: winston.format,
    transports: [],
    log(level, message) {
      callback({ level, message });
    }
  };

  // Add level-specific methods
  ['fatal', 'error', 'warn', 'info', 'debug', 'trace'].forEach(level => {
    logger[level] = (msg) => logger.log(level, msg);
  });

  return logger;
};

// Use in tests
const loggedMessages = [];
const mockLogger = createMockLogger(({ level, message }) => {
  loggedMessages.push({ level, message });
});

// Replace Winston's createLogger
const originalCreateLogger = winston.createLogger;
winston.createLogger = () => mockLogger;

// Test logging and verify output
await logger.info('test message');
assert(loggedMessages.some(m => m.message === 'test message'));

// Restore original
winston.createLogger = originalCreateLogger;

Maintenance

Dependencies

This project uses the following major dependencies:

  • @launchdarkly/node-server-sdk: ^9.7.3 (production)
  • winston: ^3.11.0 (production)
  • Node.js: >=18.0.0

Development dependencies:

  • eslint: ^9.0.0
  • @eslint/js: ^8.57.0
  • glob: ^10.3.10
  • rimraf: ^5.0.5

To check for and address any deprecation warnings or updates:

# Update dependencies to their latest compatible versions
npm update

# Check for any vulnerabilities
npm audit

# Check for outdated packages
npm outdated

License

MIT License

Copyright (c) 2025 Brad Bunce

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1.0.24

9 months ago

1.0.23

9 months ago

1.0.22

9 months ago

1.0.21

9 months ago

1.0.20

9 months ago

1.0.19

9 months ago

1.0.18

9 months ago

1.0.17

9 months ago

1.0.16

9 months ago

1.0.15

9 months ago

1.0.14

9 months ago

1.0.13

9 months ago

1.0.12

9 months ago

1.0.11

9 months ago

1.0.10

9 months ago

1.0.9

9 months ago

1.0.7

9 months ago

1.0.6

9 months ago

1.0.5

9 months ago

1.0.4

9 months ago

1.0.3

9 months ago

1.0.2

9 months ago

1.0.1

9 months ago

1.0.0

9 months ago