Skip to main content
Sonamu uses LogTape to provide structured logging. You can configure logging behavior with the logging option in sonamu.config.ts.

What is LogTape?

LogTape is a structured logging library for TypeScript. Instead of simple string logs, it records logs as structured data.

Regular Logging vs Structured Logging

Regular logging (string):
console.log("User login failed: email=user@example.com, reason=invalid_password");
// → Requires string parsing for analysis
Structured logging (LogTape):
logger.error("User login failed", {
  email: "user@example.com",
  reason: "invalid_password",
});
// → Already structured data, ready for analysis

Benefits of Structured Logging

  1. Easy search/filtering - Easily find logs matching specific conditions
  2. Analyzable - Aggregate log data and generate statistics
  3. Type safe - TypeScript validates log data structure
  4. Flexible output - Output same log in multiple formats (console, file, external services)

Core Concepts

1. Sink (Output Destination)

Where logs are ultimately recorded.
{
  console: getConsoleSink(),        // Terminal output
  file: getFileSink("app.log"),     // File storage
  sentry: sentrySink,                // External service
}
A single log can be recorded to multiple Sinks simultaneously:
  • Console: Real-time monitoring
  • File: Permanent storage
  • Sentry: Error notifications

2. Filter (Filtering Conditions)

Determines which logs to record.
{
  "api-only": (record) => record.url.startsWith("/api"),
  "errors": (record) => record.level >= "error",
}
Without Filters, all logs are output, degrading performance and readability.

3. Logger (Logger Configuration)

Connects categories, Sinks, and Filters.
{
  category: ["fastify"],           // What category of log?
  sinks: ["console", "file"],      // Where to record?
  filters: ["api-only"],           // What conditions to filter by?
  lowestLevel: "info",             // Starting from what level?
}

Sink-Filter-Logger Relationship

Example:
// 1. Fastify generates log
logger.info("Request completed", { url: "/api/user" });

// 2. Logger check: Is category ["fastify"]? ✓
// 3. Filter check: Does url start with /api? ✓
// 4. Sink output: Record to console and file

Log Level Selection Guide

Log levels indicate the importance of logs.

Meaning and Usage of Each Level

1. debug
  • Meaning: Detailed debugging information
  • When to use: Problem tracking during development
  • Examples: Function call order, variable values
logger.debug("Processing order", { orderId, items });
2. info (default)
  • Meaning: General operational information
  • When to use: Confirming normal operation
  • Examples: HTTP requests/responses, task completion
logger.info("User logged in", { userId });
3. warning
  • Meaning: Potential problem warning
  • When to use: Not an error but needs attention
  • Examples: Slow responses, retries occurring
logger.warn("Slow query detected", { duration: 5000 });
4. error
  • Meaning: Recoverable error
  • When to use: Exception handling, recoverable failures
  • Examples: Validation failure, API call failure
logger.error("Payment failed", { error: "card_declined" });
5. fatal
  • Meaning: Critical failure (server shutdown level)
  • When to use: Unrecoverable serious problems
  • Examples: DB connection failure, out of memory
logger.fatal("Database connection lost", { error });
EnvironmentMinimum LevelReason
DevelopmentdebugNeed all information
StaginginfoCheck operational info
ProductionwarningPerformance consideration
Level Hierarchy:
Setting a lower level outputs all higher levels. Example: Setting lowestLevel: "warning" outputs warning, error, and fatal.

Why is it Designed This Way?

1. Default Values are Safe

Sonamu’s default configuration:
{
  category: ["fastify"],
  sinks: ["fastify-console"],
  lowestLevel: "info",
  filters: ["fastify-console"],  // /api only, exclude healthcheck
}
Reasons:
  • ✅ Only /api paths logged → Excludes unnecessary static file requests
  • info level → Not too much, not too little
  • ✅ Excludes healthcheck → Excludes repetitive requests from monitoring systems

2. Extensible

You can add custom settings on top of default settings:
logging: {
  // Keep default settings while
  sinks: {
    errorFile: getFileSink("errors.log"),  // Additional Sink
  },
  loggers: [
    // Additional Logger (default Fastify logger is preserved)
    {
      category: ["app"],
      sinks: ["console"],
      lowestLevel: "debug",
    },
  ],
}

3. Minimal Performance Impact

Block unnecessary logs early through Filters:
// ❌ No Filter: Process all logs → Slow
loggersink (output all logs)

// ✅ With Filter: Process only needed logs → Fast
loggerfilter (block unnecessary logs) → sink (output only needed logs)

Default Configuration

If you omit the logging option, default settings are automatically applied.
import { defineConfig } from "sonamu";

export default defineConfig({
  // Use defaults when logging is omitted
  server: {
    // ...
  },
});
Default behavior:
  • Logs Fastify requests/responses to console
  • Only logs /api/* paths (excluding healthcheck)
  • Pretty format output (timestamp, category, level)

logging Options

type SonamuLoggingOptions<TSinkId extends string, TFilterId extends string> = {
  fastifyCategory?: readonly string[];
  sinks?: Record<TSinkId, Sink>;
  filters?: Record<TFilterId, FilterLike>;
  loggers?: LoggerConfig<TSinkId, TFilterId>[];
};

Disable Logging

export default defineConfig({
  logging: false,  // Disable all logging
  
  server: {
    // ...
  },
});

fastifyCategory

Specifies the category to use for Fastify HTTP logging. Type: readonly string[] Default: ["fastify"]
export default defineConfig({
  logging: {
    fastifyCategory: ["myapp", "http"],  // Custom category
  },
  
  server: {
    // ...
  },
});
Category format: ["a", "b", "c"] array represents hierarchy Log output example:
[2025-01-09 12:34:56] [myapp.http] INFO: [GET:200] /api/user/list - Request completed

sinks

Add destinations (sinks) for log output. Type: Record<string, Sink>
import { defineConfig } from "sonamu";
import { getConsoleSink, getFileSink } from "@logtape/logtape";

export default defineConfig({
  logging: {
    sinks: {
      console: getConsoleSink(),
      file: getFileSink("logs/app.log"),
    },
  },
  
  server: {
    // ...
  },
});
The name "fastify-console" is the default sink automatically generated by Sonamu. Adding with this name will overwrite the default sink.

filters

Add conditions for selective log filtering. Type: Record<string, FilterLike>
import { defineConfig } from "sonamu";
import type { LogRecord } from "@logtape/logtape";

export default defineConfig({
  logging: {
    filters: {
      "errors-only": (record: LogRecord) => record.level >= "error",
    },
  },
  
  server: {
    // ...
  },
});
The name "fastify-console" is the default filter automatically generated by Sonamu. Adding with this name will overwrite the default filter.

loggers

Configure which sinks and filters to use per category. Type: LoggerConfig[]
import { defineConfig } from "sonamu";
import { getConsoleSink } from "@logtape/logtape";

export default defineConfig({
  logging: {
    sinks: {
      console: getConsoleSink(),
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["console"],
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    // ...
  },
});
If there’s a logger configuration for the category set in fastifyCategory, Sonamu won’t add the default logger.

Basic Examples

Minimal Configuration (Using Defaults)

import { defineConfig } from "sonamu";

export default defineConfig({
  server: {
    listen: { port: 1028 },
  },
});

Disable Logging

import { defineConfig } from "sonamu";

export default defineConfig({
  logging: false,
  
  server: {
    listen: { port: 1028 },
  },
});

Custom Category

import { defineConfig } from "sonamu";

export default defineConfig({
  logging: {
    fastifyCategory: ["app", "server", "http"],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

Default Behavior Details

Sonamu automatically configures logging as follows:

1. Fastify Sink Auto-Generation

// Auto-generated "fastify-console" sink
{
  type: "console",
  formatter: prettyFormatter({
    timestamp: "time",
    categoryWidth: 20,
    categoryTruncate: "middle",
  }),
}
Features:
  • Shows HTTP method and response code: [GET:200]
  • Shows request URL: /api/user/list
  • Colors distinguish levels

2. Fastify Filter Auto-Generation

// Auto-generated "fastify-console" filter
(record) => {
  // Only paths starting with /api, excluding healthcheck
  return request.url.startsWith("/api") && 
         request.url !== "/api/healthcheck";
}

3. Logger Auto-Generation

// Auto-generated logger
{
  category: ["fastify"],  // Or user-specified fastifyCategory
  sinks: ["fastify-console"],
  lowestLevel: "info",
  filters: ["fastify-console"],
}

4. Meta Logger Disabled

// Auto-added logger
{
  category: ["logtape", "meta"],
  lowestLevel: "fatal",  // Hide LogTape internal logs
}

Log Levels

LogTape log levels (in ascending order):
  1. debug - Detailed debugging information
  2. info - General information (default)
  3. warning - Warning
  4. error - Error
  5. fatal - Fatal error

Practical Examples

import { defineConfig } from "sonamu";

export default defineConfig({
  logging: {
    fastifyCategory: ["fastify"],
    loggers: [
      {
        category: ["fastify"],
        sinks: ["fastify-console"],
        lowestLevel: "debug",  // Check all logs
        filters: ["fastify-console"],
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});
Features:
  • debug level to check all details
  • Console output for real-time viewing
  • Fast feedback loop

Adding File Logging

import { defineConfig } from "sonamu";
import { getConsoleSink, getFileSink } from "@logtape/logtape";

export default defineConfig({
  logging: {
    sinks: {
      console: getConsoleSink(),
      file: getFileSink("logs/app.log"),
      errorFile: getFileSink("logs/errors.log"),
    },
    
    filters: {
      "errors-only": (record) => record.level >= "error",
    },
    
    loggers: [
      // Console: all logs
      {
        category: ["fastify"],
        sinks: ["console"],
        lowestLevel: "info",
      },
      // File: all logs
      {
        category: ["fastify"],
        sinks: ["file"],
        lowestLevel: "info",
      },
      // Error file: errors only
      {
        category: ["fastify"],
        sinks: ["errorFile"],
        filters: ["errors-only"],
        lowestLevel: "error",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

Production Configuration

import { defineConfig } from "sonamu";
import { getConsoleSink, getFileSink } from "@logtape/logtape";

export default defineConfig({
  logging: {
    sinks: {
      console: getConsoleSink(),
      errorLog: getFileSink("logs/errors.log"),
    },
    
    filters: {
      "errors": (record) => record.level >= "error",
    },
    
    loggers: [
      // Console: warning and above only
      {
        category: ["fastify"],
        sinks: ["console"],
        lowestLevel: "warning",
      },
      // File: errors only
      {
        category: ["fastify"],
        sinks: ["errorLog"],
        filters: ["errors"],
        lowestLevel: "error",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

Fastify Logging Auto-Integration

Sonamu automatically integrates Fastify logging with the @logtape/fastify package.
// Automatically performed internally by Sonamu
import { getLogTapeFastifyLogger } from "@logtape/fastify";

const server = fastify({
  logger: getLogTapeFastifyLogger({
    category: config.logging?.fastifyCategory ?? ["fastify"],
  }),
});
Automatically logged information:
  • HTTP requests (method, URL)
  • Response codes
  • Response times
  • Error stack traces

Important Notes

1. Overwriting Default sink/filter

// ❌ Bad example: Losing default settings
logging: {
  sinks: {
    "fastify-console": getConsoleSink(),  // Loses default formatter
  },
}

// ✅ Good example: Use different name
logging: {
  sinks: {
    "my-console": getConsoleSink(),
  },
}

2. logger Option When Logging is Disabled

// ❌ Bad example: Conflict
logging: false,
server: {
  fastify: {
    logger: true,  // Conflicts with logging: false
  },
}

// ✅ Good example: Maintain consistency
logging: false,
server: {
  fastify: {
    // Don't set logger (use default)
  },
}

3. Category Consistency

// ✅ Good example: Consistent category
fastifyCategory: ["app", "http"]

// ❌ Bad example: Meaningless category
fastifyCategory: ["a", "b", "c"]

Next Steps