Configuration
WARNING
If you are authoring a library, you should not set up LogTape in the library itself. It is up to the application to set up LogTape.
See also Using in libraries.
Setting up LogTape for your application is a crucial step in implementing effective logging. The configure() function is your main tool for this task. Let's explore how to use it to tailor LogTape to your specific needs.
At its core, configuring LogTape involves three main components:
- Sinks: Where your logs will be sent
- Filters: Rules for which logs should be processed
- Loggers: The logging instances for different parts of your application
Here's a simple configuration to get you started:
import { configure, getConsoleSink } from "@logtape/logtape";
await configure({
sinks: {
console: getConsoleSink(),
},
loggers: [
{
category: "my-app",
lowestLevel: "info",
sinks: ["console"],
},
],
});This setup will log all "info" level and above messages from the ["my-app"] category to the console.
TIP
Want to avoid the await? Check out the Synchronous configuration section.
Crafting your configuration
NOTE
The configure() is an asynchronous function. Always use await or handle the returned Promise appropriately.
Setting up sinks
Sinks determine where your logs end up. You can have multiple sinks for different purposes:
import { getFileSink } from "@logtape/file";
import { configure, getConsoleSink } from "@logtape/logtape";
await configure({
sinks: {
console: getConsoleSink(),
file: getFileSink("app.log"),
errorFile: getFileSink("error.log"),
},
// ... rest of configuration
});Defining filters
Filters allow you to fine-tune which logs are processed. They can be based on log levels, content, or custom logic:
await configure({
// ... sinks configuration
filters: {
noDebug(record) {
return record.level !== "debug";
},
onlyErrors(record) {
return record.level === "error" || record.level === "fatal";
},
containsUserData(record) {
return record.message.some(
part => typeof part === "string" && part.includes("user")
);
},
},
// ... loggers configuration
});Configuring loggers
Loggers are where you bring everything together. You can set up different loggers for different parts of your application:
await configure({
// ... sinks and filters configuration
loggers: [
{
category: "my-app",
lowestLevel: "info",
sinks: ["console", "file"],
},
{
category: ["my-app", "database"],
lowestLevel: "debug",
sinks: ["file"],
filters: ["noDebug"],
},
{
category: ["my-app", "user-service"],
lowestLevel: "info",
sinks: ["console", "file"],
filters: ["containsUserData"],
},
],
});For severity levels, see Configuring severity levels.
NOTE
By default, loggers inherit the sinks of their ascendants. You can override them by specifying the parentSinks: "override" option in the logger.
WARNING
Defining loggers with the same category is disallowed. If there are duplicate categories, LogTape will throw a ConfigError when you call configure().
Disposal of resources
If sinks or filters implement the Disposal or AsyncDisposal interface, they will be properly disposed when resetting the configuration or when the application exits.
Advanced configuration techniques
Using environment variables
It's often useful to change your logging configuration based on the environment. Here's how you might do that:
const isDevelopment = process.env.NODE_ENV === "development";
await configure({
sinks: {
console: getConsoleSink(),
file: getFileSink(isDevelopment ? "dev.log" : "prod.log"),
},
loggers: [
{
category: "my-app",
lowestLevel: isDevelopment ? "trace" : "info",
sinks: isDevelopment ? ["console", "file"] : ["file"],
},
],
});const isDevelopment = process.env.NODE_ENV === "development";
await configure({
sinks: {
console: getConsoleSink(),
file: getFileSink(isDevelopment ? "dev.log" : "prod.log"),
},
loggers: [
{
category: "my-app",
lowestLevel: isDevelopment ? "trace" : "info",
sinks: isDevelopment ? ["console", "file"] : ["file"],
},
],
});Reconfiguration
Remember that calling configure() with reset: true option will reset any existing configuration. If you need to change the configuration at runtime, you can call configure() again with reset: true and the new settings:
// Initial configuration
await configure(initialConfig);
// Later in your application...
await configure({
reset: true,
...initialConfig,
loggers: [
...initialConfig.loggers,
{
category: "new-feature",
lowestLevel: "debug",
sinks: ["console"],
},
],
});Or you can explicitly call reset() to clear the existing configuration:
import { configure, reset } from "@logtape/logtape";
await configure(initialConfig);
// Later in your application...
reset();Synchronous configuration
This API is available since LogTape 0.9.0.
If you prefer to configure LogTape synchronously, you can use the configureSync() function instead:
import { configureSync, getConsoleSink } from "@logtape/logtape";
configureSync({
sinks: {
console: getConsoleSink(),
},
loggers: [
{
category: "my-app",
lowestLevel: "info",
sinks: ["console"],
},
],
});CAUTION
However, be aware that synchronous configuration has some limitations: You cannot use sinks or filters that require asynchronous disposal, i.e., those that implement the AsyncDisposal interface. For example, among the built-in sinks, stream sinks requires asynchronous disposal.
That said, you still can use sinks or filters that require synchronous disposal, i.e., those that implement the Disposal interface.
Likewise, you can use resetSync() to reset the configuration synchronously:
import { resetSync } from "@logtape/logtape";
resetSync();CAUTION
The configure()–reset() and configureSync()–resetSync() APIs have to be paired and should not be mixed. If you use configure() to set up LogTape, you should use reset() to reset it. If you use configureSync(), you should use resetSync().
Browser and SPA environments
LogTape works seamlessly in browser environments, including Single Page Applications (SPAs). The key principle is simple: configure LogTape before your application starts. This doesn't require top-level await—you can use configureSync() at your application's entry point.
The recommended pattern
The most reliable approach is to create a dedicated configuration file and import it first in your entry point. This pattern is widely used by other SDKs like Sentry and analytics libraries:
import { configureSync, getConsoleSink } from "@logtape/logtape";
configureSync({
sinks: {
console: getConsoleSink(),
},
loggers: [
{
category: "my-app",
lowestLevel: "debug",
sinks: ["console"],
},
],
});// Configuration must be imported first!
import "./setup";
import { App } from "./App";
// ... rest of your applicationThis pattern ensures that:
- LogTape is configured before any logging calls are made
- The configuration runs synchronously during module evaluation
- No top-level
awaitis needed
React
In React applications, configure LogTape before calling createRoot():
import "./setup"; // Import configuration first
import { createRoot } from "react-dom/client";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(<App />);Or configure inline if you prefer:
import { configureSync, getConsoleSink } from "@logtape/logtape";
import { createRoot } from "react-dom/client";
import { App } from "./App";
// Configure before rendering
configureSync({
sinks: { console: getConsoleSink() },
loggers: [
{ category: "my-app", lowestLevel: "info", sinks: ["console"] },
],
});
createRoot(document.getElementById("root")!).render(<App />);Vue
In Vue applications, configure LogTape before calling app.mount(). This aligns with Vue's own guidance: “Make sure to apply all app configurations before mounting the app!”
import "./setup"; // Import configuration first
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
// ... register plugins, components, etc.
app.mount("#app");Or configure inline:
import { configureSync, getConsoleSink } from "@logtape/logtape";
import { createApp } from "vue";
import App from "./App.vue";
// Configure before mounting
configureSync({
sinks: { console: getConsoleSink() },
loggers: [
{ category: "my-app", lowestLevel: "info", sinks: ["console"] },
],
});
const app = createApp(App);
app.mount("#app");Next.js
For Next.js applications, you can use the instrumentation-client.js file for client-side initialization:
import { configureSync, getConsoleSink } from "@logtape/logtape";
configureSync({
sinks: { console: getConsoleSink() },
loggers: [
{ category: "my-app", lowestLevel: "info", sinks: ["console"] },
],
});For server-side logging, use the standard instrumentation.js file with the register() function.
Common mistakes to avoid
WARNING
Don't configure LogTape inside libraries. If you're authoring a library, leave configuration to the application developer. See Using in libraries for details.
WARNING
Don't configure inside React components or Vue setup functions. Configuration should happen once at application startup, not during component rendering. Calling configureSync() inside components can lead to repeated configuration attempts and errors.
Best practices
- Configure early: Set up your LogTape configuration early in your application's lifecycle, ideally before any logging calls are made.
- Use categories wisely: Create a logical hierarchy with your categories to make filtering and management easier.
- Configure for different environments: Have different configurations for development, testing, and production.
- Don't overuse filters: While powerful, too many filters can make your logging system complex and hard to maintain.
- Monitor performance: Be mindful of the performance impact of your logging, especially in production environments.
Configuration from objects
This API is available since LogTape 2.0.0.
While programmatic configuration with configure() is type-safe and powerful, you may want to load logging configuration from external files like JSON, YAML, or TOML. The @logtape/config package provides configureFromObject() for this purpose.
Installation
deno add jsr:@logtape/confignpm add @logtape/configpnpm add @logtape/configyarn add @logtape/configbun add @logtape/configBasic usage
import { configureFromObject } from "@logtape/config";
import { readFile } from "node:fs/promises";
const config = JSON.parse(await readFile("./logtape.json", "utf-8"));
await configureFromObject(config);Configuration schema
Sinks
{
"sinks": {
"console": {
"type": "#console()"
},
"file": {
"type": "@logtape/file#getFileSink()",
"path": "/var/log/app.log"
}
}
}Module reference syntax
The type field uses a special syntax to reference modules and exports:
| Format | Description |
|---|---|
#shorthand() | Built-in shorthand, factory function |
#shorthand | Built-in shorthand, direct value |
module#export() | Named export, factory function |
module#export | Named export, direct value |
module() | Default export, factory function |
module | Default export, direct value |
The () suffix indicates that the export is a factory function that should be called with the remaining configuration options as its argument.
Built-in shorthands
| Shorthand | Maps to |
|---|---|
#console | @logtape/logtape#getConsoleSink |
#stream | @logtape/logtape#getStreamSink |
#text | @logtape/logtape#getTextFormatter |
#ansiColor | @logtape/logtape#getAnsiColorFormatter |
#jsonLines | @logtape/logtape#getJsonLinesFormatter |
Formatters
{
"sinks": {
"console": {
"type": "#console()",
"formatter": {
"type": "#ansiColor()",
"timestamp": "date-time-tz"
}
}
}
}Or use a shorthand string:
{
"sinks": {
"file": {
"type": "@logtape/file#getFileSink()",
"path": "/var/log/app.log",
"formatter": "#jsonLines()"
}
}
}Loggers
{
"loggers": [
{
"category": ["myapp"],
"sinks": ["console", "file"],
"lowestLevel": "info"
},
{
"category": ["myapp", "database"],
"lowestLevel": "debug"
}
]
}Complete example
JSON
{
"sinks": {
"console": {
"type": "#console()",
"formatter": {
"type": "#ansiColor()",
"timestamp": "date-time-tz"
}
},
"file": {
"type": "@logtape/file#getFileSink()",
"path": "/var/log/app.log",
"formatter": "#jsonLines()"
}
},
"loggers": [
{
"category": ["myapp"],
"sinks": ["console", "file"],
"lowestLevel": "info"
}
]
}YAML
sinks:
console:
type: "#console()"
formatter:
type: "#ansiColor()"
timestamp: date-time-tz
file:
type: "@logtape/file#getFileSink()"
path: /var/log/app.log
formatter: "#jsonLines()"
loggers:
- category: [myapp]
sinks: [console, file]
lowestLevel: infoError handling
By default, configureFromObject() throws a ConfigError when it encounters invalid configuration. You can change this behavior with the onInvalidConfig option:
await configureFromObject(config, {
onInvalidConfig: "warn" // Apply valid parts, log warnings
});In "warn" mode:
- Only the minimal invalid parts are filtered out
- Valid sinks, filters, and loggers are still configured
- Warnings are logged to the meta logger (
["logtape", "meta"]) - If a logger references invalid sinks, the logger is still created but those sink references are skipped
This is useful in production environments where you'd rather have some logging working than have the application crash due to a configuration error.
Reconfiguration
To reset the existing configuration before applying the new one, you can use the reset option:
{
"reset": true,
"sinks": {
// ...
},
"loggers": [
// ...
]
}This is equivalent to calling configure() with reset: true.
Module resolution notes
When specifying module paths in the type field (e.g., module#export), please keep the following in mind:
- Package names
- You can use package names (e.g.,
@logtape/file) if they are installed in yournode_modulesor available in your runtime. - Absolute paths
- You can use absolute file paths or
file://URLs. - Relative paths
- Relative paths (e.g.,
./my-sink.ts) are resolved relative to the@logtape/configpackage file, not your configuration file or current working directory. This is usually not what you want. Therefore, it is recommended to use absolute paths or package names. Alternatively, you can register your custom modules as custom shorthands.
Environment variable expansion
Use the expandEnvVars() utility to expand environment variables before configuring:
import { configureFromObject, expandEnvVars } from "@logtape/config";
import { readFile } from "node:fs/promises";
const config = JSON.parse(await readFile("./logtape.json", "utf-8"));
const expanded = expandEnvVars(config);
await configureFromObject(expanded);The default pattern matches ${VAR} and ${VAR:default}:
{
"sinks": {
"file": {
"type": "@logtape/file#getFileSink()",
"path": "${LOG_PATH:/var/log/app.log}"
}
}
}Custom shorthands
You can define custom shorthands to simplify your configuration:
await configureFromObject(config, {
shorthands: {
sinks: {
file: "@logtape/file#getFileSink",
rotating: "@logtape/file#getRotatingFileSink",
custom: "./my-sinks#getCustomSink",
},
formatters: {
pretty: "@logtape/pretty#getPrettyFormatter",
}
}
});Then use them in your configuration:
{
"sinks": {
"app": {
"type": "#file()",
"path": "/var/log/app.log"
}
}
}