Custom Transports
Send logs to external services
Send logs to external services using custom transports. Transports work alongside built-in console and file logging.
Looking for Axiom, Better Stack, ClickHouse, Datadog, HyperDX, Loki, OTLP, PostHog, or Sentry? Built-in adapters cover those — with batching, retries, and credential handling included. Custom transports are for everything else.
Output Behavior
Logixlysia supports three types of logging outputs:
- Console Logging - Built-in console output
- File Logging - Built-in file output
- Transport Logging - Custom external services
Controlling Outputs
- Default: Console + File (if
logFilePathis set) + Transports disableInternalLogger: true: Disables console logging onlydisableFileLogging: true: Disables file logging onlyuseTransportsOnly: true: Disables both console and file, uses only transports
Basic Transport
type Transport = {
log: (
level: LogLevel,
message: string,
meta?: Record<string, unknown>
) => void | Promise<void>
flush?: () => void | Promise<void>
close?: () => void | Promise<void>
}
const consoleTransport: Transport = {
log: (level, message, meta) => {
console.log(`[${level}] ${message}`, meta)
}
}
app.use(
logixlysia({
config: {
transports: [consoleTransport]
}
})
)
log is the only required member. Implement flush when your transport batches or queues entries — the plugin calls it on shutdown so the last lines before an exit still arrive. Implement close when you hold a socket, timer, or buffer that must be released; it is never called automatically, only through flushLogixlysia(options, { close: true }), and it must be idempotent.
meta always carries request.method, request.url and durationMs, plus the merged context when there is one. An access line adds status, and its message argument is the empty string. An error line adds status, message and error, a plain object with message, name, status, code, why, fix, link and internal when the error carried them.
External Services
Elasticsearch
const elasticsearchTransport = {
log: async (level, message, meta) => {
try {
await fetch('http://elasticsearch:9200/logs/_doc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
level,
message,
...meta,
timestamp: new Date().toISOString()
})
})
} catch (err) {
console.error('Elasticsearch transport error', err)
}
}
}
MongoDB
const mongodbTransport = {
log: async (level, message, meta) => {
try {
await db.collection('logs').insertOne({
level,
message,
...meta,
timestamp: new Date()
})
} catch (err) {
console.error('MongoDB transport error', err)
}
}
}
Slack
const slackTransport = {
log: async (level, message, meta) => {
if (level.toLowerCase() === 'error') {
const webhook = process.env.SLACK_WEBHOOK_URL
if (!webhook) return
try {
await fetch(webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `[${level}] ${message}\n${JSON.stringify(meta, null, 2)}`
})
})
} catch (err) {
console.error('Slack transport error', err)
}
}
}
}
Multiple Transports
app.use(
logixlysia({
config: {
transports: [elasticsearchTransport, slackTransport]
}
})
)
Transport-Only Logging
app.use(
logixlysia({
config: {
useTransportsOnly: true,
transports: [elasticsearchTransport, slackTransport]
}
})
)
Best Practices
- Handle transport errors gracefully
- Implement retry logic for critical transports
- Use async transports for better performance
- Secure transport credentials
- Avoid logging secrets or PII
- Use
useTransportsOnlyfor production environments where you only want external logging