사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
일반▾
인터페이스▾
관측성▾
성능▾
네이티브▾
개발▾

런타임 로깅

고객이 네 시쯤 환불이 실패했다고 합니다. replica 열두 개가 쏟아낸 stdout만으로는 어느 줄이 그 호출의 것인지 알 수 없습니다.
Akan은 로그 한 줄을 먼저 레코드로 만듭니다. 한 요청의 줄은 같은 traceId를 가지므로, 위 질문은 akan logs myapp --trace <id> 명령 하나로 끝납니다.
이 페이지에서 쓰는 말
LogRecord
Logger 호출 한 번이 만드는 데이터입니다. 레벨, 로거 이름, 프로세스, 요청 안이라면 trace까지 담습니다.
traceId
요청 하나가 남긴 모든 줄이 함께 갖는 id입니다. 예: m8x1k2-a9f3c1.
sink
Logger.addSink로 등록한 수신자입니다. 회전 로그 파일과 허브가 대표적입니다.
floor
sink나 조회 도구가 받는 가장 낮은 레벨입니다. 그보다 낮은 레코드는 버려집니다.
hub
모든 프로세스의 레코드를 한곳에 모은 저널입니다. gateway가, gateway가 없으면 단독 replica가 가집니다.
child replica
gateway 뒤에서 도는 서버 프로세스입니다. 자기 레코드를 IPC로 허브에 올려 보냅니다.
레코드에 실리는 것
levelsev
레벨 이름과 OpenTelemetry severity 숫자입니다.
namecontext
로거 이름과, 두 번째 인자로 넘긴 context 문자열입니다.
rolereplicaIdxpid
어느 프로세스가 썼는지입니다. role은 gateway, all, federation, batch, rsc-worker 중 하나입니다.
traceIdendpointorigin
요청 안에서만 채워집니다. 예: http로 들어온 mutation:refundPayment.
attrs
Logger.emit으로 붙인 구조화된 key=value 값입니다.
레코드 하나가 호출에서 수집기까지 가는 길
this.logger.info(...)
LogRecordlevel · name · role · replicaIdxtraceId · endpoint · origin · attrs
Sinkfloor: minLevel, 없으면 AKAN_LOG_FILE_LEVEL
child replicaLogForwarder가 IPC로 전송
허브 소유 프로세스gateway 또는 단독 replica
링 버퍼AKAN_LOG_BUFFER건까지 보관
컨테이너 stdouttext 또는 ndjson
회전 로그 파일AKAN_LOG_TO_FILE
akan-control.sockakan logs · .tail
GET /_akan/app/logsSSE

Logger 사용법

서비스와 어댑터에는 그 이름이 붙은 this.logger가 이미 있습니다. 의도에 맞는 메서드를 골라 씁니다:
apps/myapp/lib/invoice/invoice.service.ts
  • 두 번째 인자는 context 문자열입니다. 레벨 뒤에 [invoice-sync]로 찍힙니다. 로거 하나가 여러 작업을 맡을 때 넣으세요.
  • error는 stderr로, 나머지 레벨은 stdout으로 나갑니다. 어댑터는 예외를 잡아 this.logger.error로 남기고 null을 돌려줍니다.
  • 서비스나 어댑터 밖에서는 new Logger("CsvImporter")로 만들거나, 한 번만 쓸 줄이라면 정적 메서드 Logger.info(msg, context, name)를 부릅니다.
구조화된 값은 attrs에 넣습니다
나중에 거르거나 쿼리할 값은 메시지 문자열이 아니라 attrs에 넣습니다:
apps/myapp/lib/invoice/invoice.service.ts
  • 호출 한 번으로 두 곳에서 읽힙니다. 텍스트 출력에서는 메시지 뒤에 key=value로, ndjson에서는 JSON 객체로 실립니다. 터미널에서는 grep, 수집기에서는 쿼리가 됩니다.
  • 값은 원시값만 받습니다. string, number, boolean, null 중 하나여야 합니다.
  • 비밀값처럼 보이는 키는 레코드가 만들어질 때 가려집니다. token, password, passwd, jwt, authorization, cookie, secret, api_key, private_key를 포함하는 키는 대소문자와 상관없이 "[redacted]"가 되므로, 어떤 sink도 원래 값을 보지 못합니다.

로그 레벨

레벨은 여섯 개입니다. 옆의 숫자는 0~5 순번이 아니라 OpenTelemetry severity 값입니다.
레벨심각도설명
trace1모든 단계를 남깁니다. 한 번쯤만 궁금할 세부까지 포함합니다.
verbose3개발자가 일부러 켜서 보는 상세 내역입니다. 별도 대역이 아니라 TRACE 대역의 윗칸입니다.
debug5지금 손보는 하위 시스템 하나를 진단할 때 씁니다.
info9정상적인 라이프사이클 이벤트입니다. 운영 기본값입니다.
warn13복구된 문제입니다. 처리는 계속됐지만 누군가는 알아야 합니다.
error17작업이 실패했거나 확인이 필요합니다. stdout이 아니라 stderr로 나갑니다.
ndjson 출력, SSE 페이로드, 숫자로 준 --level 필터가 모두 이 값을 씁니다. 번호를 새로 매기면 실제로 오가는 데이터와 어긋납니다.
레벨 설정 세 가지
세 설정은 서로 다른 질문에 답합니다. 터미널을 보는 사람이 원하는 범위, stdout이 수집기로 보낼 범위, floor 없는 sink가 내려갈 깊이입니다.
AKAN_PUBLIC_LOG_LEVELtrace | verbose | debug | info | warn | error기본값 info
콘솔 레벨입니다. log는 info로 읽히고(폐기 예정), 모르는 이름은 조용히 info가 됩니다.
AKAN_LOG_STDOUT_LEVELtrace | verbose | debug | info | warn | error기본값 AKAN_PUBLIC_LOG_LEVEL
stdout이 싣는 레벨입니다. 설정하면 AKAN_PUBLIC_LOG_LEVEL보다 우선하고, ndjson에서는 child가 올려 보내는 기준선도 됩니다.
AKAN_LOG_FILE_LEVELtrace | verbose | debug | info | warn | error기본값 trace
minLevel이 없는 모든 sink의 기준선입니다. 회전 로그 파일과 허브도 여기에 해당합니다.
실행 중에 바꾸려면 Logger.setLevel(level)과 Logger.setFileLevel(level)을 부릅니다.

파일 로그와 로테이션

서버는 <runtimeDir>/logs 아래에 회전 로그 파일을 씁니다. 프로세스마다 파일이 따로 있고, 로컬 날짜와 파일 크기 기준으로 각자 회전합니다.
akan start로 띄우면 gateway와 child replica가 각자 파일을 씁니다:
local/apps/myapp/runtime/logs
파일 이름은 모두 appName-environment-operationMode-YYYY-MM-DD-processKey-sequence.log 형식입니다:
appName-environment-operationMode
앱 이름, 환경, 운영 모드입니다. 예: myapp-local-local.
YYYY-MM-DD
로컬 날짜입니다. 날짜가 바뀌면 sequence가 0001부터 다시 시작합니다.
processKey
gateway는 gateway, child는 <replicaIdx>-<role>, 단독 replica는 role만 씁니다.
sequence
네 자리 번호입니다. 재시작하면 기존 파일을 덮어쓰지 않고 다음 번호로 넘어갑니다.
AKAN_LOG_TO_FILE0 | 1기본값 켜짐 (Docker 이미지는 0)
정확히 문자열 0일 때만 파일 로그가 꺼집니다. false로는 꺼지지 않습니다.
AKAN_LOG_DIRstring기본값 <runtimeDir>/logs
로그 디렉터리입니다. 상대 경로는 프로세스의 작업 디렉터리 기준으로 풉니다.
AKAN_LOG_MAX_SIZE_MBnumber기본값 50
이 크기를 넘으면 다음 sequence 파일로 넘어갑니다.
AKAN_LOG_MAX_FILESnumber기본값 100
process key마다 남기는 최신 파일 수입니다. 더 오래된 파일은 지웁니다.
  • <runtimeDir>의 위치: NODE_ENV=production이면 runtime/, 아니면 local/apps/<app>/runtime입니다. AKAN_RUNTIME_DIR로 직접 정할 수도 있습니다.
  • Docker 이미지는 파일 로그를 끕니다. 컨테이너의 쓰기 레이어는 사라지는 공간이라 거기서는 stdout이 수집 경로입니다. 파일이 필요하면 AKAN_LOG_TO_FILE=1을 줍니다.

로그 조회

앱이 트래픽을 받지 못하면 gateway 로그부터 봅니다. 그다음 요청이나 백그라운드 작업을 처리한 child 로그를 봅니다.
파일은 평범한 텍스트라 익숙한 도구로 보면 됩니다:
Terminal
  • child 줄에는 접두어가 붙습니다. [child:0 all] [stderr]처럼 어느 replica의 어느 스트림인지 드러나므로 검색 한 번으로 찾을 수 있습니다.
  • child의 stderr는 터미널에 안 보여도 파일에는 남습니다. gateway는 AKAN_CHILD_STDERR=1일 때만 child의 stderr를 터미널에 출력합니다.
  • child의 console.log는 저장됩니다. stdout/stderr 파이프로 잡히기 때문입니다. gateway 프로세스에서는 Logger sink를 거치지 않으므로, 런타임 코드에서는 Logger를 쓰세요.
  • akan start는 dev.log도 씁니다. runtime 디렉터리에 앱의 모든 프로세스와 dev host의 빌드 출력을 ANSI 없이 모읍니다. 직전 세션은 dev.prev.log로 남습니다.

실시간 조회

실행 중인 gateway(단독 replica라면 replica 자신)는 최근 레코드를 링 버퍼에 담고, runtime 디렉터리의 akan-control.sock으로 내보냅니다. akan logs와 akan console의 .tail이 여기에 붙습니다.
소켓은 chmod 0600이고 TCP 포트를 열지 않습니다. 파일시스템 권한이 인증의 전부입니다.
Terminal
--levelstring
최소 레벨입니다. 이름이나 severity 숫자로 줍니다.
--grepstring
메시지에 들어 있어야 하는 부분 문자열입니다.
--endpointstring
endpoint 글롭 목록입니다. 쉼표로 구분합니다. 예: mutation:*, query:userList.
--tracestring
요청 하나의 traceId입니다.
--childstring
replica 번호 목록입니다. 쉼표로 구분합니다.
--role, -Rstring
프로세스 역할 목록입니다. gateway, all, federation, batch, rsc-worker.
--originstring
호출 출처 목록입니다. http, websocket, mcp, internal, page.
--sincestring
이보다 새로운 레코드만 봅니다. 30s, 5m, 2h, 1d 또는 epoch ms.
--replay, -nnumber기본값 0
따라가기 전에 버퍼에서 먼저 보여 줄 레코드 수입니다.
--jsonboolean기본값 false
렌더링한 줄 대신 NDJSON 레코드를 출력합니다.
--followboolean기본값 true
계속 스트리밍합니다. 지난 기록만 보려면 --follow false를 줍니다.
--runtime-dir, -dstring기본값 local/apps/<app>/runtime
akan-control.sock이 있는 디렉터리입니다. 다른 곳에서 도는 빌드된 앱에 줍니다.
필터는 겹쳐 적용됩니다
플래그끼리는 AND, 한 플래그 안의 쉼표 목록은 OR입니다. 와일드카드는 endpoint와 로거 이름에 쓰는 * 하나뿐이고, endpoint는 mutation:refundPayment, page:<routeId>처럼 type:key 모양입니다.
보는 사람이 없으면 비용 0
child는 구독자가 그 레벨을 원하는 동안에만 IPC로 레코드를 올리고, ndjson에서는 stdout에 실릴 레코드를 늘 올립니다. AKAN_LOG_STREAM=1이면 모든 레코드를 항상 올립니다.
크기가 정해진 링
허브 소유 프로세스는 레코드 2,000건 또는 4MB까지 보관합니다(AKAN_LOG_BUFFER, AKAN_LOG_BUFFER_MB). 같은 줄이 1초에 20번을 넘으면 "suppressed" 한 줄로 접힙니다.
문맥이 없는 줄
gateway 내부 줄, 스케줄러 자체의 started/finished 줄, fast path로 처리되는 비인증 primitive GET 쿼리에는 traceId와 endpoint가 없습니다. AKAN_LOG_CONTEXT=0은 요청 문맥을 전부 끕니다.

요청 요약 줄과 flight recorder

두 옵트인은 노이즈를 걸러내는 대신 아예 줄입니다. 호출마다 요약 한 줄을 남기고, 잘못된 호출에만 trace 수준의 상세를 남깁니다.
요청 요약 줄
호출이 끝날 때 레코드 하나를 씁니다. 요청 하나가 열두 줄이 아니라 grep 한 줄이 됩니다.
AKAN_LOG_CANONICAL=1
flight recorder
호출마다 레벨에 못 미쳐 버려질 레코드를 들고 있다가, 실패했거나 오래 걸렸을 때만 flight=true로 표시해 올립니다.
AKAN_LOG_FLIGHT=1
프로세스 레벨은 info로 둔 채, 실패한 요청에 대해서만 trace 상세를 얻는 방식입니다.
요약 줄에 실리는 값
ok | error <endpoint>
메시지입니다. 정상 호출은 info, 실패한 호출은 warn으로 씁니다.
msstatus
걸린 시간과 status입니다. 실패하면 에러의 statusCode, 없으면 500입니다.
userId
호출자를 알게 된 경우 그 계정 id입니다.
dbdbMscacheHit
쿼리 수, 쿼리 시간, 캐시 적중률입니다. AKAN_TRACE=1일 때만 붙습니다.
err
에러 메시지의 첫 줄입니다. 200자에서 자릅니다.
설정
AKAN_LOG_CANONICAL1 | true | all | slow기본값 꺼짐
호출마다 요약 레코드 하나를 씁니다. slow는 실패했거나 AKAN_LOG_FLIGHT_MS를 넘긴 호출만 씁니다.
AKAN_LOG_FLIGHT1 | true기본값 꺼짐
호출마다 레벨 아래 레코드를 최근 64건까지 들고 있다가, 실패했거나 오래 걸렸을 때만 올립니다.
AKAN_LOG_FLIGHT_MSnumber기본값 1000
느림 기준(ms)입니다. flight recorder와 slow 모드가 함께 씁니다.
AKAN_LOG_FLIGHT_MAXnumber기본값 65536
모든 호출을 합쳐 동시에 보관하는 레코드 수입니다(호출당 64건, 1,024개 호출). 넘으면 기록 없이 실행됩니다.
AKAN_LOG_DEBUG_HEADERstring기본값 없음
요청 하나를 trace로 낮추는 x-akan-debug 헤더의 비밀값입니다. 없으면 local에서만 받습니다.
AKAN_TRACE1기본값 꺼짐
요약 줄에 db·cache 수치를, 메트릭에 단계별 span을 더합니다.
운영 중 요청 하나만 trace로 보기
x-akan-debug 헤더에 비밀값을 실어 보내면 그 요청 하나만 trace로 남습니다:
Terminal
  • 승격된 줄은 모든 floor를 통과합니다. flight=true나 debug=true 레코드는 레벨 아래에서 일부러 요청된 것이므로, forwarder의 floor, stdout 레벨, --level 필터가 모두 통과시킵니다.
  • 같은 줄이 두 번 찍히지 않습니다. 아직 어디에도 쓰이지 않은 줄만 승격되고, 호출이 64건 링을 넘쳤다면 첫 승격 줄에 flightEvicted=N이 붙습니다.
  • 헤더 값이 틀리면 거절하지 않고 무시합니다. 요청은 평소 레벨로 그대로 실행됩니다.
  • 측정한 비용: recorder는 정상 호출에 약 190ns, 게이트는 trace 안에서 걸러지는 로그 호출마다 약 20ns를 더합니다. 둘 다 기본값은 꺼짐이고, 메모리 상한은 운영자가 정합니다.

수집: NDJSON stdout

수집과 실시간 조회는 다른 문제입니다. 수집은 잃는 것이 없어야 하고 재시작에도 안전해야 하므로 컨테이너 stdout으로 합니다.
  • writer는 하나입니다. AKAN_LOG_FORMAT=ndjson이면 허브 소유 프로세스만 stdout에 한 줄에 JSON 하나씩 쓰고, 나머지 프로세스는 콘솔 출력을 끕니다.
  • Logger 밖의 출력도 감쌉니다. Logger를 거치지 않고 쓴 것은 크래시 스택까지 raw=true 레코드가 되므로, 스트림은 계속 유효한 JSON입니다.
  • 정렬은 at이 아니라 seq로 합니다. at은 여러 프로세스의 시계에서 오고, seq는 허브에 도착한 순서입니다.
AKAN_LOG_FORMATtext | ndjson | ndjson-only기본값 text
ndjson은 stdout 한 줄에 JSON 하나를 쓰고, ndjson-only는 회전 로그 파일까지 JSON으로 씁니다.
AKAN_LOG_STREAM1기본값 꺼짐
child의 IPC forwarder를 허브 기준선에 맡기지 않고 항상 켜 둡니다.
AKAN_LOG_BUFFERnumber기본값 2000
허브 소유 프로세스의 링이 보관하는 레코드 수입니다. 이 값이나 바이트 상한에 닿으면 밀려납니다.
AKAN_LOG_BUFFER_MBnumber기본값 4
같은 링의 바이트 상한입니다. 양수가 아니면 무시됩니다.
ndjson을 내보내는 docker-compose 서비스는 이렇게 씁니다:
docker-compose.yml
  • AKAN_LOG_TO_FILE: "0"은 이미지 기본값입니다. 쓰기 레이어는 사라지는 공간이고, ndjson에서 파일을 켜면 허브 floor가 trace로 내려가 모든 child가 모든 레코드를 IPC로 올립니다.
  • AKAN_LOG_STDOUT_LEVEL: info로 둡니다. kubelet은 컨테이너 로그를 크기 기준으로 회전하므로, trace만큼 쏟아내면 에이전트가 읽기 전에 밀려납니다.
  • json-file은 설정하지 않으면 회전하지 않습니다. max-size와 max-file을 주거나, 드라이버를 수집기로 바꾸세요.
Kubernetes에서는 Fluent Bit 같은 노드 에이전트가 CRI 래퍼를 벗기고 JSON을 파싱합니다:
fluent-bit.conf
  • traceId와 userId는 Loki 라벨이 아니라 JSON 필드로 둡니다. 라벨은 app, env, role, level처럼 값의 종류가 적어야 합니다. id는 쿼리할 때 {app="myapp"} | json | traceId="m8x1k2-a9f3c1"로 고릅니다.

SSE 스트림

실시간 조회는 세션 도구입니다. GET /_akan/app/logs가 bearer 토큰을 가진 쪽에 허브를 text/event-stream으로 흘려보내고, akan logs와 같은 필터를 받습니다:
Terminal
AKAN_LOG_STREAM_TOKEN
설정하지 않으면 라우트가 아예 없습니다. 403을 답하는 것이 아니라 존재하지 않습니다.
Authorization: Bearer
토큰이 없거나 틀리면 401로 답합니다.
?level=&endpoint=…
akan logs와 같은 필터에 name, stream, limit을 더 받습니다.
idLast-Event-ID
이벤트 id는 허브 seq입니다. 다시 연결하면 끊긴 곳부터 이어 받습니다.
: heartbeat
15초마다 heartbeat 주석을 보내고, 재연결 간격 힌트는 2초입니다.
빠진 구간은 명시합니다
재개할 때 조용히 건너뛰는 일은 없습니다. Last-Event-ID 이후를 다 줄 수 없으면 먼저 gap 이벤트를 보냅니다:
ring-buffer-evicted
링이 그 구간 일부를 이미 밀어냈습니다. 이벤트에 from, to, missed가 실립니다.
sequence-reset
id가 현재 seq보다 큽니다. 재시작한 프로세스가 답하고 있다는 뜻입니다.
  • 허브 소유 프로세스만 제공합니다. gateway 또는 단독 replica입니다. gateway 뒤의 child는 제공하지 않으므로, 토큰만으로는 child에 닿을 수 없습니다.
  • 수집 경로가 아닙니다. 구독은 pod가 재시작하는 동안의 구간을 통째로 잃고, pod마다 따로 붙어야 합니다. 지금 프로세스 하나를 볼 때 쓰고, 보관할 로그는 stdout과 노드 에이전트로 보내세요.

운영 체크리스트

운영 로그를 쓸모 있고 감당할 만하게 유지하는 다섯 가지 규칙입니다.
  • 터미널 로그는 읽을 수 있게 유지합니다. 운영에서는 AKAN_PUBLIC_LOG_LEVEL=info나 warn을 쓰고, 실시간 디버깅할 때만 잠깐 올립니다.
  • 모든 sink에 floor를 줍니다. Logger.addSink에 minLevel을 넘기세요. 없으면 AKAN_LOG_FILE_LEVEL, 즉 trace를 따릅니다.
  • 레코드를 전달할 때마다 로그를 남기지 않습니다. forwarder, sink, 스트림 라우트처럼 레코드를 전달하는 코드가 항목마다 로그를 남기면 자기 출력을 다시 먹습니다.
  • 디스크 사용량을 계획합니다. AKAN_LOG_MAX_SIZE_MB와 AKAN_LOG_MAX_FILES는 process key마다 적용되므로 replica 수만큼 최대 사용량이 늘어납니다.
  • 메시지에 비밀값을 넣지 않습니다. 가림 처리는 비밀값을 뜻하는 attrs 키에만 적용됩니다. 문자열에 끼워 넣은 토큰은 가려지지 않고, 파일 로그는 터미널 출력보다 오래 남습니다.
함께 볼 문서

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman