Skip to content
Warlock.js v4

Custom Channel

Need to send logs to Slack, a database, or any other destination? Extend LogChannel and implement the log method. The abstract base class handles configuration merging, level filtering, the filter predicate, and date formatting for you.

src/channels/slack-log.ts
import { LogChannel, type BasicLogConfigurations, type LoggingData } from "@warlock.js/logger";
// Extend the base config so the inherited `levels` / `filter` / `redact`
// options are part of the type the channel accepts.
type SlackConfig = BasicLogConfigurations & {
webhookUrl: string;
};
export class SlackLog extends LogChannel<SlackConfig> {
public name = "slack";
public async log(data: LoggingData) {
// shouldBeLogged checks both the levels list and the filter predicate
if (!this.shouldBeLogged(data)) return;
const { type, module, action, message } = data;
await fetch(this.config("webhookUrl"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `[${type.toUpperCase()}] [${module}][${action}]: ${message}`,
}),
});
}
}

Register it alongside the built-in channels:

src/config/log.ts
import { ConsoleLog, FileLog } from "@warlock.js/logger";
import { SlackLog } from "../channels/slack-log";
const channels = [
new ConsoleLog(),
new FileLog({ chunk: "daily" }),
new SlackLog({
webhookUrl: process.env.SLACK_WEBHOOK_URL!,
levels: ["error", "warn"],
}),
];

Override the protected init() method for one-time setup that should happen after construction — opening a socket, establishing a database connection, preparing a write stream.

import { LogChannel, type BasicLogConfigurations, type LoggingData } from "@warlock.js/logger";
type DbConfig = BasicLogConfigurations & { connectionString: string };
export class DatabaseLog extends LogChannel<DbConfig> {
public name = "database";
private client!: SomeDbClient;
protected async init() {
// Runs once, automatically, after the channel is constructed.
// The logger waits for this promise before marking the channel ready.
this.client = await SomeDbClient.connect(this.config("connectionString"));
}
public async log(data: LoggingData) {
if (!this.shouldBeLogged(data)) return;
await this.client.insert("logs", data);
}
}
MemberTypeDescription
namestringUnique identifier for this channel
descriptionstring?Optional human-readable description
terminalbooleantrue preserves ANSI codes; false (default) strips them via clearMessage()
log(data)abstract methodYour write/send logic
shouldBeLogged(data)protected methodfalse if data.type is not in levels, or if filter returns false
config(key)protected methodType-safe accessor merging provided options with channel defaults
getDateAndTimeFormat()protected methodReturns { date, time } Day.js format strings resolved from config
init()protected hookOptional async setup called once after construction
defaultConfigurationsprotected fieldOverride in subclasses to provide option defaults
getRedactConfig()methodReturns the channel’s redact config (additive on top of the logger floor)

Extend LogChannel when you want the built-in handling of levels filtering, the filter predicate, dateFormat config merging, and the init() hook — you only provide log().

Implement LogContract directly when you want full control with no inherited behavior:

import type { LogContract, LoggingData } from "@warlock.js/logger";
class SlackChannel implements LogContract {
name = "slack";
description = "Sends error logs to a Slack webhook";
async log(data: LoggingData): Promise<void> {
if (data.type !== "error") return;
await fetch(process.env.SLACK_WEBHOOK!, {
method: "POST",
body: JSON.stringify({ text: `[${data.module}] ${data.message}` }),
});
}
}
log.addChannel(new SlackChannel());

LogChannel already satisfies LogContract. See Types for the full interface definitions.