LogTape는 카테고리 시스템으로 로그를 구조화합니다. Sonamu는 Model, Frame, Workflow, Agent 클래스에 대해 자동으로 카테고리를 생성합니다.
카테고리란?
카테고리는 로그의 출처를 계층 구조로 표현합니다.
형식 : ["a", "b", "c"] 배열
예시:
["fastify"] - Fastify 프레임워크
["sonamu", "model", "user-model"] - UserModel 클래스
["sonamu", "workflow", "email-send"] - EmailSendWorkflow
["app", "payment", "processor"] - 커스텀 카테고리
카테고리 시스템의 목적
카테고리는 로그를 체계적으로 관리하기 위해 필요합니다.
1. 로그 출처 식별
// 어디서 발생한 로그인가?
[ "fastify" ][( "sonamu" , "model" )][( "app" , "payment" )]; // HTTP 요청 // Model 레이어 // 결제 로직
2. 선택적 로깅
// 특정 부분만 로깅
loggers : [
{
category: [ "sonamu" , "model" ], // Model만 debug 레벨로
lowestLevel: "debug" ,
},
{
category: [ "fastify" ], // Fastify는 info 레벨로
lowestLevel: "info" ,
},
];
3. 로그 분리 저장
// 부분별로 다른 파일에
loggers : [
{
category: [ "sonamu" , "model" ],
sinks: [ "modelLog" ], // models.log
},
{
category: [ "app" , "payment" ],
sinks: [ "paymentLog" ], // payment.log
},
];
계층 구조의 이점
카테고리가 계층적인 이유:
1. 명확한 구조
[ "sonamu" , "model" , "user-model" ];
// ↓ ↓ ↓
// 프레임워크 타입 구체적 이름
어디서 발생한 로그인지 한눈에 파악 가능
2. 유연한 필터링
// 전체 Sonamu 로그
category : [ "sonamu" ];
// Model 레이어만
category : [ "sonamu" , "model" ];
// UserModel만
category : [ "sonamu" , "model" , "user-model" ];
3. 충돌 방지
// 다른 프로젝트의 "user" 로그도 구분 가능
[ "sonamu" , "model" , "user-model" ][( "app" , "user" , "service" )][( "external" , "api" , "user" )]; // Sonamu의 user // 애플리케이션의 user // 외부 API의 user
왜 정확한 매칭만 되나?
LogTape는 카테곣리를 정확히 일치 하는 것만 매칭합니다.
// ❌ 부분 매칭 안 됨
category : [ "sonamu" ];
// → ["sonamu", "model", "user-model"] 로그는 매칭 안 됨
// ✅ 정확히 일치해야 함
category : [ "sonamu" , "model" , "user-model" ];
// → ["sonamu", "model", "user-model"] 로그만 매칭
이유:
명확성
// 부분 매칭이 된다면?
category : [ "sonamu" ];
// → ["sonamu", "model", "user-model"]
// → ["sonamu", "workflow", "email-send"]
// → ["sonamu", "agent", "payment-agent"]
// → 너무 많은 로그가 매칭됨!
// 정확한 매칭
category : [ "sonamu" , "model" , "user-model" ];
// → 원하는 로그만 정확히 매칭
성능
// 와일드카드 매칭이 된다면?
category : [ "sonamu" , "*" ]; // 모든 sonamu 하위 카테고리
// → 매번 패턴 매칭 필요 (느림)
// 정확한 매칭
category : [ "sonamu" , "model" ];
// → 단순 문자열 비교 (빠름)
예측 가능성
// 부분 매칭이라면 혼란
loggers : [
{ category: [ "sonamu" ], lowestLevel: "info" },
{ category: [ "sonamu" , "model" ], lowestLevel: "debug" },
];
// → ["sonamu", "model", "user-model"] 로그는 어디에 매칭?
// 정확한 매칭으로 명확
loggers : [{ category: [ "sonamu" , "model" ], lowestLevel: "debug" }];
// → ["sonamu", "model"]만 매칭, 명확함!
카테고리 구조 상세
계층 표현
카테고리는 점점 구체적으로 좁혀집니다.
[ "sonamu" , "model" , "user-model" ];
// ↓ ↓ ↓
// 네임스페이스 타입 구체적 이름
설정 예시:
export default defineConfig ({
logging: {
loggers: [
// "sonamu"로 시작하는 모든 로그
{
category: [ "sonamu" ],
sinks: [ "console" ],
lowestLevel: "info" ,
},
// Model만
{
category: [ "sonamu" , "model" ],
sinks: [ "modelLog" ],
lowestLevel: "debug" ,
},
// UserModel만
{
category: [ "sonamu" , "model" , "user-model" ],
sinks: [ "userLog" ],
lowestLevel: "debug" ,
},
],
} ,
server: {
// ...
} ,
}) ;
카테고리 매칭
LogTape는 카테고리를 정확히 일치 하는 것만 매칭합니다.
// ❌ 부분 매칭 안 됨
category : [ "sonamu" ];
// → ["sonamu", "model", "user-model"] 로그는 매칭 안 됨
// ✅ 정확히 일치해야 함
category : [ "sonamu" , "model" , "user-model" ];
// → ["sonamu", "model", "user-model"] 로그만 매칭
자동 카테고리 생성
Sonamu는 특정 클래스에 대해 자동으로 카테고리를 생성합니다.
Model 클래스
// UserModelClass → ["sonamu", "model", "user-model"]
// PostModelClass → ["sonamu", "model", "post-model"]
// OrderItemModelClass → ["sonamu", "model", "order-item-model"]
변환 규칙:
클래스 이름에서 ModelClass 제거
PascalCase → snake_case 변환
snake_case → kebab-case 변환
예시:
UserModelClass
→ User (ModelClass 제거)
→ user (snake_case)
→ ["sonamu", "model", "user"] (kebab-case, 접두사 추가)
OrderItemModelClass
→ OrderItem
→ order_item
→ ["sonamu", "model", "order-item"]
Frame 클래스
// UserFrameClass → ["sonamu", "frame", "user-frame"]
// AdminUserFrameClass → ["sonamu", "frame", "admin-user-frame"]
변환 규칙 : Model과 동일하되 FrameClass 제거
Workflow
// EmailSendWorkflow → ["sonamu", "workflow", "email-send"]
// OrderProcessWorkflow → ["sonamu", "workflow", "order-process"]
변환 규칙:
클래스 이름에서 Workflow 제거 (접미사만)
PascalCase → snake_case 변환
snake_case → kebab-case 변환
예시:
EmailSendWorkflow
→ EmailSend (Workflow 제거)
→ email_send (snake_case)
→ ["sonamu", "workflow", "email-send"] (kebab-case, 접두사 추가)
Agent 클래스
// PaymentAgentClass → ["sonamu", "agent", "payment-agent"]
// AdminNotificationAgentClass → ["sonamu", "agent", "admin-notification-agent"]
변환 규칙 : Model과 동일하되 AgentClass 제거
Naite 카테고리
Naite 테스트 키는 특별한 카테고리 변환 규칙을 따릅니다.
// "user.findOne" → ["user", "findOne"]
// "user:list" → ["user", "list"]
// "admin.company:detail" → ["admin", "company", "detail"]
변환 규칙:
.로 split
각 부분을 :로 split
평탄화 (flatten)
예시:
"user.findOne"
→ ["user", "findOne"]
"admin.company:detail"
→ split by "." → ["admin", "company:detail"]
→ split by ":" → [["admin"], ["company", "detail"]]
→ flatten → ["admin", "company", "detail"]
카테고리별 로깅 설정
Model별 로그 분리
import { defineConfig } from "sonamu" ;
import { getFileSink } from "@logtape/logtape" ;
export default defineConfig ({
logging: {
sinks: {
userLog: getFileSink ( "logs/user-model.log" ),
postLog: getFileSink ( "logs/post-model.log" ),
},
loggers: [
{
category: [ "sonamu" , "model" , "user-model" ],
sinks: [ "userLog" ],
lowestLevel: "debug" ,
},
{
category: [ "sonamu" , "model" , "post-model" ],
sinks: [ "postLog" ],
lowestLevel: "debug" ,
},
],
} ,
server: {
// ...
} ,
}) ;
타입별 로그 레벨 조정
export default defineConfig ({
logging: {
loggers: [
// Model: debug 레벨
{
category: [ "sonamu" , "model" ],
sinks: [ "console" ],
lowestLevel: "debug" ,
},
// Workflow: info 레벨
{
category: [ "sonamu" , "workflow" ],
sinks: [ "console" ],
lowestLevel: "info" ,
},
// Agent: warning 레벨
{
category: [ "sonamu" , "agent" ],
sinks: [ "console" ],
lowestLevel: "warning" ,
},
],
} ,
server: {
// ...
} ,
}) ;
네임스페이스별 관리
export default defineConfig ({
logging: {
sinks: {
sonamuLog: getFileSink ( "logs/sonamu.log" ),
appLog: getFileSink ( "logs/app.log" ),
},
loggers: [
// Sonamu 내부
{
category: [ "sonamu" ],
sinks: [ "sonamuLog" ],
lowestLevel: "info" ,
},
// 애플리케이션
{
category: [ "app" ],
sinks: [ "appLog" ],
lowestLevel: "debug" ,
},
],
} ,
server: {
// ...
} ,
}) ;
커스텀 카테고리
애플리케이션 코드에서 커스텀 카테고리를 사용할 수 있습니다.
import { getLogger } from "@logtape/logtape" ;
// 로거 생성
const logger = getLogger ([ "app" , "payment" , "processor" ]);
// 로깅
logger . info ( "Processing payment" , { orderId: 123 });
logger . error ( "Payment failed" , { error: "timeout" });
설정:
export default defineConfig ({
logging: {
sinks: {
paymentLog: getFileSink ( "logs/payment.log" ),
},
loggers: [
{
category: [ "app" , "payment" , "processor" ],
sinks: [ "paymentLog" ],
lowestLevel: "info" ,
},
],
} ,
server: {
// ...
} ,
}) ;
실전 예시
개발 환경 상세 로깅
import { defineConfig } from "sonamu" ;
import { getConsoleSink } from "@logtape/logtape" ;
export default defineConfig ({
logging: {
sinks: {
console: getConsoleSink (),
},
loggers: [
// Fastify: info
{
category: [ "fastify" ],
sinks: [ "console" ],
lowestLevel: "info" ,
},
// 모든 Sonamu 내부: debug
{
category: [ "sonamu" ],
sinks: [ "console" ],
lowestLevel: "debug" ,
},
// 앱 로직: debug
{
category: [ "app" ],
sinks: [ "console" ],
lowestLevel: "debug" ,
},
],
} ,
server: {
listen: { port: 34900 },
} ,
}) ;
프로덕션 선택적 로깅
import { defineConfig } from "sonamu" ;
import { getConsoleSink , getFileSink } from "@logtape/logtape" ;
export default defineConfig ({
logging: {
sinks: {
console: getConsoleSink (),
workflowLog: getFileSink ( "logs/workflow.log" ),
errorLog: getFileSink ( "logs/error.log" ),
},
loggers: [
// Fastify: warning 이상
{
category: [ "fastify" ],
sinks: [ "console" ],
lowestLevel: "warning" ,
},
// Workflow: 전체 기록
{
category: [ "sonamu" , "workflow" ],
sinks: [ "workflowLog" ],
lowestLevel: "info" ,
},
// 에러만 별도 파일
{
category: [ "sonamu" ],
sinks: [ "errorLog" ],
lowestLevel: "error" ,
},
],
} ,
server: {
listen: { port: 34900 },
} ,
}) ;
도메인별 로그 분리
import { defineConfig } from "sonamu" ;
import { getFileSink } from "@logtape/logtape" ;
export default defineConfig ({
logging: {
sinks: {
userDomain: getFileSink ( "logs/user-domain.log" ),
orderDomain: getFileSink ( "logs/order-domain.log" ),
paymentDomain: getFileSink ( "logs/payment-domain.log" ),
},
loggers: [
// User 관련
{
category: [ "sonamu" , "model" , "user-model" ],
sinks: [ "userDomain" ],
lowestLevel: "debug" ,
},
{
category: [ "app" , "user" ],
sinks: [ "userDomain" ],
lowestLevel: "debug" ,
},
// Order 관련
{
category: [ "sonamu" , "model" , "order-model" ],
sinks: [ "orderDomain" ],
lowestLevel: "debug" ,
},
{
category: [ "app" , "order" ],
sinks: [ "orderDomain" ],
lowestLevel: "debug" ,
},
// Payment 관련
{
category: [ "app" , "payment" ],
sinks: [ "paymentDomain" ],
lowestLevel: "debug" ,
},
],
} ,
server: {
listen: { port: 34900 },
} ,
}) ;
내부 동작 원리
이 섹션은 Sonamu의 카테고리 시스템이 내부적으로 어떻게 동작하는지 설명합니다. 여기 나오는 함수들은
Sonamu가 자동으로 사용하며, 사용자가 직접 호출할 수 없습니다.
isSameCategory()
두 카테고리가 정확히 일치하는지 확인하는 내부 함수입니다.
// Sonamu 내부 함수 (직접 사용 불가)
isSameCategory ([ "sonamu" , "model" ], [ "sonamu" , "model" ]);
// → true
isSameCategory ([ "sonamu" , "model" ], [ "sonamu" , "workflow" ]);
// → false
isSameCategory ([ "sonamu" ], [ "sonamu" , "model" ]);
// → false (길이가 다름)
내부 용도: LogTape의 logger 설정에서 카테고리를 매칭할 때 사용됩니다.
convertDomainToCategory()
클래스 이름을 카테고리로 변환하는 내부 함수입니다.
// Sonamu 내부 함수 (직접 사용 불가)
convertDomainToCategory ( "UserModelClass" , "model" );
// → ["sonamu", "model", "user-model"]
convertDomainToCategory ( "EmailSendWorkflow" , "workflow" );
// → ["sonamu", "workflow", "email-send"]
convertDomainToCategory ( "PaymentAgentClass" , "agent" );
// → ["sonamu", "agent", "payment-agent"]
내부 용도: Model, Frame, Workflow, Agent 클래스가 생성될 때 자동으로 카테고리를 할당합니다.
변환 로직:
클래스 이름에서 접미사 제거 (ModelClass, FrameClass, AgentClass, Workflow)
PascalCase → snake_case 변환
snake*case → kebab-case 변환 (*→-)
["sonamu", type, name] 형식으로 구성
convertNaiteKeyToCategory()
Naite 테스트 키를 카테고리로 변환하는 내부 함수입니다.
// Sonamu 내부 함수 (직접 사용 불가)
convertNaiteKeyToCategory ( "user.findOne" );
// → ["user", "findOne"]
convertNaiteKeyToCategory ( "admin.company:detail" );
// → ["admin", "company", "detail"]
내부 용도: Naite 테스트 실행 시 로그 카테고리를 자동으로 생성합니다.
변환 로직:
.로 split
각 부분을 :로 split
평탄화 (flatten)
주의사항
1. 정확한 매칭만 지원
// ❌ 와일드카드 없음
category : [ "sonamu" , "*" ]; // 작동 안 함
// ❌ 부분 매칭 없음
category : [ "sonamu" ]; // ["sonamu", "model"]과 매칭 안 됨
// ✅ 정확한 카테고리만
category : [ "sonamu" , "model" , "user-model" ];
2. 카테고리는 readonly 배열
// ✅ readonly 배열 사용
const category : readonly string [] = [ "app" , "payment" ];
// ❌ 일반 배열은 타입 에러 가능
const category : string [] = [ "app" , "payment" ];
3. 클래스 이름 컨벤션
// ✅ 올바른 이름
class UserModelClass {} // → ["sonamu", "model", "user-model"]
class OrderItemModelClass {} // → ["sonamu", "model", "order-item-model"]
class EmailSendWorkflow {} // → ["sonamu", "workflow", "email-send"]
// ❌ 컨벤션 위반 (자동 변환 안 됨)
class User {} // Model이 아님
class UserModel {} // "Class" 접미사 없음
class WorkflowEmailSend {} // "Workflow" 접두사 (접미사여야 함)
4. Fastify 카테고리 중복
// ❌ Fastify 카테고리와 충돌
fastifyCategory : [ "sonamu" , "model" ];
loggers : [
{
category: [ "sonamu" , "model" ], // Fastify 로그와 섞임!
// ...
},
];
// ✅ 분리된 카테고리
fastifyCategory : [ "fastify" ];
loggers : [
{
category: [ "sonamu" , "model" ],
// ...
},
];
다음 단계
Fastify 로깅 HTTP 요청/응답 로깅을 커스터마이징하세요
Sinks & Filters 커스텀 Sink와 Filter로 로그 출력을 제어하세요
LogTape 설정 기본 로깅 설정과 개념을 학습하세요
LogTape 공식 문서 LogTape의 고급 기능과 API를 확인하세요