Skip to content

Third-party integrations

This guide shows how to integrate LogTape with popular web frameworks, ORMs, and other third-party libraries for unified logging across different JavaScript environments.

Drizzle ORM

The @logtape/drizzle-orm package is available since LogTape 1.3.0.

Drizzle ORM is a lightweight, TypeScript-first ORM that supports PostgreSQL, MySQL, and SQLite. LogTape provides a Drizzle ORM adapter through the @logtape/drizzle-orm package, allowing you to use LogTape as Drizzle's logging backend for database query logging:

deno add jsr:@logtape/drizzle-orm
npm add @logtape/drizzle-orm
pnpm add @logtape/drizzle-orm
yarn add @logtape/drizzle-orm
bun add @logtape/drizzle-orm

Here's an example of using LogTape with Drizzle ORM:

import { ,  } from "@logtape/logtape";
import {  } from "@logtape/drizzle-orm";
import {  } from "drizzle-orm/postgres-js";
import  from "postgres";

await ({
  : { : () },
  : [
    { : ["drizzle-orm"], : ["console"], : "debug" }
  ],
});

const  = (..!);
const  = (, {
  : (),
});

// Now all database queries will be logged through LogTape

Custom category

You can specify a custom category for the logger:

const  = ({
  : ["myapp", "database"],
});

Custom log level

By default, queries are logged at the debug level. You can change this:

const  = ({
  : "info",
});

Dialects

By default, the PostgreSQL dialect ("pg") is used. You can change this:

const  = ({
  : "sqlite",  // "pg" | "sqlite"
});

Structured logging output

The adapter logs queries with structured data that includes:

  • formattedQuery: The query with parameter placeholders (e.g., $1, $2) replaced with actual values for easier reading
  • query: The original query string with placeholders
  • params: The original parameters array

This allows you to:

  • Get human-readable output with text formatters
  • Get machine-parseable output with JSON Lines formatter
  • Use full query and params data with OpenTelemetry, Sentry, and other sinks

Express

The @logtape/express package is available since LogTape 1.3.0.

Express is the most popular Node.js web framework. LogTape provides an Express middleware adapter through the @logtape/express package, which provides HTTP request logging using LogTape as the backend, as an alternative to Morgan:

deno add jsr:@logtape/express
npm add @logtape/express
pnpm add @logtape/express
yarn add @logtape/express
bun add @logtape/express

Here's an example of using LogTape with Express:

import { ,  } from "@logtape/logtape";
import {  } from "@logtape/express";
import  from "express";

await ({
  : { : () },
  : [
    { : ["express"], : ["console"], : "info" }
  ],
});

const  = ();
.(());

.("/", (, ) => {
  .({ : "world" });
});

.(3000);

Custom category

You can specify a custom category for the logger:

const  = ({
  : ["myapp", "http"],
});

Options

The middleware accepts the following options:

const  = ({
  : ["myapp", "http"],  // Custom category (default: ["express"])
  : "debug",                // Log level (default: "info")
  : "dev",                 // Predefined format (default: "combined")
  : (, ) => . < 400,  // Skip successful requests
  : false,              // Log after response (default: false)
  : true,                 // Add requestId to request-scoped logs
});

Request context

Set context: true to add request-scoped correlation fields. By default, the middleware reads the x-request-id request header, generates an ID when the header is missing, writes the resolved ID to the x-request-id response header, and adds requestId to the request log record.

To make logs emitted by route handlers inherit the same requestId, configure LogTape with contextLocalStorage as described in implicit contexts:

import {  } from "node:async_hooks";
import {  } from "@logtape/logtape";
import {  } from "@logtape/express";

await ({
  : {},
  : [],
  : new (),
});

const  = ({ : true });

The context is established even when skip suppresses the request log, so application logs inside the skipped request can still carry the same request ID.

You can customize request ID headers and include additional request fields:

const  = ({
  : {
    : {
      : ["x-correlation-id", "x-request-id"],
      : "x-request-id",
    },
    : ["requestId", "method", "url", "httpVersion"],
    : () => ({ : . }),
  },
});

Predefined formats

The middleware supports structured presets and text presets:

  • "structured-combined": Structured request properties with all fields (default)
  • "structured-common": Structured request properties without referrer/userAgent
  • "combined": Deprecated alias for "structured-combined"
  • "common": Deprecated alias for "structured-common"
  • "morgan-combined": Morgan-compatible Apache combined access log output
  • "morgan-common": Morgan-compatible Apache common access log output
  • "dev": Concise output for development (e.g., GET /path 200 1.234 ms - 123)
  • "short": Shorter format with remote address
  • "tiny": Minimal output

Custom format function

You can also provide a custom format function that returns either a string or an object with structured properties:

const  = ({
  : (, , ) => ({
    : .,
    : .,
    : .,
    : ,
  }),
});

Structured logging output

When using the "structured-combined" format (default), the middleware logs structured data that includes:

  • method: HTTP request method
  • url: Request URL
  • status: HTTP response status code
  • responseTime: Response time in milliseconds
  • contentLength: Response content-length header value
  • remoteAddr: Remote client address
  • userAgent: User-Agent header value
  • referrer: Referrer header value
  • httpVersion: HTTP version (e.g., "1.1")

Elysia

The @logtape/elysia package is available since LogTape 2.0.0.

Elysia is a fast, Bun-first web framework with end-to-end type safety. LogTape provides an Elysia plugin adapter through the @logtape/elysia package, which provides HTTP request logging using LogTape as the backend:

deno add jsr:@logtape/elysia
npm add @logtape/elysia
pnpm add @logtape/elysia
yarn add @logtape/elysia
bun add @logtape/elysia

Since LogTape 2.4.0, this adapter supports Elysia 1.4 and Elysia 2 starting with 2.0.0-beta.12. Elysia 2 is still in beta. The elysiaLogger() options are the same for both versions; application routes and hooks use the native API of the installed Elysia version.

The application and adapter must resolve the same Elysia package copy and module format. Use ESM throughout or CommonJS throughout, including imported child plugins. Local request context relies on Elysia 2's internal hook and route representation, which the compatibility tests check against the pinned beta version.

When using scope: "local" with request context enabled, the adapter registers an internal macro in Elysia 2. Elysia 2 rejects adding macros to the parent app from an async plugin function after await. Register the logger before that await, or return a separate logger plugin for Elysia to merge:

const app = new Elysia().use(async () => {
  await initializeDatabase();
  return elysiaLogger({ scope: "local", context: true })
    .get("/", () => "Hello");
});

Do not use return app.use(elysiaLogger(...)) after await inside .use(async (app) => { ... }) with these options. Returning the separate plugin lets Elysia merge it in its ordered async plugin queue. This restriction does not affect await inside request handlers.

For Elysia 2 on Deno, add these overrides to your deno.json imports map, alongside the LogTape imports. The exact npm specifiers also redirect the adapter's JSR dependencies, so the application and adapter share Elysia:

{
  "imports": {
    "elysia": "npm:elysia@2.0.0-beta.12",
    "npm:elysia@^1.4.0": "npm:elysia@2.0.0-beta.12",
    "npm:elysia@^1.4.0/utils": "npm:elysia@2.0.0-beta.12/utils"
  }
}

Keep all three mappings on the same Elysia version when upgrading.

Here's an example of using LogTape with Elysia:

import {  } from "elysia";
import { ,  } from "@logtape/logtape";
import {  } from "@logtape/elysia";

await ({
  : { : () },
  : [
    { : ["elysia"], : ["console"], : "info" }
  ],
});

const  = new ()
  .(())
  .("/", () => ({ : "world" }))
  .(3000);

.(`Server running at ${.?.}`);

Custom category

You can specify a custom category for the logger:

const  = ({
  : ["myapp", "http"],
});

Options

The plugin accepts the following options:

const  = ({
  : ["myapp", "http"],  // Custom category (default: ["elysia"])
  : "debug",                // Log level (default: "info")
  : "dev",                 // Predefined format (default: "combined")
  : () => . === "/health",  // Skip health check endpoint
  : false,             // Log after response (default: false)
  : "global",               // Plugin scope (default: "global")
  : true,                 // Add requestId to request-scoped logs
});

Request context

Set context: true to add request-scoped correlation fields. By default, the plugin reads the x-request-id request header, generates an ID when the header is missing, writes the resolved ID to the x-request-id response header, and adds requestId to request and error log records.

To make logs emitted by route handlers inherit the same requestId, configure LogTape with contextLocalStorage as described in implicit contexts:

import {  } from "node:async_hooks";
import {  } from "@logtape/logtape";
import {  } from "@logtape/elysia";

await ({
  : {},
  : [],
  : new (),
});

const  = ({ : true });

The context is established even when skip suppresses the request log, so application logs inside the skipped request can still carry the same request ID.

You can customize request ID headers and include additional request fields:

const  = ({
  : {
    : {
      : ["x-correlation-id", "x-request-id"],
      : "x-request-id",
    },
    : ["requestId", "method", "path", "userAgent"],
    : () => ({ : . }),
  },
});

Plugin scope

Elysia supports plugin scoping to control how lifecycle hooks propagate:

  • "global": Hooks apply to all routes in the application (default)
  • "scoped": Hooks apply to the parent instance where the plugin is used (Elysia 2 calls this scope "plugin")
  • "local": Hooks only apply within the plugin itself
// Only log requests within a specific group
const  = ({
  : "scoped",
});

Predefined formats

The plugin supports structured presets and text presets:

  • "structured-combined": Structured request properties (default)
  • "structured-common": Structured request properties without referrer/userAgent
  • "combined": Deprecated alias for "structured-combined"
  • "common": Deprecated alias for "structured-common"
  • "morgan-combined": Morgan-compatible Apache combined access log output
  • "morgan-common": Morgan-compatible Apache common access log output
  • "dev": Concise output for development (e.g., GET /path 200 1.234 ms - 123)
  • "short": Shorter format with URL
  • "tiny": Minimal output

Elysia does not expose socket-level fields consistently across runtimes. The Morgan-compatible text formats use X-Forwarded-For for the remote address and render unavailable fields, such as the HTTP version, as -.

Custom format function

You can also provide a custom format function that returns either a string or an object with structured properties:

const  = ({
  : (, ) => ({
    : ..,
    : .,
    : ..,
    : ,
  }),
});

Error logging

The plugin automatically logs errors at the error level using Elysia's onError hook in Elysia 1 or error hook in Elysia 2. Error logs include errorMessage in addition to standard request properties. errorCode is Elysia 1's context code, or Elysia 2's error.code when it is a string or number; it is omitted when Elysia 2 supplies no code.

Structured logging output

When using the "structured-combined" format (default), the plugin logs structured data that includes:

  • method: HTTP request method
  • url: Request URL
  • path: Request path
  • status: HTTP response status code
  • responseTime: Response time in milliseconds
  • contentLength: Response content-length header value
  • remoteAddr: Remote client address (from X-Forwarded-For header)
  • userAgent: User-Agent header value
  • referrer: Referrer header value

Hono

The @logtape/hono package is available since LogTape 1.3.0.

Hono is a modern web framework that works across multiple JavaScript runtimes. LogTape provides a Hono adapter through the @logtape/hono package, allowing you to use LogTape as Hono's logging backend with HTTP request logging:

deno add jsr:@logtape/hono
npm add @logtape/hono
pnpm add @logtape/hono
yarn add @logtape/hono
bun add @logtape/hono

Here's an example of using LogTape with Hono:

import {  } from "hono";
import { ,  } from "@logtape/logtape";
import {  } from "@logtape/hono";

await ({
  : { : () },
  : [
    { : ["hono"], : ["console"], : "info" }
  ],
});

const  = new ();
.(());

.("/", () => .({ : "world" }));

// Works in Deno, Node.js, Bun, Cloudflare Workers, etc.
export default ;

Custom category

You can specify a custom category for the logger:

const  = ({
  : ["myapp", "http"],
});

Options

The middleware accepts the following options:

const  = ({
  : ["myapp", "http"],  // Custom category (default: ["hono"])
  : "debug",                // Log level (default: "info")
  : "dev",                 // Predefined format (default: "combined")
  : () => .. === "/health",  // Skip health check endpoint
  : false,             // Log after response (default: false)
  : true,                 // Add requestId to request-scoped logs
});

Request context

Set context: true to add request-scoped correlation fields. By default, the middleware reads the x-request-id request header, generates an ID when the header is missing, writes the resolved ID to the x-request-id response header, and adds requestId to the request log record.

To make logs emitted by route handlers inherit the same requestId, configure LogTape with contextLocalStorage as described in implicit contexts:

import {  } from "node:async_hooks";
import {  } from "@logtape/logtape";
import {  } from "@logtape/hono";

await ({
  : {},
  : [],
  : new (),
});

const  = ({ : true });

The context is established even when skip suppresses the request log, so application logs inside the skipped request can still carry the same request ID.

You can customize request ID headers and include additional request fields:

const  = ({
  : {
    : {
      : ["x-correlation-id", "x-request-id"],
      : "x-request-id",
    },
    : ["requestId", "method", "path", "userAgent"],
    : () => ({ : .. }),
  },
});

Predefined formats

The middleware supports structured presets and text presets:

  • "structured-combined": Structured request properties (default)
  • "structured-common": Structured request properties without referrer/userAgent
  • "combined": Deprecated alias for "structured-combined"
  • "common": Deprecated alias for "structured-common"
  • "morgan-combined": Morgan-compatible Apache combined access log output
  • "morgan-common": Morgan-compatible Apache common access log output
  • "dev": Concise output for development (e.g., GET /path 200 1.234 ms - 123)
  • "short": Shorter format with URL
  • "tiny": Minimal output

Hono does not expose socket-level fields consistently across runtimes. The Morgan-compatible text formats use X-Forwarded-For for the remote address and render unavailable fields, such as the HTTP version, as -.

Custom format function

You can also provide a custom format function that returns either a string or an object with structured properties:

const  = ({
  : (, ) => ({
    : ..,
    : ..,
    : ..,
    : ,
  }),
});

Structured logging output

When using the "structured-combined" format (default), the middleware logs structured data that includes:

  • method: HTTP request method
  • url: Request URL
  • path: Request path
  • status: HTTP response status code
  • responseTime: Response time in milliseconds
  • contentLength: Response content-length header value
  • userAgent: User-Agent header value
  • referrer: Referrer header value

Fastify

The @logtape/fastify package is available since LogTape 1.3.0.

Fastify is a fast and low-overhead web framework for Node.js. LogTape provides a Pino-compatible logger adapter through the @logtape/fastify package, allowing you to use LogTape as Fastify's logging backend with seamless integration:

deno add jsr:@logtape/fastify
npm add @logtape/fastify
pnpm add @logtape/fastify
yarn add @logtape/fastify
bun add @logtape/fastify

Here's an example of using LogTape with Fastify:

import { ,  } from "@logtape/logtape";
import {  } from "@logtape/fastify";
import  from "fastify";

await ({
  : { : () },
  : [
    { : ["fastify"], : ["console"], : "info" }
  ],
});

const  = ({
  : (),
});

.("/", async (, ) => {
  // Uses LogTape under the hood with request context
  ..("Handling request");
  return { : "world" };
});

await .({ : 3000 });

Custom category

You can specify a custom category for the logger:

const  = ({
  : ["myapp", "http"],
});

Child loggers

Fastify automatically creates child loggers with request-scoped bindings (like reqId). These bindings are passed to LogTape's structured logging:

const  = ({
  : (),
});

.("/users/:id", async (, ) => {
  // Child logger automatically includes reqId and any additional bindings
  ..({ : . }, "Fetching user");
  return { : "data" };
});

Pino method signatures

The adapter supports all Pino-style logging signatures:

// Simple message
.("Hello world");

// Printf-style interpolation
.("User %s logged in %d times", "alice", 3);

// Object with message
.({ : 123, : "login" }, "User logged in");

// Object with msg property
.({ : "User logged in", : 123 });

// Object only
.({ : { : "value" } });

GraphQL Yoga

The @logtape/graphql-yoga package is available since LogTape 2.3.0.

GraphQL Yoga is a GraphQL server that works across JavaScript runtimes. LogTape provides a Yoga logger adapter through the @logtape/graphql-yoga package, allowing you to use LogTape as Yoga's logging backend:

deno add jsr:@logtape/graphql-yoga
npm add @logtape/graphql-yoga
pnpm add @logtape/graphql-yoga
yarn add @logtape/graphql-yoga
bun add @logtape/graphql-yoga

Here's an example of using LogTape with GraphQL Yoga:

import { ,  } from "@logtape/logtape";
import {  } from "@logtape/graphql-yoga";
import { ,  } from "graphql-yoga";

await ({
  : { : () },
  : [
    { : ["graphql-yoga"], : ["console"], : "debug" }
  ],
});

const  = ({
  : ({
    : /* GraphQL */ `
      type Query {
        hello: String!
      }
    `,
    : {
      : {
        : () => "world",
      },
    },
  }),
  : (),
});

Custom category

You can specify a custom category for the logger:

const  = ({
  : ["myapp", "graphql"],
});

Custom log levels

Yoga exposes debug, info, warn, and error logger methods. By default, these map to LogTape's debug, info, warning, and error levels. You can customize the mapping:

const  = ({
  : {
    : "trace",
    : "error",
  },
});

Koa

The @logtape/koa package is available since LogTape 1.3.0.

Koa is a modern, lightweight web framework for Node.js that uses async/await throughout. LogTape provides a Koa middleware adapter through the @logtape/koa package, which provides HTTP request logging using LogTape as the backend, as an alternative to koa-logger:

deno add jsr:@logtape/koa
npm add @logtape/koa
pnpm add @logtape/koa
yarn add @logtape/koa
bun add @logtape/koa

Here's an example of using LogTape with Koa:

import { ,  } from "@logtape/logtape";
import {  } from "@logtape/koa";
import  from "koa";

await ({
  : { : () },
  : [
    { : ["koa"], : ["console"], : "info" }
  ],
});

const  = new ();
.(());

.(() => {
  . = { : "world" };
});

.(3000);

Custom category

You can specify a custom category for the logger:

const  = ({
  : ["myapp", "http"],
});

Options

The middleware accepts the following options:

const  = ({
  : ["myapp", "http"],  // Custom category (default: ["koa"])
  : "debug",                // Log level (default: "info")
  : "dev",                 // Predefined format (default: "combined")
  : () => . === "/health",  // Skip health check endpoint
  : false,             // Log after response (default: false)
  : true,                 // Add requestId to request-scoped logs
});

Request context

Set context: true to add request-scoped correlation fields. By default, the middleware reads the x-request-id request header, generates an ID when the header is missing, writes the resolved ID to the x-request-id response header, and adds requestId to the request log record.

To make logs emitted by route handlers inherit the same requestId, configure LogTape with contextLocalStorage as described in implicit contexts:

import {  } from "node:async_hooks";
import {  } from "@logtape/logtape";
import {  } from "@logtape/koa";

await ({
  : {},
  : [],
  : new (),
});

const  = ({ : true });

The context is established even when skip suppresses the request log, so application logs inside the skipped request can still carry the same request ID.

You can customize request ID headers and include additional request fields:

const  = ({
  : {
    : {
      : ["x-correlation-id", "x-request-id"],
      : "x-request-id",
    },
    : ["requestId", "method", "path", "remoteAddr"],
    : () => ({ : . }),
  },
});

Predefined formats

The middleware supports structured presets and text presets:

  • "structured-combined": Structured request properties (default)
  • "structured-common": Structured request properties without referrer/userAgent
  • "combined": Deprecated alias for "structured-combined"
  • "common": Deprecated alias for "structured-common"
  • "morgan-combined": Morgan-compatible Apache combined access log output
  • "morgan-common": Morgan-compatible Apache common access log output
  • "dev": Concise output for development (e.g., GET /path 200 1.234 ms - 123)
  • "short": Shorter format with remote address
  • "tiny": Minimal output

Custom format function

You can also provide a custom format function that returns either a string or an object with structured properties:

const  = ({
  : (, ) => ({
    : .,
    : .,
    : .,
    : ,
  }),
});

Structured logging output

When using the "structured-combined" format (default), the middleware logs structured data that includes:

  • method: HTTP request method
  • url: Request URL
  • path: Request path
  • status: HTTP response status code
  • responseTime: Response time in milliseconds
  • contentLength: Response content-length
  • remoteAddr: Remote client address
  • userAgent: User-Agent header value
  • referrer: Referrer header value

Proxy configuration

When your Koa application runs behind a reverse proxy, you need to set app.proxy = true in your Koa configuration for remoteAddr to correctly reflect the client's IP address from the X-Forwarded-For header. See Koa's proxy documentation for details.

SvelteKit

SvelteKit can use LogTape in both server-side hooks and API routes. Unlike Express, Elysia, Hono, and Koa, LogTape does not provide a dedicated SvelteKit adapter. Use hooks with withContext() for global request logging:

src/hooks.server.ts
import {
  ,
  ,
  ,
  ,
} from "@logtape/logtape";
import type {  } from "@sveltejs/kit";
import {  } from "node:async_hooks";

await ({
  : { : () },
  : [
    { : ["sveltekit"], : ["console"], : "info" }
  ],
  : new (),
});

const  = (["sveltekit"]);

export const :  = async ({ ,  }) => {
  const  = .();
  const  = .();

  return await ({
    ,
    : ..,
    : ..,
    : ...("user-agent")
  }, async () => {
    .("Request started", {
      : ..,
      : ..,
      
    });

    try {
      const  = await ();

      const  = .() - ;
      .("Request completed", {
        : .,
        ,
        
      });

      return ;
    } catch () {
      const  = .() - ;

      .("Request error", {
        : {
          :  instanceof  ? . : "Unknown",
          :  instanceof  ? . : ()
        },
        ,
        
      });

      throw ;
    }
  });
};

And in individual API routes:

src/routes/api/users/+server.ts
import {  } from "@logtape/logtape";
import { , type  } from "@sveltejs/kit";

const  = (["sveltekit", "api"]);

export const :  = async ({  }) => {
  .("Users API called");

  try {
    const  = await ();

    .("Users retrieved successfully", {
      : .
    });

    return ();
  } catch () {
    .("Failed to retrieve users", {
      :  instanceof  ? . : ()
    });

    return ({ : "Internal server error" }, { : 500 });
  }
};

async function (): <any[]> {
  return [];
}

Third-party log integration

This API is available since LogTape 1.1.0.

When integrating logs from external systems, you may want to preserve their original timestamps and metadata. LogTape provides the Logger.emit() method for this purpose, giving you full control over log record fields.

Basic usage

The Logger.emit() method accepts a log record without the category field:

import {  } from "@logtape/logtape";

const  = (["my-app", "external"]);

// Forward external log with preserved timestamp
.({
  : .,
  : "info",
  : ["External system event"],
  : "External system event",
  : {
    : "external-service",
    : .,
  },
});

Best practices

  1. Request ID correlation: Always generate and use request IDs to correlate logs across your application:

    const requestId = crypto.randomUUID(); // Preferred method
    // For environments without crypto.randomUUID():
    const requestId = Math.random().toString(36).substring(2, 11);
  2. Context management: Use withContext() to automatically include request metadata in all logs within the request scope.

  3. Error handling: Always log errors with structured information including exception object and request context.

  4. Performance monitoring: Log request duration and response sizes to monitor application performance.

  5. Security considerations: Be careful not to log sensitive information like passwords, tokens, or personal data. Use the @logtape/redaction package for data sanitization patterns.

  6. Environment-specific configuration: Configure different log levels and sinks for development vs production environments to balance verbosity with performance.

Released under the MIT License.