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')
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
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
]
})
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
]
})
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'))
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'
})
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
})
})
})
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'
})
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)