Skip to content

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 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 application

This pattern ensures that:

  • LogTape is configured before any logging calls are made
  • The configuration runs synchronously during module evaluation
  • No top-level await is needed

React

In React applications, configure LogTape before calling createRoot():

main.ts
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:

main.ts
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!”

main.ts
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:

main.ts
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:

instrumentation-client.ts
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

  1. Configure early: Set up your LogTape configuration early in your application's lifecycle, ideally before any logging calls are made.
  2. Use categories wisely: Create a logical hierarchy with your categories to make filtering and management easier.
  3. Configure for different environments: Have different configurations for development, testing, and production.
  4. Don't overuse filters: While powerful, too many filters can make your logging system complex and hard to maintain.
  5. 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/config
npm add @logtape/config
pnpm add @logtape/config
yarn add @logtape/config
bun add @logtape/config

Basic 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:

FormatDescription
#shorthand()Built-in shorthand, factory function
#shorthandBuilt-in shorthand, direct value
module#export()Named export, factory function
module#exportNamed export, direct value
module()Default export, factory function
moduleDefault 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

ShorthandMaps 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: info

Error 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 your node_modules or 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/config package 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"
    }
  }
}