๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Sonamu๋Š” Fastify์˜ HTTP ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ž๋™์œผ๋กœ ๋กœ๊น…ํ•ฉ๋‹ˆ๋‹ค. @logtape/fastify ํŒจํ‚ค์ง€๋ฅผ ํ†ตํ•ด LogTape์™€ ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

HTTP ๋กœ๊น…์ด ์ค‘์š”ํ•œ ์ด์œ 

HTTP ๋กœ๊ทธ๋Š” ์›น ์„œ๋น„์Šค ์šด์˜์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

1. ๋ฌธ์ œ ํ•ด๊ฒฐ

์—๋Ÿฌ ์ถ”์ :
[ERROR] POST /api/order โ†’ 500
โ†’ ์–ด๋–ค API๊ฐ€ ์‹คํŒจํ–ˆ๋Š”๊ฐ€?
โ†’ ์–ธ์ œ ๋ฐœ์ƒํ–ˆ๋Š”๊ฐ€?
โ†’ ์–ด๋–ค ์‚ฌ์šฉ์ž๊ฐ€ ์˜ํ–ฅ๋ฐ›์•˜๋Š”๊ฐ€?
๋ฒ„๊ทธ ์žฌํ˜„:
์‚ฌ์šฉ์ž ๋ณด๊ณ : "๊ฒฐ์ œ๊ฐ€ ์•ˆ ๋ผ์š”"
โ†’ ๋กœ๊ทธ ํ™•์ธ: POST /api/payment โ†’ 500
โ†’ ์˜ค๋ฅ˜ ๋‚ด์šฉ: "card_declined"
โ†’ ๋ฌธ์ œ ํŒŒ์•…: ์นด๋“œ ๊ฒฐ์ œ ์‹œ์Šคํ…œ ๋ฌธ์ œ

2. ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง

๋А๋ฆฐ ์š”์ฒญ ๋ฐœ๊ฒฌ:
[WARN] GET /api/user/list โ†’ 200 (3500ms)
โ†’ 3.5์ดˆ ๊ฑธ๋ฆผ, ๋„ˆ๋ฌด ๋А๋ฆผ!
โ†’ ์ฟผ๋ฆฌ ์ตœ์ ํ™” ํ•„์š”
๋ณ‘๋ชฉ ํ˜„์ƒ ๊ฐ์ง€:
[INFO] GET /api/product/123 โ†’ 200 (100ms)
[INFO] GET /api/product/123 โ†’ 200 (100ms)
[INFO] GET /api/product/123 โ†’ 200 (100ms)
โ†’ ๊ฐ™์€ ์š”์ฒญ์ด ๋ฐ˜๋ณต๋จ
โ†’ ์บ์‹ฑ ํ•„์š”

3. ๋ณด์•ˆ ๋ชจ๋‹ˆํ„ฐ๋ง

๋น„์ •์ƒ์ ์ธ ์ ‘๊ทผ ๊ฐ์ง€:
[WARN] GET /api/admin/users โ†’ 403 (from 192.168.1.100)
[WARN] GET /api/admin/users โ†’ 403 (from 192.168.1.100)
[WARN] GET /api/admin/users โ†’ 403 (from 192.168.1.100)
โ†’ ๊ถŒํ•œ ์—†๋Š” ์ ‘๊ทผ ์‹œ๋„ ๋ฐ˜๋ณต
โ†’ ๋ณด์•ˆ ์œ„ํ˜‘ ๊ฐ€๋Šฅ์„ฑ
๊ณต๊ฒฉ ํŒจํ„ด ๋ฐœ๊ฒฌ:
[ERROR] POST /api/login โ†’ 401
[ERROR] POST /api/login โ†’ 401
[ERROR] POST /api/login โ†’ 401
โ†’ ๋ธŒ๋ฃจํŠธ ํฌ์Šค ๊ณต๊ฒฉ ๊ฐ€๋Šฅ์„ฑ

4. ๋น„์ฆˆ๋‹ˆ์Šค ๋ถ„์„

์‚ฌ์šฉ ํŒจํ„ด ๋ถ„์„:
์˜ค๋Š˜์˜ ๋กœ๊ทธ:
GET /api/product/* โ†’ 1,000๊ฑด
GET /api/user/* โ†’ 500๊ฑด
POST /api/order โ†’ 200๊ฑด

โ†’ ์ œํ’ˆ ์กฐํšŒ๊ฐ€ ๊ฐ€์žฅ ๋งŽ์Œ
โ†’ ์ฃผ๋ฌธ ์ „ํ™˜์œจ 20%

๋ฌด์—‡์„ ๋กœ๊น…ํ•ด์•ผ ํ•˜๋‚˜?

ํ•„์ˆ˜ ๋กœ๊น… ํ•ญ๋ชฉ

1. ์š”์ฒญ ์ •๋ณด
  • HTTP ๋ฉ”์„œ๋“œ (GET, POST, PUT, DELETE)
  • URL ๊ฒฝ๋กœ
  • ํƒ€์ž„์Šคํƒฌํ”„
[INFO] GET /api/user/list
[INFO] POST /api/order
2. ์‘๋‹ต ์ •๋ณด
  • ์ƒํƒœ ์ฝ”๋“œ (200, 404, 500)
  • ์‘๋‹ต ์‹œ๊ฐ„
[INFO] GET /api/user/list โ†’ 200 (123ms)
[ERROR] POST /api/order โ†’ 500 (456ms)
3. ์—๋Ÿฌ ์ •๋ณด
  • ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
  • ์Šคํƒ ํŠธ๋ ˆ์ด์Šค
[ERROR] POST /api/payment โ†’ 500
Error: Card declined
  at PaymentService.charge
  at OrderController.create

๋กœ๊น…ํ•˜๋ฉด ์•ˆ ๋˜๋Š” ๊ฒƒ

๋ณด์•ˆ ์ •๋ณด:
// โŒ ์ ˆ๋Œ€ ๋กœ๊น… ๊ธˆ์ง€
POST /api/login
{ password: "user-password-123" }  // ๋น„๋ฐ€๋ฒˆํ˜ธ!

// โœ… ์•ˆ์ „ํ•˜๊ฒŒ
POST /api/login
{ userId: "[email protected]" }  // ๋น„๋ฐ€๋ฒˆํ˜ธ ์ œ์™ธ
๊ฐœ์ธ์ •๋ณด:
// โŒ ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ
[INFO] ์ฃผ๋ฏผ๋ฒˆํ˜ธ: 123456-1234567
[INFO] ์นด๋“œ ๋ฒˆํ˜ธ: 1234-5678-9012-3456

// โœ… ๋งˆ์Šคํ‚น ์ฒ˜๋ฆฌ
[INFO] ์ฃผ๋ฏผ๋ฒˆํ˜ธ: 123456-*******
[INFO] ์นด๋“œ ๋ฒˆํ˜ธ: 1234-****-****-3456

๋กœ๊ทธ ๋ ˆ๋ฒจ ์„ ํƒ

info - ์ •์ƒ ์š”์ฒญ
[INFO] GET /api/user/list โ†’ 200 (100ms)
[INFO] POST /api/order โ†’ 201 (200ms)
  • 2xx, 3xx ์ƒํƒœ ์ฝ”๋“œ
  • ์ •์ƒ์ ์ธ ๋น„์ฆˆ๋‹ˆ์Šค ํ๋ฆ„
warning - ์ฃผ์˜ ํ•„์š”
[WARN] GET /api/user/list โ†’ 200 (3500ms)  // ๋А๋ฆผ
[WARN] POST /api/order โ†’ 429 (50ms)      // Rate limit
  • ๋А๋ฆฐ ์‘๋‹ต (>1์ดˆ)
  • 4xx ์ƒํƒœ ์ฝ”๋“œ (ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜)
  • Rate limiting
error - ์„œ๋ฒ„ ์˜ค๋ฅ˜
[ERROR] POST /api/payment โ†’ 500
[ERROR] GET /api/user/123 โ†’ 500
  • 5xx ์ƒํƒœ ์ฝ”๋“œ
  • ์˜ˆ์™ธ ๋ฐœ์ƒ
  • DB ์—ฐ๊ฒฐ ์‹คํŒจ

์‘๋‹ต ์‹œ๊ฐ„ (responseTime) ํ™œ์šฉ

responseTime์€ API ์‘๋‹ต์— ๊ฑธ๋ฆฐ ์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ)์ž…๋‹ˆ๋‹ค.

์„ฑ๋Šฅ ๊ธฐ์ค€

์‹œ๊ฐ„ํ‰๊ฐ€์กฐ์น˜
< 100ms๋งค์šฐ ๋น ๋ฆ„์œ ์ง€
100-500ms์ ์ ˆ๋ชจ๋‹ˆํ„ฐ๋ง
500ms-1s๋А๋ฆผ์ตœ์ ํ™” ๊ฒ€ํ† 
> 1s๋งค์šฐ ๋А๋ฆผ์ฆ‰์‹œ ์ตœ์ ํ™”

์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ์„ค์ •

export default defineConfig({
  logging: {
    filters: {
      "fast": (record) => {
        const time = record.properties.responseTime as number | undefined;
        return time ? time < 100 : false;
      },
      "slow": (record) => {
        const time = record.properties.responseTime as number | undefined;
        return time ? time > 1000 : false;
      },
    },
    
    loggers: [
      // ๋น ๋ฅธ ์š”์ฒญ: debug ๋ ˆ๋ฒจ
      {
        category: ["fastify"],
        sinks: ["console"],
        filters: ["fast"],
        lowestLevel: "debug",
      },
      // ๋А๋ฆฐ ์š”์ฒญ: warning ๋ ˆ๋ฒจ
      {
        category: ["fastify"],
        sinks: ["console", "slowLog"],
        filters: ["slow"],
        lowestLevel: "warning",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

์„ฑ๋Šฅ ๋ถ„์„

// ๋กœ๊ทธ ๋ฐ์ดํ„ฐ๋กœ ํ†ต๊ณ„
/api/user/list
- ํ‰๊ท : 150ms
- ์ตœ์†Œ: 50ms
- ์ตœ๋Œ€: 500ms

/api/order/create
- ํ‰๊ท : 800ms
- ์ตœ์†Œ: 300ms  
- ์ตœ๋Œ€: 2000ms  // ๋ฌธ์ œ!

โ†’ order/create API ์ตœ์ ํ™” ํ•„์š”

์ž๋™ ๋กœ๊น…

๋ณ„๋„ ์„ค์ • ์—†์ด HTTP ๋กœ๊น…์ด ์ž๋™์œผ๋กœ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.
import { defineConfig } from "sonamu";

export default defineConfig({
  server: {
    listen: { port: 1028 },
  },
});
์ž๋™์œผ๋กœ ๋กœ๊น…๋˜๋Š” ์ •๋ณด:
  • HTTP ๋ฉ”์„œ๋“œ (GET, POST, PUT, DELETE ๋“ฑ)
  • ์š”์ฒญ URL
  • ์‘๋‹ต ์ฝ”๋“œ (200, 404, 500 ๋“ฑ)
  • ์‘๋‹ต ์‹œ๊ฐ„
  • ์—๋Ÿฌ ์ŠคํƒํŠธ๋ ˆ์ด์Šค (์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ)

๊ธฐ๋ณธ ๋กœ๊ทธ ํ˜•์‹

[2025-01-09 12:34:56] [fastify] INFO: [GET:200] /api/user/list - Request completed
[2025-01-09 12:34:57] [fastify] INFO: [POST:201] /api/user - User created
[2025-01-09 12:34:58] [fastify] ERROR: [POST:500] /api/order - Internal server error
ํ˜•์‹ ๊ตฌ์กฐ:
[ํƒ€์ž„์Šคํƒฌํ”„] [์นดํ…Œ๊ณ ๋ฆฌ] ๋ ˆ๋ฒจ: [๋ฉ”์„œ๋“œ:์ฝ”๋“œ] URL - ๋ฉ”์‹œ์ง€

๊ธฐ๋ณธ ๋™์ž‘

1. ๋กœ๊น… ๋Œ€์ƒ

๊ธฐ๋ณธ์ ์œผ๋กœ /api๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ๋กœ๋งŒ ๋กœ๊น…ํ•ฉ๋‹ˆ๋‹ค.
// โœ… ๋กœ๊น…๋จ
GET /api/user/list
POST /api/order
PUT /api/company/123

// โŒ ๋กœ๊น… ์•ˆ ๋จ
GET /                      // API๊ฐ€ ์•„๋‹˜
GET /assets/logo.png       // ์ •์  ํŒŒ์ผ
GET /api/healthcheck       // ํ—ฌ์Šค์ฒดํฌ๋Š” ์ œ์™ธ

2. ๊ธฐ๋ณธ sink

Sonamu๋Š” ์ž๋™์œผ๋กœ "fastify-console" sink๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ํŠน์ง•:
  • ์ฝ˜์†” ์ถœ๋ ฅ
  • ์˜ˆ์œ ํ˜•์‹ (pretty formatter)
  • HTTP ๋ฉ”์„œ๋“œ์™€ ์‘๋‹ต ์ฝ”๋“œ ํ‘œ์‹œ
  • ํƒ€์ž„์Šคํƒฌํ”„, ์นดํ…Œ๊ณ ๋ฆฌ ํ‘œ์‹œ

3. ๊ธฐ๋ณธ filter

"fastify-console" filter๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์กฐ๊ฑด:
  • URL์ด /api๋กœ ์‹œ์ž‘
  • /api/healthcheck ์ œ์™ธ

4. ๊ธฐ๋ณธ logger

{
  category: ["fastify"],
  sinks: ["fastify-console"],
  lowestLevel: "info",
  filters: ["fastify-console"],
}

fastifyCategory ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

Fastify ๋กœ๊น…์— ์‚ฌ์šฉํ•  ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
export default defineConfig({
  logging: {
    fastifyCategory: ["app", "server", "http"],
  },
  
  server: {
    listen: { port: 1028 },
  },
});
๋กœ๊ทธ ์ถœ๋ ฅ:
[2025-01-09 12:34:56] [app.server.http] INFO: [GET:200] /api/user/list - Request completed

LogRecord ํ”„๋กœํผํ‹ฐ

Fastify ๋กœ๊ทธ๋Š” ํŠน๋ณ„ํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
type FastifyLogRecord = LogRecord & {
  properties: {
    req?: FastifyRequest;      // ์š”์ฒญ ์ •๋ณด
    res?: FastifyReply;         // ์‘๋‹ต ์ •๋ณด
    responseTime?: number;      // ์‘๋‹ต ์‹œ๊ฐ„ (ms)
  };
};

req ํ”„๋กœํผํ‹ฐ

{
  method: "GET",
  url: "/api/user/list?page=1",
  originalUrl: "/api/user/list?page=1",
  headers: { ... },
  params: { ... },
  query: { page: "1" },
  body: { ... },
}

res ํ”„๋กœํผํ‹ฐ

{
  statusCode: 200,
  request: FastifyRequest,  // ์—ฐ๊ด€๋œ ์š”์ฒญ
}

responseTime ํ”„๋กœํผํ‹ฐ

{
  responseTime: 123  // ๋ฐ€๋ฆฌ์ดˆ (ms)
}

์ปค์Šคํ…€ Filter

URL ํŒจํ„ด ํ•„ํ„ฐ๋ง

import type { LogRecord } from "@logtape/logtape";
import type { FastifyRequest, FastifyReply } from "fastify";

export default defineConfig({
  logging: {
    filters: {
      "admin-only": (record: LogRecord) => {
        const req = record.properties.req as FastifyRequest | undefined;
        const res = record.properties.res as FastifyReply | undefined;
        const url = req?.url ?? res?.request.url;
        return url?.startsWith("/api/admin") ?? false;
      },
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["fastify-console"],
        filters: ["admin-only"],  // /api/admin/* ๋งŒ
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

์ƒํƒœ ์ฝ”๋“œ ํ•„ํ„ฐ๋ง

export default defineConfig({
  logging: {
    filters: {
      "errors-only": (record: LogRecord) => {
        const res = record.properties.res as FastifyReply | undefined;
        return res ? res.statusCode >= 400 : false;
      },
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["fastify-console"],
        filters: ["errors-only"],  // 4xx, 5xx๋งŒ
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

๋А๋ฆฐ ์š”์ฒญ ํ•„ํ„ฐ๋ง

export default defineConfig({
  logging: {
    filters: {
      "slow-requests": (record: LogRecord) => {
        const responseTime = record.properties.responseTime as number | undefined;
        return responseTime ? responseTime > 1000 : false;  // 1์ดˆ ์ด์ƒ
      },
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["fastify-console"],
        filters: ["slow-requests"],
        lowestLevel: "warning",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

์ปค์Šคํ…€ Formatter

๊ฐ„๋‹จํ•œ ํ˜•์‹

import { getConsoleSink, type LogRecord } from "@logtape/logtape";
import type { FastifyRequest, FastifyReply } from "fastify";

const simpleFormatter = (record: LogRecord): string => {
  const req = record.properties.req as FastifyRequest | undefined;
  const res = record.properties.res as FastifyReply | undefined;
  
  if (req) {
    return `${req.method} ${req.url}`;
  }
  
  if (res) {
    return `${res.request.method} ${res.request.url} โ†’ ${res.statusCode}`;
  }
  
  return record.message.join(" ");
};

export default defineConfig({
  logging: {
    sinks: {
      simple: getConsoleSink({ formatter: simpleFormatter }),
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["simple"],
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});
์ถœ๋ ฅ:
GET /api/user/list
POST /api/user โ†’ 201
GET /api/order/123 โ†’ 200

์ƒ์„ธ ํ˜•์‹

import { getConsoleSink } from "@logtape/logtape";
import type { LogRecord } from "@logtape/logtape";
import type { FastifyRequest, FastifyReply } from "fastify";

const detailedFormatter = (record: LogRecord): string => {
  const timestamp = record.timestamp.toISOString();
  const level = record.level.toUpperCase();
  
  const req = record.properties.req as FastifyRequest | undefined;
  const res = record.properties.res as FastifyReply | undefined;
  const responseTime = record.properties.responseTime as number | undefined;
  
  if (res) {
    const time = responseTime ? ` (${responseTime}ms)` : "";
    return `[${timestamp}] ${level}: ${res.request.method} ${res.request.url} โ†’ ${res.statusCode}${time}`;
  }
  
  if (req) {
    return `[${timestamp}] ${level}: ${req.method} ${req.url}`;
  }
  
  return `[${timestamp}] ${level}: ${record.message.join(" ")}`;
};

export default defineConfig({
  logging: {
    sinks: {
      detailed: getConsoleSink({ formatter: detailedFormatter }),
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["detailed"],
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});
์ถœ๋ ฅ:
[2025-01-09T03:34:56.123Z] INFO: GET /api/user/list
[2025-01-09T03:34:56.456Z] INFO: GET /api/user/list โ†’ 200 (123ms)
[2025-01-09T03:34:57.789Z] ERROR: POST /api/order โ†’ 500 (456ms)

JSON ํ˜•์‹

import { getConsoleSink } from "@logtape/logtape";
import type { LogRecord } from "@logtape/logtape";
import type { FastifyRequest, FastifyReply } from "fastify";

const jsonFormatter = (record: LogRecord): string => {
  const req = record.properties.req as FastifyRequest | undefined;
  const res = record.properties.res as FastifyReply | undefined;
  const responseTime = record.properties.responseTime as number | undefined;
  
  const log = {
    timestamp: record.timestamp.toISOString(),
    level: record.level,
    category: record.category,
    method: req?.method ?? res?.request.method,
    url: req?.url ?? res?.request.url,
    statusCode: res?.statusCode,
    responseTime,
  };
  
  return JSON.stringify(log);
};

export default defineConfig({
  logging: {
    sinks: {
      json: getConsoleSink({ formatter: jsonFormatter }),
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["json"],
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});
์ถœ๋ ฅ:
{"timestamp":"2025-01-09T03:34:56.123Z","level":"info","category":["fastify"],"method":"GET","url":"/api/user/list","statusCode":200,"responseTime":123}

์‹ค์ „ ์˜ˆ์‹œ

๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

import { defineConfig } from "sonamu";

export default defineConfig({
  logging: {
    fastifyCategory: ["fastify"],
    loggers: [
      {
        category: ["fastify"],
        sinks: ["fastify-console"],
        lowestLevel: "debug",  // ์ƒ์„ธ ๋กœ๊ทธ
        filters: ["fastify-console"],
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

ํ”„๋กœ๋•์…˜ ์„ค์ •

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

export default defineConfig({
  logging: {
    sinks: {
      console: getConsoleSink(),
      accessLog: getFileSink("logs/access.log"),
      errorLog: getFileSink("logs/errors.log"),
    },
    
    filters: {
      "errors": (record) => {
        const res = record.properties.res;
        return res ? res.statusCode >= 400 : false;
      },
    },
    
    loggers: [
      // ์ฝ˜์†”: warning ์ด์ƒ
      {
        category: ["fastify"],
        sinks: ["console"],
        lowestLevel: "warning",
      },
      // access.log: ๋ชจ๋“  ์š”์ฒญ
      {
        category: ["fastify"],
        sinks: ["accessLog"],
        lowestLevel: "info",
      },
      // error.log: ์—๋Ÿฌ๋งŒ
      {
        category: ["fastify"],
        sinks: ["errorLog"],
        filters: ["errors"],
        lowestLevel: "error",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

๊ฒฝ๋กœ๋ณ„ ๋กœ๊ทธ ๋ถ„๋ฆฌ

import { defineConfig } from "sonamu";
import { getFileSink } from "@logtape/logtape";
import type { LogRecord } from "@logtape/logtape";
import type { FastifyRequest, FastifyReply } from "fastify";

export default defineConfig({
  logging: {
    sinks: {
      adminLog: getFileSink("logs/admin.log"),
      userLog: getFileSink("logs/user.log"),
      publicLog: getFileSink("logs/public.log"),
    },
    
    filters: {
      "admin": (record: LogRecord) => {
        const req = record.properties.req as FastifyRequest | undefined;
        const res = record.properties.res as FastifyReply | undefined;
        const url = req?.url ?? res?.request.url;
        return url?.startsWith("/api/admin") ?? false;
      },
      "user": (record: LogRecord) => {
        const req = record.properties.req as FastifyRequest | undefined;
        const res = record.properties.res as FastifyReply | undefined;
        const url = req?.url ?? res?.request.url;
        return url?.startsWith("/api/user") ?? false;
      },
      "public": (record: LogRecord) => {
        const req = record.properties.req as FastifyRequest | undefined;
        const res = record.properties.res as FastifyReply | undefined;
        const url = req?.url ?? res?.request.url;
        return url?.startsWith("/api/public") ?? false;
      },
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["adminLog"],
        filters: ["admin"],
        lowestLevel: "info",
      },
      {
        category: ["fastify"],
        sinks: ["userLog"],
        filters: ["user"],
        lowestLevel: "info",
      },
      {
        category: ["fastify"],
        sinks: ["publicLog"],
        filters: ["public"],
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

๋А๋ฆฐ ์š”์ฒญ ๋ชจ๋‹ˆํ„ฐ๋ง

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

export default defineConfig({
  logging: {
    sinks: {
      console: getConsoleSink(),
      slowLog: getFileSink("logs/slow-requests.log"),
    },
    
    filters: {
      "slow": (record: LogRecord) => {
        const responseTime = record.properties.responseTime as number | undefined;
        return responseTime ? responseTime > 1000 : false;
      },
    },
    
    loggers: [
      // ์ผ๋ฐ˜ ๋กœ๊ทธ
      {
        category: ["fastify"],
        sinks: ["console"],
        lowestLevel: "info",
      },
      // ๋А๋ฆฐ ์š”์ฒญ (1์ดˆ ์ด์ƒ)
      {
        category: ["fastify"],
        sinks: ["slowLog"],
        filters: ["slow"],
        lowestLevel: "warning",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

๋กœ๊น… ๋น„ํ™œ์„ฑํ™”

์ „์ฒด ๋น„ํ™œ์„ฑํ™”

export default defineConfig({
  logging: false,  // ๋ชจ๋“  ๋กœ๊น… ๋น„ํ™œ์„ฑํ™”
  
  server: {
    listen: { port: 1028 },
  },
});

Fastify ๋กœ๊น…๋งŒ ๋น„ํ™œ์„ฑํ™”

export default defineConfig({
  logging: {
    loggers: [
      // fastifyCategory์— ๋Œ€ํ•œ logger๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์Œ
      // โ†’ Fastify ๋กœ๊น… ๋น„ํ™œ์„ฑํ™”
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

ํŠน์ • ๊ฒฝ๋กœ ์ œ์™ธ

export default defineConfig({
  logging: {
    filters: {
      "exclude-healthcheck": (record) => {
        const req = record.properties.req;
        const res = record.properties.res;
        const url = req?.url ?? res?.request.url;
        return url !== "/api/healthcheck";  // healthcheck ์ œ์™ธ
      },
    },
    
    loggers: [
      {
        category: ["fastify"],
        sinks: ["fastify-console"],
        filters: ["exclude-healthcheck"],
        lowestLevel: "info",
      },
    ],
  },
  
  server: {
    listen: { port: 1028 },
  },
});

์ฃผ์˜์‚ฌํ•ญ

1. req vs res ํ”„๋กœํผํ‹ฐ

// โœ… ๋‘˜ ๋‹ค ์ฒดํฌ
const req = record.properties.req as FastifyRequest | undefined;
const res = record.properties.res as FastifyReply | undefined;
const url = req?.url ?? res?.request.url;

// โŒ ํ•˜๋‚˜๋งŒ ์ฒดํฌํ•˜๋ฉด ๋ˆ„๋ฝ ๊ฐ€๋Šฅ
const req = record.properties.req;
const url = req.url;  // res๋งŒ ์žˆ์„ ๊ฒฝ์šฐ ์—๋Ÿฌ

2. responseTime์€ res์—๋งŒ ์žˆ์Œ

// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฒดํฌ
const responseTime = record.properties.responseTime as number | undefined;
if (responseTime) {
  // responseTime ์‚ฌ์šฉ
}

// โŒ req์—์„œ ์ฐพ์œผ๋ฉด ์—†์Œ
const req = record.properties.req;
const responseTime = req?.responseTime;  // undefined

3. fastify-console ๋ฎ์–ด์“ฐ๊ธฐ ์ฃผ์˜

// โŒ ๊ธฐ๋ณธ sink ์†์‹ค
sinks: {
  "fastify-console": getConsoleSink(),  // ๊ธฐ๋ณธ formatter ์†์‹ค
}

// โœ… ๋‹ค๋ฅธ ์ด๋ฆ„ ์‚ฌ์šฉ
sinks: {
  "my-console": getConsoleSink(),
}

4. Fastify logger ์˜ต์…˜ ์ถฉ๋Œ

// โŒ ์ถฉ๋Œ ๋ฐœ์ƒ
logging: false,
server: {
  fastify: {
    logger: true,  // logging: false์™€ ์ถฉ๋Œ
  },
}

// โœ… ์ผ๊ด€์„ฑ ์œ ์ง€
logging: false,
server: {
  fastify: {
    // logger ์„ค์ • ์•ˆ ํ•จ
  },
}

๋‹ค์Œ ๋‹จ๊ณ„