Integrations
Clarity is designed to integrate seamlessly with other tools and systems in your logging and monitoring stack. This guide covers how to integrate Clarity with popular logging aggregators, monitoring systems, and frameworks.
Log Aggregation Systems
ELK Stack (Elasticsearch, Logstash, Kibana)
Clarity can be configured to output logs in a format compatible with the ELK stack:
import { Logger } from 'clarity'
import { ElasticsearchFormatter } from './custom-formatters'
// Create a logger with Elasticsearch-friendly output
const logger = new Logger('app', {
format: 'json',
formatter: new ElasticsearchFormatter({
addMetadata: true,
includeHostInfo: true,
}),
})
Example custom Elasticsearch formatter:
import os from 'node:os'
import { Formatter, LogEntry } from 'clarity'
class ElasticsearchFormatter implements Formatter {
constructor(private options: { addMetadata: boolean, includeHostInfo: boolean }) {}
async format(entry: LogEntry): Promise<string> {
const { timestamp, level, name, message, args } = entry
const doc = {
'@timestamp': timestamp.toISOString(),
'log': {
level,
logger: name,
message,
},
}
// Add host info if configured
if (this.options.includeHostInfo) {
doc.host = {
hostname: os.hostname(),
architecture: os.arch(),
platform: os.platform(),
}
}
// Add additional metadata from args
if (this.options.addMetadata && args?.length) {
const metadata = args.reduce((acc, arg) => {
if (typeof arg === 'object' && arg !== null) {
return { ...acc, ...arg }
}
return acc
}, {})
if (Object.keys(metadata).length) {
doc.metadata = metadata
}
}
return JSON.stringify(doc)
}
}
Datadog
Integration with Datadog using the Datadog agent:
import { Logger } from 'clarity'
import { DatadogFormatter } from './datadog-formatter'
const logger = new Logger('app', {
formatter: new DatadogFormatter({
service: 'api-service',
env: process.env.NODE_ENV,
version: '1.0.0',
}),
})
Example Datadog formatter:
import { Formatter, LogEntry } from 'clarity'
class DatadogFormatter implements Formatter {
constructor(private config: { service: string, env: string, version: string }) {}
async format(entry: LogEntry): Promise<string> {
const { timestamp, level, message, name } = entry
return JSON.stringify({
timestamp: Math.floor(timestamp.getTime() / 1000),
message,
level,
logger: {
name,
},
service: this.config.service,
ddsource: 'nodejs',
ddtags: `env:${this.config.env},version:${this.config.version}`,
// Add additional metadata from args if available
...this.extractMetadata(entry.args),
})
}
private extractMetadata(args?: any[]): Record<string, any> {
if (!args || !args.length)
return {}
return args.reduce((acc, arg) => {
if (typeof arg === 'object' && arg !== null) {
return { ...acc, ...arg }
}
return acc
}, {})
}
}
Cloud Logging Providers
AWS CloudWatch
Send logs to AWS CloudWatch:
import { Logger } from 'clarity'
import { CloudWatchTransport } from './cloudwatch-transport'
const logger = new Logger('app', {
level: 'info',
})
// Add CloudWatch transport
const cloudwatchTransport = new CloudWatchTransport({
logGroupName: '/app/api',
logStreamName: `${process.env.NODE_ENV}-${new Date().toISOString().split('T')[0]}`,
region: 'us-west-2',
})
logger.addTransport(cloudwatchTransport)
Example CloudWatch transport implementation:
import { CloudWatchLogs } from '@aws-sdk/client-cloudwatch-logs'
import { LogEntry, Transport } from './transport-interface'
class CloudWatchTransport implements Transport {
private cloudwatch: CloudWatchLogs
private buffer: LogEntry[] = []
private flushInterval: NodeJS.Timeout
constructor(private config: {
logGroupName: string
logStreamName: string
region: string
batchSize?: number
flushIntervalMs?: number
}) {
this.cloudwatch = new CloudWatchLogs({
region: config.region,
})
// Create log group and stream if they don't exist
this.initializeLogGroup()
// Set up flush interval
this.flushInterval = setInterval(() => {
this.flush()
}, config.flushIntervalMs || 5000)
}
async write(entry: LogEntry): Promise<void> {
this.buffer.push(entry)
if (this.buffer.length >= (this.config.batchSize || 20)) {
await this.flush()
}
}
async flush(): Promise<void> {
if (this.buffer.length === 0)
return
const events = this.buffer.map(entry => ({
timestamp: entry.timestamp.getTime(),
message: JSON.stringify({
level: entry.level,
message: entry.message,
logger: entry.name,
...(entry.args?.length ? { metadata: entry.args } : {}),
}),
}))
try {
await this.cloudwatch.putLogEvents({
logGroupName: this.config.logGroupName,
logStreamName: this.config.logStreamName,
logEvents: events,
})
this.buffer = []
}
catch (err) {
console.error('Failed to send logs to CloudWatch:', err)
}
}
async destroy(): Promise<void> {
clearInterval(this.flushInterval)
await this.flush()
}
private async initializeLogGroup(): Promise<void> {
// Implementation to create log group/stream if needed
}
}
Google Cloud Logging
Integration with Google Cloud Logging:
import { Logger } from 'clarity'
import { GCPLogFormatter } from './gcp-formatter'
const logger = new Logger('app', {
formatter: new GCPLogFormatter({
projectId: 'my-gcp-project',
serviceContext: {
service: 'api',
version: '1.0.0',
},
}),
})
Framework Integrations
Express.js
Integrate Clarity with Express.js for HTTP request logging:
import { Logger } from 'clarity'
import express from 'express'
const app = express()
const logger = new Logger('express')
// Request logging middleware
app.use((req, res, next) => {
const startTime = performance.now()
// Log when request is received
logger.info(`${req.method} ${req.url} - Request received`, {
method: req.method,
url: req.url,
ip: req.ip,
headers: req.headers,
})
// Override end to log response
const originalEnd = res.end
res.end = function (...args) {
const duration = Math.round(performance.now() - startTime)
logger.info(`${req.method} ${req.url} - Response sent`, {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
})
return originalEnd.apply(this, args)
}
next()
})
// Error logging middleware
app.use((err, req, res, next) => {
logger.error('Express error middleware caught exception', err, {
method: req.method,
url: req.url,
ip: req.ip,
})
res.status(500).json({ error: 'Internal Server Error' })
})
NestJS
Create a custom NestJS logger using Clarity:
import { LoggerService } from '@nestjs/common'
import { Logger } from 'clarity'
export class ClarityLogger implements LoggerService {
private logger: Logger
private contextLogger: Record<string, Logger> = {}
constructor() {
this.logger = new Logger('nest')
}
log(message: any, context?: string): void {
this.getContextLogger(context).info(message)
}
error(message: any, trace?: string, context?: string): void {
this.getContextLogger(context).error(message)
if (trace) {
this.getContextLogger(context).debug(trace)
}
}
warn(message: any, context?: string): void {
this.getContextLogger(context).warn(message)
}
debug(message: any, context?: string): void {
this.getContextLogger(context).debug(message)
}
verbose(message: any, context?: string): void {
this.getContextLogger(context).debug(message)
}
private getContextLogger(context?: string): Logger {
if (!context) {
return this.logger
}
if (!this.contextLogger[context]) {
this.contextLogger[context] = this.logger.extend(context)
}
return this.contextLogger[context]
}
}
Usage in NestJS main.ts:
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { ClarityLogger } from './clarity-logger'
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: new ClarityLogger(),
})
await app.listen(3000)
}
bootstrap()
Database Integrations
MongoDB
Track MongoDB operations:
import { Logger } from 'clarity'
import { MongoClient } from 'mongodb'
const logger = new Logger('mongodb')
const dbLogger = logger.extend('operations')
async function connectWithLogging() {
const client = new MongoClient('mongodb://localhost:27017')
// Log connection events
client.on('serverOpening', (event) => {
logger.info(`Opening connection to ${event.address}`)
})
client.on('serverClosed', (event) => {
logger.info(`Closed connection to ${event.address}`)
})
client.on('error', (error) => {
logger.error('MongoDB error', error)
})
// Connect with command monitoring for query logging
await client.connect()
// Enable command monitoring
const db = client.db('myapp')
db.addListener('commandStarted', (event) => {
dbLogger.debug(`Command started: ${event.commandName}`, {
db: event.databaseName,
command: event.command,
})
})
db.addListener('commandSucceeded', (event) => {
dbLogger.debug(`Command succeeded: ${event.commandName}`, {
db: event.databaseName,
duration: event.duration,
})
})
db.addListener('commandFailed', (event) => {
dbLogger.error(`Command failed: ${event.commandName}`, {
db: event.databaseName,
error: event.failure,
duration: event.duration,
})
})
return { client, db }
}
Creating Custom Integrations
The flexibility of Clarity allows you to create custom integrations with any system. The key components for integration are:
- Custom Formatters: Create formatters that output logs in the format expected by your target system
- Transport Adapters: Implement custom transport adapters to send logs to external services
- Integration Middleware: Create middleware for frameworks to automatically log relevant information
Transport Adapter Pattern
// Define a Transport interface
interface Transport {
write: (entry: LogEntry) => Promise<void>
flush?: () => Promise<void>
destroy?: () => Promise<void>
}
// Extend Logger to support transports
class EnhancedLogger extends Logger {
private transports: Transport[] = []
addTransport(transport: Transport): void {
this.transports.push(transport)
}
async log(level: LogLevel, message: string, ...args: any[]): Promise<void> {
// First, log normally using parent implementation
await super.log(level, message, ...args)
// Then send to all transports
const entry: LogEntry = {
timestamp: new Date(),
level,
message,
args,
name: this.name,
}
await Promise.all(this.transports.map(transport => transport.write(entry)))
}
async destroy(): Promise<void> {
// Destroy all transports
await Promise.all(this.transports.map(async (transport) => {
if (transport.flush) {
await transport.flush()
}
if (transport.destroy) {
await transport.destroy()
}
}))
// Call parent destroy
await super.destroy()
}
}
By leveraging these integration patterns, Clarity can easily become part of a comprehensive logging and monitoring stack for your applications.