DEV Community

Theo Gravity
Theo Gravity

Posted on

Why use LogLayer as the logger for your SDK

When building Typescript / JavaScript SDKs and libraries, choosing the right logging solution is crucial for both developer experience and maintainability. LogLayer stands out as the ideal choice for SDK and library developers due to its unique combination of flexibility, consistency, and powerful features designed specifically for complex software ecosystems.

The Logging Challenge in SDK Development

SDK and library developers face unique logging challenges that traditional logging solutions often fail to address adequately:

  • Dependency conflicts: Different consumers may use different logging libraries
  • Inconsistent APIs: Each logging library has its own parameter order and method signatures
  • Context management: Libraries need to maintain context across different execution scopes
  • Flexibility requirements: Different deployment environments require different logging destinations
  • Error handling: Inconsistent error serialization across different logging backends

LogLayer solves these problems by providing a unified, consistent interface that works across all major logging libraries and environments.

Key Benefits for SDK and Library Developers

1. Developer-friendly API

LogLayer provides an intuitive, fluent API that makes logging simple and expressive. Here's how easy it is to get started:

import { LogLayer, ConsoleTransport } from 'loglayer'

// Simple setup - works out of the box
const log = new LogLayer({
  transport: new ConsoleTransport({ logger: console })
})

// Clean, readable logging
log.info('User logged in successfully')

// Add structured metadata
log.withMetadata({ userId: '1234', action: 'login' }).info('User action completed')

// Handle errors elegantly
log.withError(new Error('Database connection failed')).error('Operation failed')

// Chain multiple operations
log
  .withMetadata({ requestId: 'req-123' })
  .withError(new Error('Validation failed'))
  .error('Request processing failed')
Enter fullscreen mode Exit fullscreen mode

This fluent API makes your SDK's logging code clean, maintainable, and easy to understand for both you and your users.

2. Universal Compatibility

LogLayer supports over 30 different logging libraries and cloud providers, ensuring your SDK works seamlessly regardless of what your users prefer:

Logging Libraries: Pino, Winston, Bunyan, Log4js, tslog, and many more
Cloud Providers: Datadog, New Relic, Google Cloud Logging, AWS Lambda Powertools, Better Stack, and others
Runtime Support: Node.js, Deno, Bun, and browser environments

This means your SDK can integrate with any logging infrastructure your users have, without forcing them to adopt a specific logging library.

3. Consistent API Across All Environments

Unlike traditional logging libraries that have different parameter orders and method signatures, LogLayer provides a consistent, fluent API:

// LogLayer - consistent everywhere
log.withMetadata({ userId: '1234' }).withError(error).error('Operation failed')

// Traditional libraries - different APIs
winston.info('message', { data })     // winston
bunyan.info({ data }, 'message')      // bunyan
pino.info({ data }, 'message')        // pino
Enter fullscreen mode Exit fullscreen mode

This consistency reduces cognitive load for developers using your SDK and eliminates the need to remember different logging patterns.

4. Flexible Transport System

Your SDK users may want to send logs to different destinations. LogLayer's transport system allows them to choose:

// User can configure multiple transports simultaneously
const log = new LogLayer({
  transport: [
    new PinoTransport({ logger: pino() }),           // Local logging
    new DatadogTransport({ apiKey: 'xxx' }),         // Cloud logging
    new ConsoleTransport({ logger: console })        // Development
  ]
})
Enter fullscreen mode Exit fullscreen mode

5. Built-in Plugin System

LogLayer's plugin system allows you to add powerful features without bloating your SDK:

const log = new LogLayer({
  transport: new ConsoleTransport({ logger: console }),
  plugins: [
    redactionPlugin({ paths: ['password', 'token'] }), // Security
    sprintfPlugin()                                    // String formatting
  ]
})
Enter fullscreen mode Exit fullscreen mode

6. Standardized Error Handling

Error handling is notoriously inconsistent across logging libraries. LogLayer standardizes this:

// Works the same way regardless of underlying logging library
log.withError(new Error('Database connection failed')).error('Operation failed')

// Automatic error serialization with stack traces
log.errorOnly(new Error('Validation error'))
Enter fullscreen mode Exit fullscreen mode

7. Zero Configuration for Users

Your SDK users can start with minimal configuration and scale up as needed:

// Minimal setup - works out of the box
const log = new LogLayer({
  transport: new ConsoleTransport({ logger: console })
})

// Advanced setup - when users need more features
const log = new LogLayer({
  transport: [
    new PinoTransport({ logger: pino() }),
    new DatadogTransport({ apiKey: process.env.DATADOG_API_KEY })
  ],
  plugins: [redactionPlugin({ paths: ['password'] })],
  contextFieldName: 'context',
  metadataFieldName: 'metadata'
})
Enter fullscreen mode Exit fullscreen mode

8. Testing and Mocking Support

LogLayer makes testing your SDK much easier with built-in support for mocking and test-friendly configurations:

// In your SDK tests
import { MockLogLayer } from 'loglayer'
import { vi } from 'vitest'

describe('SDK Functionality', () => {
  let sdk: MySDK

  beforeEach(() => {
    sdk = new MySDK({ 
      logger: new MockLogLayer() // no-op logger so tests don't produce logs
    })
  })
})
Enter fullscreen mode Exit fullscreen mode

Implementation Example

Here's how you might integrate LogLayer into an SDK:

// sdk.ts
import { type ILogLayer, LogLayer, ConsoleTransport } from 'loglayer'

export interface SDKConfig {
  // Use the ILogLayer interface to allow different LogLayer versions to be used by end-users
  logger?: ILogLayer
  sdkVersion?: string
  sdkName?: string
}

export class MySDK {
  private log: ILogLayer

  constructor(config?: SDKConfig) {
    // Allow users to provide their own LogLayer instance
    this.log = config?.logger || this.createDefaultLogger()

    // Set SDK-level context
    this.log.withContext({
      sdk_version: config?.sdkVersion || '1.0.0',
      sdk_name: config?.sdkName || 'my-awesome-sdk'
    })
  }

  private createDefaultLogger(): ILogLayer {
    // Fallback to a simple console logger if none provided
    return new LogLayer({
      transport: new ConsoleTransport({ logger: console }),
      contextFieldName: 'context',
      metadataFieldName: 'metadata'
    })
  }
}

// Usage examples:
// 1. SDK creates its own default logger
const sdk = new MySDK({
  sdkVersion: '2.0.0',
  sdkName: 'my-sdk'
})

// 2. User provides their own LogLayer instance
const userLogger = new LogLayer({
  transport: new PinoTransport({ logger: pino() }),
  plugins: [redactionPlugin({ paths: ['password'] })]
})

const sdk = new MySDK({
  logger: userLogger,
  sdkVersion: '2.0.0',
  sdkName: 'my-sdk'
})
Enter fullscreen mode Exit fullscreen mode

Conclusion

By choosing LogLayer for your SDK or library, you're providing your users with a logging solution that adapts to their needs rather than forcing them to adapt to yours.

Top comments (0)