TIL - 20260921

juni·2026년 9월 21일

TIL

목록 보기
459/468

0921 운영 자동화/AI 워크플로우 심화 (15/N): Observability, Correlation ID와 실행 추적 구조


✅ 1. 자동화가 많아질수록 가장 먼저 어려워지는 것은 ‘원인 추적’이다

처음에는 요청 하나가 단순하다.

사용자 요청
→ API
→ DB 저장
→ 응답

문제가 생기면 서버 로그만 확인해도 어느 정도 원인을 찾을 수 있다.

하지만 시스템에 자동화가 붙기 시작하면 흐름이 달라진다.

관리자 주문 상태 변경
→ API
→ DB Transaction
→ Event 생성
→ Queue
→ Worker
→ 알림톡 API
→ Audit Log
→ Webhook
→ Webhook Worker
→ DB 상태 변경

여기에 AI 자동화까지 붙는다면:

Git Push
→ Automation Trigger
→ AI Task
→ AI Run
→ 코드 분석
→ 코드 수정
→ Test
→ Review
→ Report 생성
→ Notion 업로드

어느 한 단계에서 문제가 발생했을 때 단순히

Error occurred

라는 로그만 있어서는 원인을 찾기 어렵다.

그래서 필요한 것이 Observability다.


✅ 2. Monitoring과 Observability는 조금 다르다

Monitoring은 보통

CPU 사용률
Memory 사용량
HTTP Error Rate
응답 시간
Queue 길이

등 이미 알고 있는 지표를 감시한다.

반면 Observability는

문제가 발생했을 때 시스템 내부에서 무슨 일이 있었는지 외부 데이터만으로 추적할 수 있는 능력

에 가깝다.

즉,

Monitoring
→ 문제가 있다는 사실을 발견

Observability
→ 왜 문제가 발생했는지 추적

이라고 생각하면 된다.


✅ 3. Observability의 기본 3요소

전통적으로 다음 세 가지를 많이 본다.

Logs
Metrics
Traces

Logs

개별 사건 기록이다.

NotificationJob #1920 발송 시작
Notification API timeout
Retry scheduled

Metrics

숫자로 집계되는 운영 상태다.

API Error Rate = 2.3%
Queue Pending = 38
Notification Failure = 4

Traces

한 요청이 여러 시스템을 지나가는 전체 흐름이다.

Request
→ API
→ DB
→ Queue
→ Worker
→ External API

현재 투게더몰처럼 기능이 점점 연결되는 구조에서는 세 가지 모두 필요하다.


✅ 4. 로그가 많다고 Observability가 좋은 것은 아니다

다음과 같은 로그를 많이 남겨도 실질적인 도움이 적다.

Request received
Processing...
Processing...
API called
API success
Done

운영 중 정말 필요한 것은

어떤 요청인가?
어떤 주문인가?
어떤 사용자가 실행했는가?
어떤 Worker가 처리했는가?
어떤 외부 요청과 연결되는가?
같은 작업의 이전 Retry는 무엇인가?
최종 결과가 무엇인가?

이다.

따라서 로그의 양보다 연결 가능성이 중요하다.


✅ 5. Correlation ID란?

Correlation ID는 여러 시스템에 흩어진 로그를 하나의 흐름으로 묶기 위한 ID다.

예를 들어 관리자에서 주문 상태를 변경한다.

PATCH /admin/orders/321/status

이 요청에 다음 ID를 부여한다.

correlationId = cor_7f31a91

그 이후 발생하는 모든 작업에 같은 ID를 전달한다.

HTTP Request
cor_7f31a91

↓
Order Update
cor_7f31a91

↓
Domain Event
cor_7f31a91

↓
NotificationJob
cor_7f31a91

↓
Worker
cor_7f31a91

↓
Alimtalk API
cor_7f31a91

그러면 운영 중

cor_7f31a91

만 검색해도 요청 전체 흐름을 찾을 수 있다.


✅ 6. Request ID와 Correlation ID

둘을 같은 개념으로 사용할 수도 있지만 규모가 커지면 구분하는 것이 편하다.

Request ID
= 하나의 HTTP Request 식별

Correlation ID
= 여러 Request / Job / Event를 묶는 전체 흐름 식별

예를 들어:

Correlation ID
cor_order_192

├─ Request ID
│  req_001
│
├─ Queue Job
│  job_813
│
├─ External Request
│  ext_392
│
└─ Webhook Request
   req_822

Webhook은 몇 초 뒤 다른 HTTP 요청으로 들어오더라도 같은 업무 흐름으로 연결할 수 있다.


✅ 7. Trace ID까지 등장하면 헷갈릴 수 있다

OpenTelemetry 같은 분산 추적 시스템에서는 흔히 다음 개념을 사용한다.

Trace ID
Span ID

Trace는 하나의 전체 요청 흐름이다.

Trace
├─ HTTP Request
├─ Prisma Query
├─ Queue Publish
├─ Worker
└─ External API

각각의 작은 작업이 Span이다.

Trace ID
abc123

Span
HTTP Request

Span
DB Update

Span
Notification Worker

Span
External API

소규모 프로젝트에서는 처음부터 완전한 Distributed Tracing까지 구축할 필요는 없다.

현재 단계에서는 우선

Correlation ID
Request ID
Job ID

만 제대로 연결해도 상당한 효과가 있다.


✅ 8. 투게더몰 기준 필요한 주요 ID

운영 로그에서 다음 정도를 일관되게 가지고 있으면 좋다.

requestId

correlationId

userId / adminId

orderId

consultId

jobId

eventId

runId

externalRequestId

단 개인정보는 직접 로그에 남기지 않는 것이 중요하다.

예:

phone = 01012345678

보다는

orderId = order_123

을 기준으로 추적한다.


✅ 9. 로그에 개인정보를 넣지 않는 이유

편하다고 이런 로그를 남기기 쉽다.

김동준 010-1234-5678 알림톡 발송

하지만 운영 로그는 생각보다 많은 곳에 복제될 수 있다.

CloudWatch
Local Log
CI Log
Error Tracking
Backup
AI 분석 시스템

따라서 고객 개인정보 자체보다 내부 ID를 활용해야 한다.

Bad

name = 홍길동
phone = 01012345678


Better

customerId = cus_921
orderId = order_4421

필요하면 관리자 시스템에서 ID로 고객을 조회한다.


✅ 10. Structured Logging

다음 로그보다

알림톡 발송 실패했습니다.

이런 구조가 낫다.

{
  "level": "error",
  "event": "notification.send.failed",
  "correlationId": "cor_123",
  "jobId": "job_392",
  "orderId": "order_1022",
  "provider": "kakao",
  "errorCode": "PROVIDER_TIMEOUT",
  "attempt": 2
}

이를 Structured Logging이라고 한다.

사람만 읽기 위한 문장이 아니라 프로그램으로 검색·집계 가능한 데이터 형태다.


✅ 11. 이벤트 이름도 규칙을 정해놓는 것이 좋다

예를 들어:

order.created
order.status.changed

notification.queued
notification.send.started
notification.send.succeeded
notification.send.failed

export.started
export.completed
export.failed

webhook.received
webhook.processed
webhook.failed

ai.run.started
ai.run.completed
ai.run.failed

이렇게 하면 로그 검색이 쉬워진다.

예:

event = notification.send.failed

만 검색해서 전체 알림톡 장애를 확인할 수 있다.


✅ 12. 단순 메시지 로그를 줄이고 Context를 늘린다

좋지 않은 예:

logger.error('알림톡 발송 실패');

조금 더 나은 예:

logger.error({
  event: 'notification.send.failed',
  jobId: job.id,
  correlationId: job.correlationId,
  provider: job.provider,
  errorCode: error.code,
});

운영 로그에서는 문장보다 Context가 더 중요하다.


✅ 13. NestJS에서 Request ID 부여

Middleware나 Interceptor를 통해 요청마다 ID를 생성할 수 있다.

개념적으로는:

request.requestId =
  request.headers['x-request-id']
  ?? generateRequestId();

그리고 응답에도 반환한다.

X-Request-Id: req_f3912

사용자가 오류를 문의할 때도

오류 번호 req_f3912

형태로 활용할 수 있다.


✅ 14. Correlation ID 전달

외부에서 이미 Correlation ID가 넘어왔다면 유지하고,

없다면 새로 만든다.

const correlationId =
  request.headers['x-correlation-id']
  ?? generateCorrelationId();

이 값을

Service
Repository
Event
Queue
Worker

로 계속 전달한다.


✅ 15. 함수마다 correlationId를 인자로 넣으면 지저분해진다

처음에는 이렇게 구현하기 쉽다.

service.updateOrder(
  orderId,
  status,
  correlationId,
);

그리고 내부 함수도 계속:

repository.update(
  data,
  correlationId,
);

이 방식은 금방 번거로워진다.

그래서 Request Context 같은 개념을 사용할 수 있다.

RequestContext

requestId
correlationId
actorId

서비스는 필요할 때 Context를 읽는다.


✅ 16. AsyncLocalStorage

Node.js에는 AsyncLocalStorage가 있다.

이를 이용하면 하나의 비동기 실행 흐름 안에서 Context를 유지할 수 있다.

개념:

{
  requestId: 'req_123',
  correlationId: 'cor_123',
  actorId: 'admin_3'
}

이후 Service에서 굳이 모든 함수에 인자로 넘기지 않아도 Logger가 Context를 읽을 수 있다.


✅ 17. 단 Queue에서는 Context가 자동 전달되지 않는다

HTTP 요청이 Queue에 Job을 등록하면 실행 흐름이 끊긴다.

HTTP Request
↓
Queue Insert
----------------
다른 Worker Process
↓
Job 실행

AsyncLocalStorage는 프로세스를 넘어 전달되지 않는다.

그래서 Job Payload에 직접 넣어야 한다.

{
  "jobId": "job_192",
  "correlationId": "cor_9182",
  "orderId": "order_299"
}

Worker가 시작할 때 이 값을 Context로 복원한다.


✅ 18. Event에도 Correlation ID를 넣는다

예:

{
  eventId: 'evt_123',

  type: 'ORDER_STATUS_CHANGED',

  aggregateId: 'order_921',

  correlationId: 'cor_882',

  causationId: 'req_991'
}

여기서 Causation ID라는 개념도 활용할 수 있다.


✅ 19. Correlation과 Causation의 차이

Correlation은 같은 흐름이라는 뜻이다.

Causation은

무엇 때문에 이 이벤트가 발생했는가?

를 나타낸다.

예:

Request
req_1

↓ 발생

OrderStatusChanged
evt_1

↓ 발생

NotificationQueued
evt_2

각각:

evt_1.causationId = req_1

evt_2.causationId = evt_1

그리고 모두:

correlationId = cor_1

이다.

이 구조를 가지면 이벤트 흐름을 거의 트리처럼 복원할 수 있다.


✅ 20. NotificationJob 예시

관리자가 주문 상태를 변경한다.

correlationId
cor_20260921_001

로그:

order.status.change.requested

↓

order.status.changed

↓

notification.queued

↓

notification.send.started

↓

notification.send.succeeded

모든 이벤트에 동일한 correlationId가 있다.

운영자는 한 번의 검색으로 전체 과정을 볼 수 있다.


✅ 21. Webhook이 돌아오면 Correlation 연결하기

외부 Provider가 Webhook을 보낸다.

Webhook HTTP 요청 자체는 새 Request이므로:

requestId = req_webhook_883

를 가진다.

하지만 처음 발송한 메시지와 연결하기 위해:

providerMessageId

를 조회한다.

DB에서 NotificationJob을 찾고,

correlationId = cor_20260921_001

를 다시 가져온다.

결과적으로:

원본 요청

→ 알림톡 발송

→ 외부 Provider

→ Webhook

→ 상태 반영

전체가 하나의 Correlation으로 연결된다.


✅ 22. Audit Log와 Application Log는 목적이 다르다

둘을 하나로 생각하면 안 된다.

Application Log는:

시스템이 어떻게 동작했는가?

를 본다.

Audit Log는:

누가 무엇을 변경했는가?

를 본다.

예:

Application Log

Notification API Timeout
Retry scheduled

반면:

Audit Log

관리자 admin_3
주문 order_921
상태 WAITING → COMPLETED

둘은 함께 연결되어야 하지만 목적은 다르다.


✅ 23. Audit Log에도 Correlation ID를 넣는다

{
  actorId: 'admin_3',

  action: 'ORDER_STATUS_CHANGE',

  targetId: 'order_921',

  before: 'WAITING',

  after: 'COMPLETED',

  correlationId: 'cor_123'
}

그럼 관리자 행동 이후 어떤 자동화들이 발생했는지 추적할 수 있다.


✅ 24. Metrics는 개별 사건보다 ‘전체 건강 상태’를 본다

예:

HTTP 요청 수

HTTP Error Rate

평균 응답 시간

Queue Pending Count

Worker 처리량

Retry 횟수

UNKNOWN Job 수

DLQ 수

Webhook 실패 건수

Notification 실패율

이런 값은 개별 로그보다 시스템 전체 상태를 보는 데 유용하다.


✅ 25. Counter, Gauge, Histogram

Metrics는 대표적으로 이런 형태가 있다.

Counter

계속 증가하는 값.

notification_sent_total
notification_failed_total
http_requests_total

Gauge

현재 값.

queue_pending_jobs

worker_active_count

unknown_jobs

Histogram

값의 분포.

http_request_duration

notification_send_duration

export_duration

✅ 26. Error Rate

성공률만 보는 것보다 실패율이 중요하다.

Error Rate
=
Failed Requests
/
Total Requests

예:

오늘 알림톡

발송 시도 1,000
실패 15

Error Rate = 1.5%

평소 실패율이 0.1%였는데 갑자기 5%가 되면 장애 가능성이 높다.


✅ 27. Queue Lag

Worker 자동화에서는 Queue 크기만 보면 부족하다.

예:

Pending Job = 300

이라도 Worker가 매우 빠르게 처리하고 있다면 문제가 아닐 수 있다.

중요한 것은 가장 오래 기다린 Job이다.

Oldest Job Age

예:

Pending = 300
Oldest Age = 2 seconds

이면 정상일 수 있다.

하지만

Pending = 20
Oldest Age = 40 minutes

이면 Worker가 멈췄을 가능성이 있다.


✅ 28. Retry Metrics도 중요하다

실패율은 낮지만 Retry가 급증할 수도 있다.

Success 99.9%

여도 내부적으로

평균 Retry 4회

를 거치고 있다면 이미 Provider나 Network에 문제가 생기고 있는 것이다.

따라서:

retry_total

retry_success_total

retry_exhausted_total

등을 보면 좋다.


✅ 29. Reconciliation Metrics

0917에서 만든 Reconciliation에도 지표가 필요하다.

reconciliation_total

reconciliation_recovered_total

reconciliation_failed_total

manual_required_total

unknown_jobs

특히

UNKNOWN 증가

는 시스템이 외부 상태를 확실하게 확인하지 못하고 있다는 신호다.


✅ 30. 로그 레벨

보통 다음 정도를 사용한다.

DEBUG
INFO
WARN
ERROR

DEBUG

개발 중 상세 정보.

INFO

정상적인 주요 흐름.

notification.send.started
notification.send.succeeded

WARN

시스템은 동작하지만 이상 징후.

retry scheduled
stale job detected
provider response slow

ERROR

실제 처리 실패.

notification send failed
database transaction failed

운영 환경에서 DEBUG 로그를 과도하게 남기면 비용과 노이즈가 크게 늘어난다.


✅ 31. 모든 성공 로그를 남겨야 하는 것도 아니다

예를 들어 페이지 조회마다:

GET /products success
GET /products success
GET /products success

를 저장하면 로그가 너무 많아진다.

반면 중요 업무는 성공 로그도 가치가 있다.

주문 생성

상태 변경

알림톡 발송

Excel Export

Webhook 처리

AI Run

배포

즉 비즈니스 중요도에 따라 다르게 적용한다.


✅ 32. Error Log에는 Stack Trace만 있어도 부족하다

예:

TypeError: Cannot read property...

만으로는 부족하다.

최소한:

correlationId

requestId

jobId

event

errorCode

stack

attempt

service

정도는 같이 보는 것이 좋다.


✅ 33. Error Code를 직접 정의해두면 좋다

문자열 메시지만 사용하면 관리하기 어렵다.

예:

NOTIFICATION_PROVIDER_TIMEOUT

NOTIFICATION_TEMPLATE_INVALID

EXPORT_S3_UPLOAD_FAILED

WEBHOOK_SIGNATURE_INVALID

AI_TOOL_TIMEOUT

AI_POLICY_DENIED

이런 식으로 Error Code를 정의하면 통계도 낼 수 있다.

이번 달 가장 많이 발생한 오류

1. PROVIDER_TIMEOUT 93회
2. RATE_LIMIT 31회
3. INVALID_TEMPLATE 7회

✅ 34. Error Message와 Error Code 역할 분리

errorCode
= 프로그램이 판단하기 위한 값

errorMessage
= 사람이 읽는 설명

예:

{
  "errorCode": "NOTIFICATION_PROVIDER_TIMEOUT",
  "errorMessage": "알림톡 Provider 응답 시간이 초과되었습니다."
}

Error Message 문구가 바뀌어도 Error Code는 유지한다.


✅ 35. 외부 API 호출 기록

외부 API 문제는 특히 추적이 어렵다.

최소한 다음 정도를 남긴다.

provider

endpoint 이름

requestId

externalRequestId

statusCode

duration

result

단 전체 Request Body를 그대로 로그에 남기면 안 된다.

개인정보와 Secret이 포함될 수 있기 때문이다.


✅ 36. 외부 API 로그 예시

{
  "event": "external_api.completed",

  "provider": "kakao",

  "operation": "send_alimtalk",

  "correlationId": "cor_123",

  "jobId": "job_391",

  "externalRequestId": "kakao_8221",

  "statusCode": 200,

  "durationMs": 382
}

실제 전화번호나 메시지 전체 내용은 로그에서 제외한다.


✅ 37. Slow Request 추적

Error가 아니더라도 지나치게 느린 요청은 문제다.

예:

관리자 주문 검색

평균 120ms

특정 요청
4.8s

이런 요청은 별도 WARN으로 남길 수 있다.

{
  "event": "http.slow_request",
  "path": "/admin/orders",
  "durationMs": 4821,
  "correlationId": "cor_123"
}

이후 DB Slow Query와 연결한다.


✅ 38. Prisma Query 역시 전체 SQL을 무조건 찍지 않는다

개발환경에서는 Query 로그가 편하다.

하지만 운영 환경에서 모든 SQL과 parameter를 남기면:

로그 폭증

개인정보 노출

비용 증가

문제가 생긴다.

운영에서는 주로:

Slow Query

Query Type

Model

Duration

중심으로 관찰하는 것이 낫다.


✅ 39. AI Run Observability

AI 자동화가 들어가면 일반 API보다 훨씬 추적이 중요하다.

한 Run이:

Task 생성

Prompt 구성

Context 조회

Model 호출

Tool 실행

File 수정

Test

Review

Commit

Report

등 여러 단계를 거치기 때문이다.


✅ 40. AI Run에서 기록해야 할 것

예:

taskId

runId

attemptId

stepId

correlationId

model

promptVersion

policyVersion

toolName

startedAt

completedAt

duration

status

단 Prompt 전체를 무조건 저장하지 않는 것이 좋다.

회사 코드나 개인정보가 포함될 수 있기 때문이다.


✅ 41. Prompt Hash

Prompt 자체 대신 Hash를 저장하는 방법도 있다.

promptHash
=
sha256(prompt)

같은 Prompt인지 비교할 수 있으면서 전체 내용은 로그에 노출하지 않는다.

필요한 경우 별도 보안 저장소에 원본을 저장한다.


✅ 42. AI Tool Call 추적

AI가 Tool을 실행할 때:

tool.git.diff

tool.test.run

tool.file.write

tool.github.create_pr

처럼 이벤트를 남길 수 있다.

예:

{
  "event": "ai.tool.completed",
  "runId": "run_123",
  "tool": "test.run",
  "durationMs": 18392,
  "status": "SUCCESS"
}

✅ 43. Token 사용량도 Metric이 된다

LLM API를 사용한다면:

inputTokens

outputTokens

totalTokens

estimatedCost

를 Run 단위로 기록하는 것이 좋다.

그러면 나중에:

AI Report 자동화 월 비용

평균 Run 비용

가장 비용 많이 쓰는 Workflow

등을 확인할 수 있다.

로컬 LLM이라면 비용 대신:

executionTime

modelLoadTime

GPU / RAM 사용량

등을 볼 수 있다.


✅ 44. AI 실패 원인 분류

AI 자동화 실패를 모두

AI_ERROR

로 처리하면 의미가 없다.

예:

MODEL_TIMEOUT

TOOL_ERROR

TEST_FAILED

POLICY_DENIED

CONTEXT_TOO_LARGE

INVALID_OUTPUT

GIT_CONFLICT

APPROVAL_REQUIRED

등으로 나누는 것이 낫다.

이 분류는 Retry 정책과도 직접 연결된다.


✅ 45. Dashboard를 만든다면 처음부터 거창할 필요 없다

현재 규모에서는 Grafana 대시보드 수십 개를 만들 필요가 없다.

우선 핵심만 보면 된다.

오늘 API Error Rate

평균 API Response Time

Queue Pending

Oldest Job Age

Notification 실패

Webhook 실패

Retry

UNKNOWN

DLQ

AI Run 실패

이 정도만 봐도 상당한 운영 정보를 얻을 수 있다.


✅ 46. 관리자 어드민에 운영 상태를 넣는 것도 현실적이다

별도 모니터링 툴뿐 아니라 기존 관리자에 내부 운영 페이지를 만들 수 있다.

예:

운영 현황

API
정상

Queue
Pending 8

알림톡
오늘 성공 821
실패 3

Webhook
실패 0

DLQ
1

AI Automation
실행 중 1
실패 0

1인 개발 환경에서는 오히려 이런 화면이 실용적일 수 있다.


✅ 47. Incident Detail 화면

문제가 발생했을 때 Correlation ID 하나를 기준으로 이런 화면을 보여줄 수 있다.

Correlation
cor_20260921_a921

Timeline

09:31:02
관리자 주문 상태 변경

09:31:02
ORDER_STATUS_CHANGED

09:31:02
NotificationJob 생성

09:31:03
Worker 처리 시작

09:31:08
Provider Timeout

09:31:08
상태 UNKNOWN

09:32:01
Reconciliation 시작

09:32:02
Provider 결과 DELIVERED 확인

09:32:02
NotificationJob SENT 복구

이 정도면 장애 분석 속도가 크게 빨라진다.


✅ 48. Timeline View

Audit Log와 Automation Log를 Timeline으로 묶으면 운영성이 좋아진다.

[09:31:02] ADMIN
주문 상태 변경

[09:31:02] SYSTEM
Notification Job 생성

[09:31:03] WORKER
발송 요청

[09:31:08] SYSTEM
Provider Timeout

[09:32:01] RECONCILER
외부 상태 조회

[09:32:02] SYSTEM
발송 완료 확인

고객 문의 대응에도 꽤 유용하다.


✅ 49. Alert는 ‘에러 발생’마다 보내면 안 된다

에러 한 건마다 Slack이나 알림을 보내면 금방 무시하게 된다.

이를 Alert Fatigue라고 한다.

예:

알림톡 1건 실패
→ 즉시 긴급 알림

보다는:

5분간 실패율 10% 이상

또는

UNKNOWN 20건 이상

또는

Oldest Queue Job > 10분

같은 기준이 낫다.


✅ 50. Alert Severity

심각도를 나눠두는 것도 좋다.

INFO

WARNING

CRITICAL

예:

INFO
Retry 발생

WARNING
알림톡 실패율 증가

CRITICAL
Worker 중단
전체 주문 API 장애
Production Health Check 실패

✅ 51. 사람을 깨울 가치가 있는 Alert만 Critical

특히 나중에 워홀이나 원격 근무를 생각한다면 운영 자동화가 늘어날 가능성이 높다.

그때 모든 오류가 긴급 알림으로 오면 운영이 불가능하다.

Critical 기준은:

즉시 대응하지 않으면

고객 영향이 커지거나

데이터 손실 위험이 있거나

매출에 직접 영향이 있는가?

정도로 좁히는 것이 좋다.


✅ 52. SLI, SLO 기초

운영이 더 성숙하면 SLI/SLO 개념을 사용할 수 있다.

SLI:

실제 측정 값

예:

주문 API 성공률
99.7%

SLO:

목표 수준

예:

주문 API 성공률
99.9% 이상

현재 규모에서 SLA 계약까지 갈 필요는 없지만 내부 운영 기준을 만드는 데 도움이 된다.


✅ 53. 비즈니스 Metric도 같이 봐야 한다

기술 Metric만 정상이라고 서비스가 정상인 것은 아니다.

예:

CPU 정상
Memory 정상
HTTP 200 정상

인데 주문 생성 수가 갑자기 0건일 수도 있다.

따라서:

시간당 주문 수

신청 완료율

알림톡 발송 건수

상담 등록 건수

결제 성공 건수

같은 Business Metric도 중요하다.


✅ 54. Silent Failure

가장 위험한 장애 중 하나다.

시스템은 Error를 발생시키지 않지만 실제 기능은 동작하지 않는다.

예:

Webhook HTTP 200

하지만 DB Update 없음

또는:

Worker 정상 실행

하지만 NotificationJob 생성 자체가 안 됨

이 경우 Error Log만 보고 있으면 발견하기 어렵다.

그래서 Expected Metric이 필요하다.

주문 완료 100건

그런데 완료 알림톡 생성 0건

→ 이상

✅ 55. Business Invariant Monitoring

비즈니스 규칙 자체를 모니터링할 수도 있다.

예:

COMPLETED 주문인데
완료 NotificationJob이 없음
ExportJob SUCCESS인데
파일 URL이 없음
Webhook PROCESSED인데
관련 상태 변경 기록이 없음

이런 조건을 주기적으로 검사할 수 있다.

이는 Reconciliation과도 연결된다.


✅ 56. Observability와 Reconciliation 연결

0921 핵심 연결점이다.

Observability는 문제를 발견한다.

UNKNOWN 증가

Queue Lag 증가

Webhook Error 증가

Reconciliation은 이를 복구한다.

실제 상태 조회

Drift 확인

안전한 Repair

즉:

Observe
↓
Detect
↓
Diagnose
↓
Reconcile
↓
Recover

흐름으로 연결된다.


✅ 57. Observability와 Idempotency 연결

0916에서 다룬 Idempotency 역시 중요하다.

로그를 통해:

같은 idempotencyKey가
몇 번 실행됐는지

확인할 수 있어야 한다.

예:

idempotencyKey
notification:order:921:completed

Attempt 1
Timeout

Attempt 2
Existing Result Found

Final
SUCCESS

그러면 중복 실행 방지 여부까지 추적할 수 있다.


✅ 58. AI가 로그 분석을 도와줄 수도 있다

AI를 바로 Production 복구 권한에 연결하기보다는 먼저 분석 용도로 쓰는 것이 좋다.

예:

지난 1시간 ERROR 로그 요약

반복 Error Code 묶기

Correlation Timeline 생성

가장 빈번한 실패 원인 분석

관련 Runbook 추천

이런 Read-Only 분석은 위험이 낮다.


✅ 59. Local LLM Work Report와도 연결 가능하다

현재 사용 중인 로컬 LLM 자동화에 운영 로그 요약을 연결할 수도 있다.

예:

오늘 Git 작업
+
배포 기록
+
Error Summary
+
Incident
+
Resolved Issue

를 모아서 일일 보고서에:

오늘 운영 이슈

- 알림톡 Timeout 3건
- 자동 Retry 3건
- 최종 실패 0건
- Webhook 오류 없음

같이 넣는 방식이다.


✅ 60. 다만 로그 전체를 LLM에 넘기면 안 된다

로그에는 다음 정보가 섞일 수 있다.

고객정보

Secret

JWT

Authorization Header

API Key

Cookie

Request Body

따라서 LLM에 전달하기 전 Redaction이 필요하다.

예:

Authorization: [REDACTED]

phone: [REDACTED]

email: [REDACTED]

✅ 61. Logger 공통 인터페이스

서비스마다 제각각 로그를 남기지 않도록 공통 Logger를 사용하는 것이 좋다.

예:

logger.info({
  event: 'order.status.changed',
  orderId,
});

Logger가 자동으로:

timestamp

service

environment

requestId

correlationId

actorId

를 붙인다.


✅ 62. 공통 Log Context

예:

interface LogContext {
  requestId?: string;
  correlationId?: string;

  actorId?: string;

  jobId?: string;
  eventId?: string;

  orderId?: string;
  consultId?: string;

  runId?: string;
}

도메인 ID만 추가하고 개인정보는 제외한다.


✅ 63. NestJS 구조 예시

src/
├─ common/
│  ├─ logging/
│  │  ├─ logger.service.ts
│  │  ├─ request-context.service.ts
│  │  └─ logging.interceptor.ts
│  │
│  ├─ tracing/
│  │  ├─ correlation-id.ts
│  │  └─ trace-context.ts
│  │
│  └─ errors/
│     └─ error-code.ts
│
├─ automation/
│  └─ ...
│
└─ audit/
   └─ ...

✅ 64. Error Code 구조 예시

export const ErrorCode = {
  NOTIFICATION_PROVIDER_TIMEOUT:
    'NOTIFICATION_PROVIDER_TIMEOUT',

  NOTIFICATION_TEMPLATE_INVALID:
    'NOTIFICATION_TEMPLATE_INVALID',

  EXPORT_UPLOAD_FAILED:
    'EXPORT_UPLOAD_FAILED',

  WEBHOOK_SIGNATURE_INVALID:
    'WEBHOOK_SIGNATURE_INVALID',

  AI_TOOL_TIMEOUT:
    'AI_TOOL_TIMEOUT',
} as const;

이런 형태로 중앙 관리할 수 있다.


✅ 65. Worker 로그 흐름 예시

automation.job.claimed

↓

automation.job.started

↓

external_api.started

↓

external_api.failed

↓

automation.retry.scheduled

↓

automation.job.released

모든 이벤트가 동일한:

jobId

correlationId

idempotencyKey

를 가진다.


✅ 66. AI Run 로그 흐름

ai.run.created

↓

ai.step.started
ANALYSE

↓

ai.step.completed

↓

ai.step.started
MODIFY

↓

ai.tool.started
file.write

↓

ai.tool.completed

↓

ai.step.started
TEST

↓

ai.test.failed

↓

ai.run.failed

여기서 runId 하나만 검색하면 실행 전체를 재구성할 수 있다.


✅ 67. 운영 로그 보관 기간도 고려해야 한다

로그는 무한 보관할 필요가 없다.

예를 들어:

DEBUG
짧게

Application Log
중간 기간

Audit Log
상대적으로 길게

처럼 목적에 따라 분리할 수 있다.

특히 개인정보가 포함될 가능성이 있는 로그는 최소화가 우선이다.


✅ 68. 현재 프로젝트에서 우선순위

지금 바로 모든 Observability 기술을 적용할 필요는 없다.

1단계

Request ID

Correlation ID

Structured Logging

Error Code

2단계

Queue Job에 Correlation ID 전달

Webhook 연결

Audit Log 연결

Worker Retry 로그

3단계

기본 Metrics

Error Rate

Queue Lag

UNKNOWN

DLQ

4단계

Incident Timeline

AI Run Trace

Distributed Trace

Alert 자동화

이 순서가 현실적이다.


✅ 69. 현재 프로젝트에서 가장 ROI 높은 5가지

먼저 이 다섯 가지 정도만 적용해도 된다.

1.
모든 HTTP Request에 requestId

2.
주요 업무 흐름에 correlationId

3.
JSON Structured Log

4.
Error Code 표준화

5.
Queue / Worker / Webhook에 correlationId 전달

이 정도만 해도 장애 추적 난이도가 크게 낮아진다.


✅ 70. Codex 구현 프롬프트

현재 NestJS + Prisma 기반 프로젝트에
운영 추적을 위한 기본 Observability 구조를 추가해줘.

과도한 APM이나 복잡한 Distributed Tracing 시스템을
처음부터 도입하지 말고,
현재 1인 개발 환경에서 유지할 수 있는 수준으로 구현한다.

목표는 HTTP Request부터 Event, Queue, Worker,
Webhook, 외부 API 호출까지 하나의 업무 흐름을
Correlation ID로 추적할 수 있도록 만드는 것이다.

요구사항:

1. 모든 HTTP Request에 requestId를 생성한다.

2. 요청 Header에 x-request-id가 있다면 사용할 수 있도록 하되,
없다면 서버에서 생성한다.

3. correlationId도 동일하게 관리한다.
- x-correlation-id가 있으면 유지
- 없으면 새로 생성

4. Request Context 구조를 만든다.
최소 필드:
- requestId
- correlationId
- actorId

5. Node AsyncLocalStorage 또는 적절한 방식으로
같은 HTTP 요청 내에서 Context를 접근할 수 있게 한다.

6. 공통 Logger를 만든다.

Logger는 자동으로 다음 데이터를 붙인다.
- timestamp
- level
- environment
- requestId
- correlationId
- actorId

7. 문자열 로그보다 JSON Structured Logging을 기본으로 한다.

8. event 필드를 사용해 로그 이름을 표준화한다.

예:
- order.status.changed
- notification.queued
- notification.send.started
- notification.send.succeeded
- notification.send.failed
- webhook.received
- webhook.failed

9. Error Code를 공통 정의한다.

예:
- NOTIFICATION_PROVIDER_TIMEOUT
- EXPORT_UPLOAD_FAILED
- WEBHOOK_SIGNATURE_INVALID

10. Queue Job Payload에 correlationId를 저장한다.

11. Worker 실행 시 Job의 correlationId를
Worker Context로 복원한다.

12. Domain Event에도 다음 필드를 지원한다.
- eventId
- correlationId
- causationId

13. Webhook 처리 시 providerMessageId 등으로
원래 Job을 찾을 수 있다면
원본 correlationId를 이어서 사용한다.

14. Audit Log에도 correlationId를 저장한다.

15. 외부 API 호출 로그에서는
다음만 기록한다.
- provider
- operation
- statusCode
- durationMs
- externalRequestId

Request Body, Authorization Header,
전화번호, 이메일 등 민감 정보는 로그에 기록하지 않는다.

16. Worker 실행에서 다음 이벤트를 기록한다.
- job.claimed
- job.started
- job.completed
- job.failed
- retry.scheduled

17. 동일 correlationId로
HTTP → Event → Queue → Worker 흐름을 검색할 수 있도록 한다.

18. 기존 비즈니스 로직을 크게 변경하지 않는다.

19. 테스트를 작성한다.
- requestId 자동 생성
- 기존 requestId 유지
- correlationId 자동 생성
- Queue Job correlationId 전달
- Worker Context 복원
- 민감 정보 로그 미포함

✅ 71. 실무 체크리스트

Request

  • 모든 Request에 ID가 있는가?
  • Correlation ID가 있는가?
  • 응답에서도 Request ID를 확인할 수 있는가?

Logging

  • Structured Logging인가?
  • Event 이름 규칙이 있는가?
  • Error Code가 있는가?
  • 개인정보를 로그에 남기지 않는가?
  • Secret이 로그에 포함되지 않는가?

Queue

  • Job ID가 있는가?
  • Correlation ID가 전달되는가?
  • Retry 횟수가 보이는가?
  • Worker가 어떤 Job을 처리했는지 추적 가능한가?

External API

  • Provider를 구분하는가?
  • 요청 시간을 측정하는가?
  • Status Code가 기록되는가?
  • External Request ID가 있으면 저장하는가?

Webhook

  • 원본 Job과 연결 가능한가?
  • Correlation ID를 복원하는가?
  • 중복 Webhook을 추적할 수 있는가?

Automation

  • UNKNOWN 건수를 확인할 수 있는가?
  • Retry 횟수를 확인할 수 있는가?
  • DLQ 건수를 확인할 수 있는가?
  • Stale Job을 찾을 수 있는가?

AI

  • Task / Run / Step을 구분하는가?
  • Run ID가 있는가?
  • Tool 실행 내역이 남는가?
  • 실패 이유를 분류하는가?
  • Prompt나 민감 코드가 무분별하게 로그에 남지 않는가?

📌 요약

자동화가 적을 때는

로그 확인
→ 오류 발견

정도로 충분하다.

하지만 시스템이

API
→ Event
→ Queue
→ Worker
→ 외부 API
→ Webhook
→ Reconciliation
→ AI Automation

처럼 연결되기 시작하면 단순 로그만으로는 원인을 추적하기 어렵다.

그래서 중요한 것이 Observability다.

핵심 세 가지는:

Logs
Metrics
Traces

그리고 현재 프로젝트에서 가장 먼저 도입할 것은 복잡한 Distributed Tracing이 아니라:

Request ID
+
Correlation ID
+
Structured Logging

이다.

Request ID는 하나의 HTTP 요청을 추적하고,

Correlation ID는:

HTTP
→ Event
→ Queue
→ Worker
→ Webhook

처럼 여러 실행에 걸친 하나의 업무 흐름을 연결한다.

또한 로그에는:

고객 이름
전화번호
이메일
Authorization Header
API Key

같은 민감 정보를 남기지 않고,

orderId
jobId
eventId
runId
correlationId

같은 내부 ID를 중심으로 추적해야 한다.

운영 자동화가 성숙하면 흐름은 결국 다음처럼 연결된다.

Observe
↓
Detect
↓
Trace
↓
Diagnose
↓
Reconcile
↓
Recover

특히 AI 자동화까지 붙게 되면

Task
→ Run
→ Step
→ Tool

마다 ID와 상태를 남겨야 실패 지점을 정확히 찾고 안전하게 Resume할 수 있다.

결국 좋은 Observability의 목표는

로그를 많이 남기는 것이 아니라, 문제가 생겼을 때 하나의 ID만으로 처음부터 끝까지 무슨 일이 있었는지 재구성할 수 있게 만드는 것

이다.

0개의 댓글