Skip to content

Sinks

A sink is a destination of log messages. LogTape currently provides a few sinks: console and stream. However, you can easily add your own sinks. The signature of a Sink is:

typescript
export type 
Sink
= (
record
: LogRecord) => void;

Here's a simple example of a sink that writes log messages to console:

typescript
import { 
configure
} from "@logtape/logtape";
await
configure
({
sinks
: {
console
(
record
) {
console
.
log
(
record
.
message
);
} }, // Omitted for brevity });

Console sink

Of course, you don't have to implement your own console sink because LogTape provides a console sink:

typescript
import { 
configure
,
getConsoleSink
} from "@logtape/logtape";
await
configure
({
sinks
: {
console
:
getConsoleSink
(),
}, // Omitted for brevity });

You can also customize the format of log messages by passing a ConsoleFormatter to the formatter option of the getConsoleSink() function. The signature of a ConsoleFormatter is:

typescript
export type 
ConsoleFormatter
= (
record
: LogRecord) => readonly unknown[];

The returned array is a list of arguments that will be passed to console.debug(), console.info(), console.warn(), or console.error() depending on the log level of the record.

Here's an example of a custom console formatter that formats log messages with a custom message format:

typescript
import { 
configure
,
getConsoleSink
, type LogRecord } from "@logtape/logtape";
await
configure
({
sinks
: {
console
:
getConsoleSink
({
formatter
(
record
: LogRecord): readonly unknown[] {
let
msg
= "";
const
values
: unknown[] = [];
for (let
i
= 0;
i
<
record
.
message
.
length
;
i
++) {
if (
i
% 2 === 0)
msg
+=
record
.
message
[
i
];
else {
msg
+= "%o";
values
.
push
(
record
.
message
[
i
]);
} } return [ `${
record
.
level
.
toUpperCase
()} %c${
record
.
category
.
join
("\xb7")
} %c${
msg
}`,
"color: gray;", "color: default;", ...
values
,
]; } }), }, // Omitted for brevity });

TIP

Although they are ignored in Node.js and Bun, you can use some styles like color: red; or font-weight: bold; in the second and third arguments of the returned array to style the log messages in the browser console and Deno.

See also getConsoleSink() function and ConsoleSinkOptions interface in the API reference for more details.

Stream sink

Another built-in sink is a stream sink. It writes log messages to a WritableStream. Here's an example of a stream sink that writes log messages to the standard error:

typescript
await 
configure
({
sinks
: {
stream
:
getStreamSink
(Deno.
stderr
.
writable
),
}, // Omitted for brevity });
typescript
import 
stream
from "node:stream";
await
configure
({
sinks
: {
stream
:
getStreamSink
(
stream
.
Writable
.
toWeb
(
process
.
stderr
)),
}, // Omitted for brevity });
typescript
let 
writer
: FileSink | undefined =
undefined
;
const
stdout
= new
WritableStream
({
start
() {
writer
=
Bun
.
stderr
.
writer
();
},
write
(
chunk
) {
writer
?.
write
(
chunk
);
},
close
() {
writer
?.close();
},
abort
() {},
}); await
configure
({
sinks
: {
stream
:
getStreamSink
(
stdout
),
}, // Omitted for brevity });

NOTE

Here we use WritableStream from the Web Streams API. If you are using Node.js, you cannot directly pass process.stderr to getStreamSink because process.stderr is not a WritableStream but a Writable, which is a Node.js stream. You can use Writable.toWeb() method to convert a Node.js stream to a WritableStream.

See also getStreamSink() function and StreamSinkOptions interface in the API reference for more details.

File sink

NOTE

File sink is unavailable in the browser environment.

LogTape provides a file sink through a separate package @logtape/file:

sh
deno add jsr:@logtape/file
sh
npm add @logtape/file
sh
pnpm add @logtape/file
sh
yarn add @logtape/file
sh
bun add @logtape/file

Here's an example of a file sink that writes log messages to a file:

typescript
import { 
getFileSink
} from "@logtape/file";
import {
configure
} from "@logtape/logtape";
await
configure
({
sinks
: {
file
:
getFileSink
("my-app.log", {
lazy
: true }),
}, // Omitted for brevity });

See also getFileSink() function and FileSinkOptions interface in the API reference for more details.

TIP

File sinks support buffering for improved performance through the ~FileSinkOptions.bufferSize option (default: 8192 characters). To prevent log loss during unexpected process termination, you can use the ~FileSinkOptions.flushInterval option (default: 5000ms) to automatically flush buffered logs after a specified time interval. Set flushInterval: 0 to disable time-based flushing, or bufferSize: 0 to disable buffering entirely for immediate writes.

NOTE

On Deno, you need to have the --allow-write flag and the --unstable-fs flag to use the file sink.

Rotating file sink

NOTE

Rotating file sink is unavailable in the browser environment.

A rotating file sink is a file sink that rotates log files, which allows you to manage log files more effectively, especially in long-running applications or environments where log file size can grow significantly over time.

It writes log records to a file, but unlike a standard file sink, it has the ability to rotate the log file when it reaches a certain size. This means:

  1. When the current log file reaches a specified maximum size, it is closed and renamed.
  2. A new log file is created with the original name to continue logging.
  3. Old log files are kept up to a specified maximum number, with the oldest being deleted when this limit is reached.

This rotation process helps prevent any single log file from growing too large, which can cause issues with file handling, log analysis, and storage management.

To use the rotating file sink, you can use the getRotatingFileSink() function, which is provided by the @logtape/file package:

sh
deno add jsr:@logtape/file
sh
npm add @logtape/file
sh
pnpm add @logtape/file
sh
yarn add @logtape/file
sh
bun add @logtape/file

Here's an example of a rotating file sink that writes log messages to a file:

typescript
import { 
getRotatingFileSink
} from "@logtape/file";
import {
configure
} from "@logtape/logtape";
await
configure
({
sinks
: {
file
:
getRotatingFileSink
("my-app.log", {
maxSize
: 0x400 * 0x400, // 1 MiB
maxFiles
: 5,
}), }, // Omitted for brevity });

Rotated log files are named with a suffix like .1, .2, .3, and so on.

For more details, see getRotatingFileSink() function and RotatingFileSinkOptions interface in the API reference.

TIP

Like regular file sinks, rotating file sinks support buffering through the ~FileSinkOptions.bufferSize option (default: 8192 characters) and time-based flushing through the ~FileSinkOptions.flushInterval option (default: 5000ms) to prevent log loss during unexpected process termination. These options work the same way as in regular file sinks.

NOTE

On Deno, you need to have the --allow-write flag and the --unstable-fs flag to use the rotating file sink.

Text formatter

The main article of this section is Text formatters.

The sinks introduced above write log messages in a plain text format. You can customize the format by providing a text formatter.

Here's an example of colorizing log messages in your terminal using the ansiColorFormatter:

typescript
import {
  
ansiColorFormatter
,
configure
,
getConsoleSink
,
} from "@logtape/logtape"; await
configure
({
sinks
: {
console
:
getConsoleSink
({
formatter
:
ansiColorFormatter
,
}), }, // Omitted for brevity });

It would look like this:

2025-06-12 10:34:10.465 +00 INF logtape·meta: LogTape loggers are configured.  Note that LogTape itself uses the meta logger, which has category [ "logtape", "meta" ].  The meta logger purposes to log internal errors such as sink exceptions.  If you are seeing this message, the meta logger is automatically configured.  It's recommended to configure the meta logger with a separate sink so that you can easily notice if logging itself fails or is misconfigured.  To turn off this message, configure the meta logger with higher log levels than "info".  See also <https://logtape.org/manual/categories#meta-logger>.
2025-06-12 10:34:10.472 +00 TRC my-app·module: This is a trace log.
2025-06-12 10:34:10.473 +00 DBG my-app·module: This is a debug log with value: { foo: 123 }
2025-06-12 10:34:10.473 +00 INF my-app: This is an informational log.
2025-06-12 10:34:10.474 +00 WRN my-app: This is a warning.
2025-06-12 10:34:10.475 +00 ERR my-app·module: This is an error with exception: Error: This is an exception.
    at file:///tmp/test.ts:28:10
2025-06-12 10:34:10.475 +00 FTL my-app: This is a fatal error.

OpenTelemetry sink

If you have an OpenTelemetry collector running, you can use the OpenTelemetry sink to send log messages to the collector using @logtape/otel package:

sh
deno add jsr:@logtape/otel
sh
npm add @logtape/otel
sh
pnpm add @logtape/otel
sh
yarn add @logtape/otel
sh
bun add @logtape/otel

The quickest way to get started is to use the getOpenTelemetrySink() function without any arguments:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getOpenTelemetrySink
} from "@logtape/otel";
await
configure
({
sinks
: {
otel
:
getOpenTelemetrySink
(),
},
loggers
: [
{
category
: [],
sinks
: ["otel"],
lowestLevel
: "debug" },
], });

This will use the default OpenTelemetry configuration, which is to send logs to the OpenTelemetry collector running on localhost:4317 or respects the OTEL_* environment variables.

If you want to customize the OpenTelemetry configuration, you can specify options to the getOpenTelemetrySink() function:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getOpenTelemetrySink
} from "@logtape/otel";
await
configure
({
sinks
: {
otel
:
getOpenTelemetrySink
({
serviceName
: "my-service",
otlpExporterConfig
: {
url
: "https://my-otel-collector:4317",
headers
: { "x-api-key": "my-api-key" },
}, }), },
loggers
: [
{
category
: [],
sinks
: ["otel"],
lowestLevel
: "debug" },
], });

Or you can even pass an existing OpenTelemetry LoggerProvider instance:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getOpenTelemetrySink
} from "@logtape/otel";
import {
OTLPLogExporter
} from '@opentelemetry/exporter-logs-otlp-http';
import {
LoggerProvider
,
SimpleLogRecordProcessor
,
} from '@opentelemetry/sdk-logs'; const
exporter
= new
OTLPLogExporter
({
url
: "https://my-otel-collector:4317",
headers
: { "x-api-key": "my-api-key" },
}); const
loggerProvider
= new
LoggerProvider
({
processors
: [
new
SimpleLogRecordProcessor
(
exporter
),
], }); await
configure
({
sinks
: {
otel
:
getOpenTelemetrySink
({
loggerProvider
}),
},
loggers
: [
{
category
: [],
sinks
: ["otel"],
level
: "debug" },
], });

For more information, see the documentation of the getOpenTelemetrySink() function and OpenTelemetrySinkOptions type.

Sentry sink

If you are using Sentry for error monitoring, you can use the Sentry sink to send log messages to Sentry using @logtape/sentry package:

sh
deno add jsr:@logtape/sentry
sh
npm add @logtape/sentry
sh
pnpm add @logtape/sentry
sh
yarn add @logtape/sentry
sh
bun add @logtape/sentry

The quickest way to get started is to use the getSentrySink() function without any arguments:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getSentrySink
} from "@logtape/sentry";
await
configure
({
sinks
: {
sentry
:
getSentrySink
(),
},
loggers
: [
{
category
: [],
sinks
: ["sentry"],
lowestLevel
: "debug" },
], });

The log records will show up in the breadcrumbs of the Sentry issues:

LogTape records show up in the breadcrumbs of a Sentry issue.

If you want to explicitly configure the Sentry client, you can pass the Client instance, which is returned by init() or getClient() functions, to the getSentrySink() function:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getSentrySink
} from "@logtape/sentry";
import {
init
} from "@sentry/node";
const
client
=
init
({
dsn
:
process
.
env
.
SENTRY_DSN
,
}); await
configure
({
sinks
: {
sentry
:
getSentrySink
(
client
),
},
loggers
: [
{
category
: [],
sinks
: ["sentry"],
lowestLevel
: "debug" },
], });

Syslog sink

This API is available since LogTape 0.12.0.

If you have a syslog server running, you can use the syslog sink to send log messages to the server using RFC 5424 format via @logtape/syslog package:

sh
deno add jsr:@logtape/syslog
sh
npm add @logtape/syslog
sh
pnpm add @logtape/syslog
sh
yarn add @logtape/syslog
sh
bun add @logtape/syslog

The quickest way to get started is to use the getSyslogSink() function without any arguments:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getSyslogSink
} from "@logtape/syslog";
await
configure
({
sinks
: {
syslog
:
getSyslogSink
(),
},
loggers
: [
{
category
: [],
sinks
: ["syslog"],
lowestLevel
: "debug" },
], });

This will send log messages to a syslog server running on localhost:514 using UDP protocol with the default facility local0 and application name derived from the process.

You can customize the syslog configuration by passing options to the getSyslogSink() function:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getSyslogSink
} from "@logtape/syslog";
await
configure
({
sinks
: {
syslog
:
getSyslogSink
({
hostname
: "syslog.example.com",
port
: 1514,
protocol
: "tcp",
facility
: "mail",
appName
: "my-application",
timeout
: 5000,
}), },
loggers
: [
{
category
: [],
sinks
: ["syslog"],
lowestLevel
: "info" },
], });

Structured data

RFC 5424 syslog supports structured data, which allows you to include key–value pairs in log messages. LogTape automatically includes log record properties as structured data when the includeStructuredData option is enabled:

typescript
import { 
configure
} from "@logtape/logtape";
import {
getSyslogSink
} from "@logtape/syslog";
await
configure
({
sinks
: {
syslog
:
getSyslogSink
({
includeStructuredData
: true,
structuredDataId
: "myapp@12345",
}), },
loggers
: [
{
category
: [],
sinks
: ["syslog"],
lowestLevel
: "debug" },
], });

With this configuration, log records with properties will include them as structured data in the syslog message:

typescript
import { 
configure
,
getLogger
} from "@logtape/logtape";
import {
getSyslogSink
} from "@logtape/syslog";
await
configure
({
sinks
: {
syslog
:
getSyslogSink
({
includeStructuredData
: true,
structuredDataId
: "myapp@12345",
}), },
loggers
: [
{
category
: [],
sinks
: ["syslog"],
lowestLevel
: "debug" },
], }); const
logger
=
getLogger
();
logger
.
info
("User login successful", {
userId
: 12345,
method
: "oauth" });

This will generate a syslog message like:

syslog
<134>1 2024-01-01T12:00:00.000Z hostname myapp 1234 - [myapp@12345 userId="12345" method="oauth"] User login successful

Supported facilities

The syslog sink supports all standard RFC 5424 facilities:

  • kern, user, mail, daemon, auth, syslog, lpr, news
  • uucp, cron, authpriv, ftp
  • local0, local1, local2, local3, local4, local5, local6, local7

Protocol support

The syslog sink supports both UDP and TCP protocols:

UDP (default)
Fire-and-forget delivery, suitable for high-throughput logging where occasional message loss is acceptable.
TCP
Reliable delivery with connection management, suitable for critical log messages that must not be lost.

For more details, see the getSyslogSink() function and SyslogSinkOptions interface in the API reference.

Disposable sink

TIP

If you are unfamiliar with the concept of disposables, see also the proposal of ECMAScript Explicit Resource Management.

A disposable sink is a sink that can be disposed of. They are automatically disposed of when the configuration is reset or the program exits. The type of a disposable sink is: Sink & Disposable. You can create a disposable sink by defining a [Symbol.dispose] method:

typescript
const 
disposableSink
:
Sink
& Disposable = (
record
: LogRecord) => {
console
.
log
(
record
.
message
);
};
disposableSink
[
Symbol
.
dispose
] = () => {
console
.
log
("Disposed!");
};

A sink can be asynchronously disposed of as well. The type of an asynchronous disposable sink is: Sink & AsyncDisposable. You can create an asynchronous disposable sink by defining a [Symbol.asyncDispose] method:

typescript
const 
asyncDisposableSink
:
Sink
& AsyncDisposable = (
record
: LogRecord) => {
console
.
log
(
record
.
message
);
};
asyncDisposableSink
[
Symbol
.
asyncDispose
] = async () => {
console
.
log
("Disposed!");
};

Explicit disposal

You can explicitly dispose of a sink by calling the dispose() method. It is useful when you want to flush the buffer of a sink without blocking returning a response in edge functions. Here's an example of using the dispose() with ctx.waitUntil() in Cloudflare Workers:

typescript
import { 
configure
,
dispose
} from "@logtape/logtape";
export default { async
fetch
(
request
,
env
,
ctx
) {
await
configure
({ /* ... */ });
// ...
ctx
.
waitUntil
(
dispose
());
return new
Response
("...");
} } satisfies
ExportedHandler
;