Skip to main content
This covers debugging methods using Sonamu’s LogTape-based logging system.

What is LogTape?

Sonamu provides structured logging using @logtape/logtape. Key features:
  • Category-based logging
  • Various log levels (debug, info, warn, error, fatal)
  • Custom Sink/Filter support
  • Fastify integration

Basic Logging

Using getLogger

import { getLogger } from "@logtape/logtape";

const logger = getLogger(["myapp", "user"]);

logger.info("Starting user creation", { email: "user@example.com" });
logger.debug("Detailed information", { step: 1, data: someData });
logger.error("Error occurred", { error: err.message });

Log Levels

const logger = getLogger(["myapp", "api"]);

logger.debug("Debug information");     // Development only
logger.info("General information");    // Production as well
logger.warn("Warning");               // Needs attention
logger.error("Error");                // Error occurred
logger.fatal("Fatal error");          // Unrecoverable error

Logging Configuration

sonamu.config.ts Configuration

export default {
  logging: {
    // Fastify logging category (default: ["fastify"])
    fastifyCategory: ["api", "request"],

    // Add custom Sinks
    sinks: {
      "file": getFileSink("logs/app.log")
    },

    // Add custom Filters
    filters: {
      "production-only": (record) =>
        process.env.NODE_ENV === "production"
    },

    // Logger configuration
    loggers: [
      {
        category: ["myapp"],
        sinks: ["console", "file"],
        lowestLevel: "info"
      }
    ]
  }
} satisfies SonamuConfig;

Disabling Logging

export default {
  logging: false  // Disable all logging
} satisfies SonamuConfig;

Category-Based Logging

Hierarchical Categories

// Category: ["myapp", "user", "auth"]
const authLogger = getLogger(["myapp", "user", "auth"]);

authLogger.info("Login attempt", { email });
authLogger.error("Authentication failed", { reason });

// Category: ["myapp", "order", "payment"]
const paymentLogger = getLogger(["myapp", "order", "payment"]);

paymentLogger.info("Payment started", { orderId, amount });
paymentLogger.warn("Payment delayed", { orderId });

Automatic Categories for Model/Frame

Sonamu automatically assigns categories to Models and Frames:
// UserModel -> ["sonamu", "model", "user"]
class UserModelClass extends BaseModel {
  async createUser(email: string) {
    // Use automatic logger
    this.logger.info("Creating user", { email });
    return this.save({ email });
  }
}

// PaymentFrame -> ["sonamu", "frame", "payment"]
class PaymentFrameClass extends BaseFrame {
  async processPayment(amount: number) {
    this.logger.info("Processing payment", { amount });
    // ...
  }
}

Fastify Logging

API Request Logging

Sonamu automatically logs requests to /api paths:
# Log output example
[api] [GET:200] /api/users?page=1 - Request completed
[api] [POST:201] /api/users - Request completed
[api] [PUT:400] /api/users/123 - Request completed

Excluding Healthcheck

/api/healthcheck is automatically excluded from logging.

Debugging Patterns

Function Entry/Exit Logging

async function complexOperation(params: Params) {
  const logger = getLogger(["myapp", "operation"]);

  logger.debug("Operation started", { params });

  try {
    const result = await doSomething(params);
    logger.debug("Operation completed", { result });
    return result;
  } catch (error) {
    logger.error("Operation failed", { error, params });
    throw error;
  }
}

Conditional Verbose Logging

const DEBUG = process.env.DEBUG === "true";

async function processData(data: Data[]) {
  const logger = getLogger(["myapp", "processor"]);

  logger.info("Data processing started", { count: data.length });

  for (const item of data) {
    if (DEBUG) {
      logger.debug("Processing item", { item });
    }

    await processItem(item);
  }

  logger.info("Data processing completed");
}

Error Tracking

async function apiCall() {
  const logger = getLogger(["myapp", "api"]);

  try {
    const response = await fetch(url);

    if (!response.ok) {
      logger.warn("API response failed", {
        status: response.status,
        url
      });
    }

    return response.json();
  } catch (error) {
    logger.error("API call error", {
      error: error.message,
      stack: error.stack,
      url
    });
    throw error;
  }
}

Custom Sinks

File Sink

import { getFileSink } from "@logtape/logtape";

export default {
  logging: {
    sinks: {
      "error-file": getFileSink("logs/errors.log")
    },
    loggers: [
      {
        category: ["myapp"],
        sinks: ["error-file"],
        lowestLevel: "error"  // Only log errors to file
      }
    ]
  }
} satisfies SonamuConfig;

Custom Formatter

import { getConsoleSink } from "@logtape/logtape";
import { getPrettyFormatter } from "@logtape/pretty";

const customSink = getConsoleSink({
  formatter: getPrettyFormatter({
    timestamp: "date-time-timezone",  // Include timezone
    categoryWidth: 30,                 // Category width
    categoryTruncate: "end",          // Truncation position
    colors: true                       // Enable colors
  })
});

export default {
  logging: {
    sinks: {
      "custom-console": customSink
    }
  }
} satisfies SonamuConfig;

Custom Filters

Environment-Based Filtering

export default {
  logging: {
    filters: {
      "production-only": (record) => {
        return process.env.NODE_ENV === "production";
      },
      "no-debug-in-prod": (record) => {
        if (process.env.NODE_ENV === "production") {
          return record.level !== "debug";
        }
        return true;
      }
    },
    loggers: [
      {
        category: ["myapp"],
        filters: ["no-debug-in-prod"]
      }
    ]
  }
} satisfies SonamuConfig;

Category Filtering

const apiOnlyFilter = (record: LogRecord) => {
  return record.category[0] === "api";
};

export default {
  logging: {
    filters: {
      "api-only": apiOnlyFilter
    },
    loggers: [
      {
        category: ["api"],
        filters: ["api-only"],
        sinks: ["api-file"]
      }
    ]
  }
} satisfies SonamuConfig;

Logging Level Control

Separating Development/Production

const isDev = process.env.NODE_ENV === "development";

export default {
  logging: {
    loggers: [
      {
        category: ["myapp"],
        lowestLevel: isDev ? "debug" : "info"  // Development: debug, Production: info
      }
    ]
  }
} satisfies SonamuConfig;

Category-Specific Levels

export default {
  logging: {
    loggers: [
      {
        category: ["myapp", "critical"],
        lowestLevel: "warn"  // Warnings and above only
      },
      {
        category: ["myapp", "debug"],
        lowestLevel: "debug"  // Include debug
      },
      {
        category: ["myapp"],
        lowestLevel: "info"  // Default
      }
    ]
  }
} satisfies SonamuConfig;

Structured Logging

Including Context

const logger = getLogger(["myapp", "user"]);

logger.info("User action", {
  userId: user.id,
  action: "create",
  timestamp: new Date().toISOString(),
  metadata: {
    ip: request.ip,
    userAgent: request.headers["user-agent"]
  }
});

Logging Error Objects

try {
  await riskyOperation();
} catch (error) {
  logger.error("Operation failed", {
    error: {
      name: error.name,
      message: error.message,
      stack: error.stack,
      cause: error.cause
    }
  });
}

Performance Logging

Measuring Execution Time

async function measureOperation() {
  const logger = getLogger(["myapp", "perf"]);
  const start = performance.now();

  try {
    const result = await operation();
    const duration = performance.now() - start;

    logger.info("Operation completed", {
      duration: `${duration.toFixed(2)}ms`,
      result
    });

    if (duration > 1000) {
      logger.warn("Slow operation detected", { duration });
    }

    return result;
  } catch (error) {
    const duration = performance.now() - start;
    logger.error("Operation failed", { duration, error });
    throw error;
  }
}

Logging Best Practices

1. Meaningful Messages

// Bad example
logger.info("done");
logger.error("error");

// Good example
logger.info("User creation completed", { userId, email });
logger.error("Database connection failed", {
  host,
  error: err.message
});

2. Appropriate Log Levels

// Appropriate usage by level
logger.debug("Variable value check", { value });           // Development only
logger.info("API request completed", { statusCode });      // General information
logger.warn("Retry needed", { attempt, maxRetries }); // Needs attention
logger.error("Processing failed", { error });              // Error
logger.fatal("Server shutdown", { reason });             // Fatal

3. Excluding Sensitive Information

// Logging sensitive information
logger.info("Login", {
  email,
  password  // Bad
});

// Masking sensitive information
logger.info("Login", {
  email,
  hasPassword: !!password  // Good
});

4. Category Consistency

// Hierarchical category structure
["myapp", "user", "auth"]
["myapp", "user", "profile"]
["myapp", "order", "payment"]
["myapp", "order", "shipping"]

// Inconsistent categories
["auth"]
["user-profile"]
["payments"]

Debugging Tips

1. Controlling Level with Environment Variables

# .env
LOG_LEVEL=debug
export default {
  logging: {
    loggers: [
      {
        category: ["myapp"],
        lowestLevel: process.env.LOG_LEVEL || "info"
      }
    ]
  }
} satisfies SonamuConfig;

2. Conditional Verbose Logging

# Debug specific features only
DEBUG=user,payment pnpm dev
const DEBUG_MODULES = process.env.DEBUG?.split(",") || [];

function shouldDebug(module: string) {
  return DEBUG_MODULES.includes(module);
}

if (shouldDebug("user")) {
  logger.debug("Detailed user information", { user });
}

3. Grouping Logs

logger.info("=== Order Processing Started ===", { orderId });
logger.info("1. Checking inventory", { available });
logger.info("2. Processing payment", { amount });
logger.info("3. Preparing shipment", { address });
logger.info("=== Order Processing Completed ===", { orderId });