📖 개요: 로그 없는 CAP 디버깅이 실패하는 이유
CAP(Node.js) 프로젝트에서 "핸들러가 왜 안 타지?", "이 요청이 DB에 어떤 SQL을 날렸지?"라는 질문에 console.log를 여기저기 심어가며 답을 찾는 경우가 많습니다. 하지만 CAP 런타임은 이미 cds.log라는 로깅 파사드를 내장하고 있어서, 환경변수 하나만 바꿔도 요청 흐름과 DB 쿼리를 그대로 추적할 수 있습니다. 이 글에서는 로그 레벨을 제어하는 3가지 방법을 중심으로 CAP 런타임 추적법을 다룹니다.
cds.log로 모듈별 로거를 만들고 레벨별 메서드를 구분해서 쓸 수 있다DEBUG환경변수와cds.env.log.levels로 서비스별 로그 레벨을 개별 조정할 수 있다- 핸들러 미호출, SQL 추적 같은 실전 디버깅 시나리오를 로그만으로 해결할 수 있다
📚 시작 전에 알아두면 좋은 것들
Node.js 기초 문법과 npm 사용법, 그리고 CAP의 기본 구조(CDS 모델 + 서비스 핸들러)를 한 번이라도 만들어 본 경험이 있으면 충분합니다. cds watch로 로컬 서버를 띄워 본 적이 있다면 바로 따라올 수 있는 초급(beginner) 수준의 내용입니다. OData나 HANA 지식은 필수가 아닙니다.
🔧 환경 및 준비물
이 글의 예제는 아래 환경을 기준으로 작성했습니다.
| 항목 | 버전 / 값 |
|---|---|
| Node.js | 20 LTS 이상 |
| @sap/cds | 8.x (CAP Node.js 런타임) |
| 로컬 DB | SQLite (in-memory) |
| 배포 대상 | SAP BTP, Cloud Foundry 환경 (선택) |
# 새 프로젝트 준비
npm i -g @sap/cds-dk
cds init order-demo && cd order-demo
npm i
cds watch # http://localhost:4004
버전 확인은 cds version으로 할 수 있으며, @sap/cds 7.x에서도 이 글의 API는 거의 동일하게 동작하지만 8.x 기준으로 설명합니다.
💡 핵심 개념: cds.log와 로그 레벨의 동작 원리
cds.log('모듈명')은 이름으로 캐싱되는 로거를 반환합니다. 같은 이름으로 몇 번을 호출해도 동일한 로거 인스턴스를 돌려주므로, 파일마다 자유롭게 호출해도 설정은 한 곳에서 관리됩니다. 비유하자면 건물의 층별 조명 스위치와 같습니다. 모듈명이 "층"이고, 로그 레벨이 "밝기 단계"입니다. 평소에는 복도등(info)만 켜두다가, 문제가 생긴 층만 서치라이트(debug/trace)를 켜는 방식입니다.
레벨은 아래처럼 계층 구조를 가지며, 설정한 레벨 이하의 심각도만 출력됩니다.
SILENT < ERROR < WARN < INFO < DEBUG < TRACE
예: 레벨이info면 error/warn/info는 출력, debug/trace는 무시
- error: 요청 실패, 예외 — 운영에서 반드시 남겨야 하는 것
- warn: 동작은 하지만 점검이 필요한 상황
- info: 서버 기동, 서비스 바인딩 등 일반 이벤트 (일반적으로 기본값)
- debug: 요청 파라미터, SQL 문장 등 개발 중 추적 정보
- trace: 가장 상세한 내부 흐름 (성능 비용이 커서 국소적으로만 사용 권장)
그리고 이 레벨을 제어하는 방법이 정확히 3가지입니다.
- DEBUG 환경변수 — 지정한 모듈을 일시적으로 debug 레벨로 올림. 예:
DEBUG=sql cds watch - cds.env.log.levels 설정 —
package.json이나.cdsrc.json에 모듈별 레벨을 영구 선언 - 코드에서 직접 지정 —
cds.log('order-service', 'debug')처럼 로거 생성 시 레벨을 부여
CAP 런타임 자체도 serve, sql, odata, auth, remote 같은 모듈명으로 내부 로거를 쓰고 있기 때문에, 우리가 레벨만 올리면 프레임워크 내부 동작이 그대로 보이는 구조입니다. 이것이 console.log 대비 가장 큰 차이점입니다.
💻 실전 코드: 3단계로 완성하는 CAP 로그 추적
1단계 — 기본: 커스텀 로거 만들기
판매오더 서비스를 가정하고, 모듈명 order-service로 로거를 만들어 봅니다.
// srv/order-service.cds
service OrderService {
entity PurchaseRequests {
key ID : UUID;
material : String(40);
quantity : Integer;
status : String(10);
}
action approveRequest(requestId : UUID) returns String;
}
// srv/order-service.js
const cds = require('@sap/cds')
const LOG = cds.log('order-service') // 모듈명으로 로거 획득 (캐싱됨)
module.exports = class OrderService extends cds.ApplicationService {
init() {
this.before('CREATE', 'PurchaseRequests', (req) => {
LOG.info('구매요청 생성 시작', { material: req.data.material })
LOG.debug('전체 페이로드:', req.data) // debug 레벨에서만 출력
})
return super.init()
}
}
이 상태로 cds watch를 실행하면 LOG.info는 보이지만 LOG.debug는 보이지 않습니다. 이제 3가지 방법 중 하나로 레벨을 올립니다.
# 방법 1: DEBUG 환경변수 (일회성, 가장 빠름)
DEBUG=order-service cds watch
DEBUG=order-service,sql cds watch # 쉼표로 여러 모듈
DEBUG=all cds watch # 전부 debug (로컬에서만 권장)
// 방법 2: package.json (영구 설정, 서비스별 개별 레벨)
{
"cds": {
"log": {
"levels": {
"order-service": "debug",
"sql": "debug",
"odata": "warn"
}
}
}
}
sql 모듈을 debug로 올리면 CAP이 실행하는 모든 SQL 문장과 바인딩 값이 콘솔에 찍히므로, "이 OData 요청이 실제로 어떤 쿼리가 됐는가"를 바로 확인할 수 있습니다.
2단계 — 실무 시나리오: 에러 로깅과 레벨 분리
액션 핸들러에서 실패 상황을 레벨별로 나눠 기록합니다. 핵심은 "예외는 error, 비정상 입력은 warn, 흐름 추적은 debug"로 역할을 고정하는 것입니다.
// srv/order-service.js (init 내부에 추가)
this.on('approveRequest', async (req) => {
const { requestId } = req.data
LOG.debug('approveRequest 진입, id =', requestId)
const row = await SELECT.one.from('OrderService.PurchaseRequests')
.where({ ID: requestId })
if (!row) {
LOG.warn('존재하지 않는 요청 승인 시도:', requestId)
return req.error(404, `요청 ${requestId} 없음`)
}
try {
await UPDATE('OrderService.PurchaseRequests')
.set({ status: 'APPROVED' }).where({ ID: requestId })
LOG.info('승인 완료:', requestId)
return 'APPROVED'
} catch (err) {
LOG.error('승인 처리 실패:', err.message, { requestId })
return req.error(500, '승인 처리 중 오류가 발생했습니다')
}
})
여기서 자주 쓰는 실전 디버깅 시나리오 하나. "핸들러가 아예 호출되는지 안 되는지"를 확인하려면 핸들러 첫 줄의 LOG.debug('...진입')과 함께 DEBUG=serve,odata,order-service로 실행합니다. serve 로그에 서비스·핸들러 등록 내역이, odata 로그에 요청 파싱 결과가 나오므로, "등록 자체가 안 됐는지"(파일명·서비스명 불일치)와 "등록은 됐지만 라우팅이 안 맞는지"를 로그만으로 구분할 수 있습니다.
3단계 — 프로덕션: 성능 가드와 구조화 로그
운영 환경에서는 두 가지가 중요합니다. 첫째, debug 레벨이 꺼져 있어도 로그 인자를 만드는 비용은 발생하므로, 무거운 직렬화는 레벨 가드로 감쌉니다. 둘째, SAP BTP의 로그 수집기가 파싱할 수 있도록 JSON 포맷으로 출력합니다.
// 비싼 연산은 레벨 가드로 보호 (_debug는 debug 활성 여부 플래그)
if (LOG._debug) {
LOG.debug('스냅샷:', JSON.stringify(await buildHeavySnapshot(requestId)))
}
// 요청 상관관계 추적: cds.context.id (correlation id)를 함께 남기기
LOG.info('승인 완료', { requestId, correlationId: cds.context?.id })
// package.json — 프로덕션 프로파일에서만 JSON 포맷
{
"cds": {
"log": {
"[production]": { "format": "json" },
"levels": { "order-service": "info", "sql": "warn" }
}
}
}
JSON 포맷으로 출력하면 SAP BTP의 Application Logging 서비스나 SAP Cloud Logging에서 correlation id 기준으로 하나의 요청 흐름을 묶어 검색할 수 있습니다. 마지막으로 테스트에서도 로그 레벨을 활용할 수 있습니다.
// test/order.test.js — 테스트 실행 시 소음 줄이기
process.env.CDS_LOG_LEVELS_SQL = 'silent'
const cds = require('@sap/cds')
const { GET, POST } = cds.test(__dirname + '/..')
it('없는 요청 승인 시 404', async () => {
await expect(POST('/odata/v4/order/approveRequest',
{ requestId: '00000000-0000-0000-0000-000000000000' }
)).rejects.toThrow(/404/)
})
보안 관점에서는 debug 로그에 개인 정보나 인증 토큰이 담기지 않도록 페이로드 전체 덤프를 운영 레벨에서 차단(info 이상만 허용)하는 것이 일반적인 권장 사항입니다.
⚠️ 자주 겪는 문제와 해결 FAQ
Q1. LOG.debug를 썼는데 아무것도 안 나옵니다.
기본 레벨이 일반적으로 info이기 때문입니다. DEBUG=모듈명 환경변수, cds.env.log.levels, 로거 생성 시 레벨 지정 — 3가지 중 하나로 debug를 활성화해야 합니다. 모듈명 오타(로거 생성 시 이름과 설정의 키 불일치)도 흔한 원인입니다.
Q2. DEBUG=all을 운영에 켜두면 안 되나요?
권장하지 않습니다. SQL 문장, 페이로드 전체가 로그로 쏟아져 성능 저하와 로그 비용 증가, 민감정보 노출 위험이 생깁니다. 운영에서는 문제 모듈만 국소적으로 debug로 올렸다가 원인 파악 후 되돌리는 방식이 일반적입니다.
Q3. 핸들러가 호출되는지 확인했는데 로그가 전혀 없습니다. 뭐가 문제죠?
핸들러 등록 자체가 실패한 경우가 대부분입니다. .cds 서비스명과 .js 파일명이 매칭되는지(예: order-service.cds ↔ order-service.js), 클래스를 module.exports 했는지 확인하고, DEBUG=serve로 기동 로그에서 서비스·핸들러 등록 여부를 확인하세요.
Q4. 로컬에서 로그가 JSON 한 줄로 나와 읽기 힘듭니다.
포맷 설정이 production 프로파일 밖에 있는 경우입니다. "[production]": { "format": "json" }처럼 프로파일로 감싸면 로컬은 사람이 읽기 좋은 plain 포맷을 유지합니다.
🚀 여기서 더 나아가기
로그 레벨 제어가 익숙해졌다면 다음 주제로 확장해 보세요. (1) cds.log.format 커스터마이징으로 팀 표준 로그 스키마 만들기, (2) @cap-js/telemetry로 OpenTelemetry 기반 분산 추적 붙이기, (3) SAP Cloud Logging 대시보드에서 correlation id 기반 요청 추적 구성, (4) cds.env 프로파일 전략으로 dev/test/prod 로그 정책 분리. 특히 원격 서비스(remote 모듈) 호출이 많은 프로젝트라면 텔레메트리 연동의 효과가 큽니다.
📚 함께 보면 좋은 문서
댓글 0
아직 댓글이 없습니다.