36940db938a8f4a1e919496793ed439850a221c2의 기본 로깅 서비스에 대한 독립 원문이다. 그림은 개념을 설명하려고 새로 그린 것이며 실제 보드의 처리 시간·손실률을 나타내지 않는다. 기본 deferred 경로와 설정으로 바뀌는 예외를 구분한다.1. 로그 출력은 호출 위치보다 선택된 경로에 달려 있다
Zephyr 로깅은 모듈이나 모듈 인스턴스가 만든 메시지를 frontend, core, 활성 backend로 보낸다. LOG_ERR·LOG_WRN·LOG_INF·LOG_DBG는 심각도별 API다. 실행 흐름을 계측하는 tracing의 이벤트 훅이나 printk() 호출 자체와 같은 개념으로 취급하면, 출력이 없는 원인을 잘못 찾는다.
기존 Build / West는 빌드 절차, Thread / Scheduler는 실행 선택을 설명한다. 이 글은 그 코드가 만든 로그 메시지의 생존 경로를 설명한다. 아래 파이프라인은 CONFIG_LOG=y, 기본 frontend, backend 사용, CONFIG_LOG_MODE_DEFERRED=y를 전제로 한다.
2. 먼저 모듈을 등록하고 컴파일 상한을 본다
한 모듈의 한 소스 파일에서 LOG_MODULE_REGISTER(sensor, LOG_LEVEL_INF)로 이름과 컴파일 때 적용할 레벨을 등록한다. 같은 모듈의 나머지 파일은 LOG_MODULE_DECLARE(sensor, LOG_LEVEL_INF)를 사용한다. 별도 레벨을 지정하지 않으면 CONFIG_LOG_DEFAULT_LEVEL을 쓴다. CONFIG_LOG_OVERRIDE_LEVEL=0인 경로에서는 모듈 레벨과 CONFIG_LOG_MAX_LEVEL을 모두 통과해야 호출이 남는다. 이 조건에서 최대 INFO면 DEBUG 호출은 실행 시 필터를 높여도 되살릴 수 없다.
| 설정·선언 | 작용 | 점검할 오해 |
|---|---|---|
CONFIG_LOG=n | 로깅 호출이 빌드에 들어가지 않는다. | 백엔드를 나중에 켜면 이전 메시지가 나올 것이라는 기대 |
LOG_MODULE_REGISTER() | 모듈 이름과 지정된 컴파일 레벨을 한 파일에서 등록한다. | 같은 모듈의 여러 파일에서 각각 등록 |
CONFIG_LOG_DEFAULT_LEVEL | 명시한 모듈 레벨이 없을 때 기본값이다. | 모든 모듈의 명시 레벨을 덮는다는 생각 |
CONFIG_LOG_MAX_LEVEL | override가 0인 일반 경로에서 최대 포함 레벨을 제한한다. | 컴파일에서 빠진 DEBUG를 런타임 필터로 복구 |
CONFIG_LOG_OVERRIDE_LEVEL | 문서는 전역 하한으로 설명한다. 고정 v3.7.0 매크로에는 override로 허용한 레벨이 MAX_LEVEL 검사와 별도의 분기를 통과하는 예외가 있다. | MAX_LEVEL을 override보다 항상 우선하는 상한으로 단정 |
3. 기본 deferred 경로에서 메시지는 다섯 단계를 지난다
아래 그림과 레벨 예시는 CONFIG_LOG_OVERRIDE_LEVEL=0을 전제로 한다. 고정 소스의 Z_LOG_CONST_LEVEL_CHECK에서는 override 허용 분기가 LOG_MAX_LEVEL을 다시 검사하지 않는다. 예를 들어 override=DEBUG, max=INFO여도 이 상수 필터는 DEBUG를 허용한다. 이후 런타임 필터·버퍼·backend 통과나 실제 출력까지 보장한다는 뜻은 아니다.
컴파일에서 살아남은 호출도 곧장 UART로 가지 않는다. 런타임 필터가 켜져 있다면 적어도 하나의 backend가 원하는 수준인지 먼저 확인한다. 기본 frontend는 메시지 설명자·타임스탬프·인수를 묶어 원형 패킷 버퍼에 넣는다. 처리 시점에 core는 활성 backend마다 다시 필터를 확인하고 전달한다. CONFIG_LOG_FRONTEND_ONLY=y 또는 minimal 모드는 이 그림의 저장·backend 경로와 다르다.
↔ 좁은 화면에서는 왼쪽부터 오른쪽으로 이동해 각 단계와 출력 분기를 읽으세요.
LOG_INF("ready")모듈·레벨이 붙은 호출
CONFIG_LOG·모듈 레벨·LOG_MAX_LEVEL제외되면 메시지 없음
LOG_RUNTIME_FILTERING=y라면 하나 이상의 backend가 원할 때 패킷 생성한 backend에 보이지 않아도 다른 backend에는 도착할 수 있다. 이 도식은 사용자 정의 frontend·멀티도메인 링크를 생략한 기본 단일 도메인 경로다.
| 관문 | 사건 | 남는 단서 |
|---|---|---|
| 컴파일 | 레벨이 빌드에서 제외 | 바이너리에도 해당 로그 코드가 없다. |
| runtime 집계 | 어느 backend도 해당 수준을 원하지 않음 | 패킷 자체를 만들 필요가 없다. |
| deferred 버퍼 | 공간 부족으로 새 메시지 또는 오래된 대기 메시지 드롭 | 드롭 정책·버퍼 크기·처리 속도를 확인한다. |
| backend별 처리 | 패킷은 있으나 이 backend가 허용하는 상세 수준 밖임 | 다른 backend에는 보일 수 있다. |
근거: 3.7.0 Default Frontend, log_core.h 238–269행, log_core.c 462–555행.
4. 런타임 레벨은 backend마다 따로 작동한다
CONFIG_LOG_RUNTIME_FILTERING=y이면 모듈 또는 인스턴스의 필터를 backend별로 조정할 수 있다. 전단에는 backend 요구 수준의 집계값이 있어 패킷을 만들지 결정하고, core는 실제 전달 때 각 backend의 값을 다시 본다. 빌드에 없는 레벨은 여기서도 복구할 수 없다.
LOG_INF()는 패킷이 생성되어 B에만 전달된다. LOG_ERR()는 두 곳에 전달된다. LOG_DBG()는 컴파일 상한 밖이므로 양쪽 모두에 전달할 패킷이 없다. A/B는 실제 장치 종류를 뜻하지 않는다.| 호출 | 컴파일 | 집계 필터 | backend A · ERR | backend B · INFO |
|---|---|---|---|---|
LOG_ERR | 포함 | 패킷 생성 | 수신 | 수신 |
LOG_INF | 포함 | 패킷 생성 | 제외 | 수신 |
LOG_DBG | 제외 | 검사할 패킷 없음 | 제외 | 제외 |
근거: 3.7.0 Run-time filtering, log_core.h 집계 필터, log_core.c backend 필터.
5. “호출이 끝남”과 “출력이 끝남”은 모드에 따라 다르다
deferred 모드는 호출할 때 메시지를 적재하고 시간이 드는 형식화·전송을 뒤로 미룬다. 다중 스레드 구성에서는 CONFIG_LOG_PROCESS_THREAD를 켜 내부 처리 스레드를 사용할 수 있고, 그렇지 않으면 앱이 초기화와 log_process() 호출을 맡을 수 있다. CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD와 스레드의 주기 설정은 언제 깨워 처리할지에 영향을 준다. 호출 직후 터미널에 문장이 없다고 해서 실패했다고 단정할 수는 없다.
↔ 좁은 화면에서 호출 문맥과 후속 처리 문맥을 가로로 비교하세요.
기본 frontend의 정상 경로에서 필터를 통과하고 버퍼 할당에 성공한 deferred 메시지는 LOG_INF 호출 중에 적재된다. 필터로 제외되거나 버퍼 부족으로 버린 메시지와 panic 경로는 이 시간축에 포함하지 않는다. 적재 직후 처리 스레드를 깨울 수 있으므로 API 복귀 뒤에만 처리된다고 단정할 수 없다. t0~t2는 메시지 처리 단계의 순서이며 실측 지연이나 스레드 실행 순서의 보장이 아니다. backend와 전송 장치가 추가 버퍼를 둘 수 있으므로 이 그림이 물리 매체의 전송 완료를 보장하지는 않는다.
| 모드 | 메시지 처리 | 선택할 때 확인할 비용 |
|---|---|---|
LOG_MODE_DEFERRED | 버퍼 적재 후 알려진 처리 문맥에서 backend 호출 | 버퍼 용량·처리 스레드/수동 호출·드롭 정책 |
LOG_MODE_IMMEDIATE | 로그를 호출한 문맥에서 동기 처리 | ISR·고우선순위 호출 비용과 backend 동시 접근 |
LOG_MODE_MINIMAL | 표준 로그를 printk() 중심으로 출력 | 컴파일 필터는 되지만 런타임 필터·타임스탬프·비동기 경로는 없음 |
근거: Kconfig.mode 4–36행, Kconfig.processing 35–85행, log_core.c 154–195행.
6. 버퍼가 찰 때 “새 것”과 “오래된 것” 중 무엇을 잃는가
deferred 경로는 CONFIG_LOG_BUFFER_SIZE 바이트의 원형 패킷 버퍼를 사용한다. 생산 속도가 처리 속도를 잠시 앞서면 패킷 할당이 실패할 수 있다. CONFIG_LOG_MODE_OVERFLOW=n에서는 할당에 실패하면 새 메시지를 떨어뜨리고, y에서는 새 패킷을 위한 공간을 얻으려고 가장 오래된 대기 메시지를 버린다. 스레드 문맥에서 별도 대기 옵션을 쓴 경우에는 기다린 뒤의 결과를 봐야 한다. 이 옵션은 v3.7.0 Kconfig에서 기본 y지만 최종 .config가 기준이다.
↔ 좁은 화면에서 overflow 설정별로 어느 메시지가 사라지는지 비교하세요.
대기 버퍼가 A·B·C로 꽉 찼다.
공간이 없으면 새 N을 드롭. A·B·C는 대기한다.
같은 조건에서 N을 담으려 한다.
필요한 공간만큼 오래된 대기 메시지를 버린다. N이 보관된다면 A는 잃는다.
A·B·C·N은 같은 크기의 패킷과 LOG_BLOCK_IN_THREAD=n을 가정한 설명용 기호다. 실제 축출 개수와 새 메시지 수용 여부는 패킷 크기·버퍼 상태에 달려 있다.
CONFIG_LOG_BLOCK_IN_THREAD=y는 할당에 실패했을 때 스레드 문맥에서 설정된 시간까지 기다리는 선택지다. ISR를 기다리게 하는 옵션은 아니다. 처리 스레드가 쓰는 경로에서 무기한 대기하도록 설정하면 교착 위험이 있으므로, 지연 상한과 드롭 허용량을 실제 부하에서 함께 확인한다.
| 증상 | 먼저 볼 것 | 판단할 결과 |
|---|---|---|
| 처음부터 일부 레벨이 없음 | LOG_MAX_LEVEL·모듈 레벨 | 컴파일 제거인지 구분 |
| backend마다 출력이 다름 | 활성 backend와 각 runtime filter | 집계 패킷 생성과 개별 전달을 분리 |
| 부하 때 오래된/새 로그가 없음 | LOG_BUFFER_SIZE·LOG_MODE_OVERFLOW·드롭 카운트 | 어느 쪽 메시지가 드롭되는지 확인 |
| 늦게 출력되거나 출력 안 됨 | LOG_PROCESS_THREAD·처리 호출·backend 활성 | 대기 중인 패킷과 최종 전달을 분리 |
근거: 3.7.0 Log message allocation, Kconfig.processing 13–35행, log_core.c 118–136행 및 630–660행.
7. 보드에서 재현할 때 남길 기록
실제 로그 누락을 평가할 때는 출력 문자열 한 장보다 최종 설정·발행량·처리량·backend를 같은 실행에서 기록해야 한다.
- Zephyr commit, 보드, 최종
.config의 LOG·모드·컴파일 최대 레벨·모듈 레벨·런타임 필터 사용 여부를 적는다. - 활성 backend별 레벨과 같은 모듈의 ERR/INF/DBG 호출 결과를 분리해 기록한다. runtime filter만 바꾼 경우와 다시 빌드한 경우를 혼동하지 않는다.
- deferred라면 버퍼 크기·overflow·thread block 설정, 처리 스레드 또는 수동
log_process()호출, 드롭 수를 함께 본다. CONFIG_LOG_PRINTK로printk()를 로깅으로 우회했는지 확인한다. 켜져 있으면 deferred 모드에서printk()출력도 지연될 수 있다.- 오류 후
log_panic()을 이용한다면 활성 backend에 panic 통지 후 대기 메시지를 flush하고 그 뒤 동기 처리한다는 별도 경계를 기록한다.
8. 고정 버전 근거와 설명의 경계
이 글은 v3.7.0의 기본 단일 도메인 로거를 기준으로 한다. 사용자 정의 frontend, LOG_FRONTEND_ONLY, 멀티도메인 링크, dictionary 출력은 선택에 따라 흐름이 달라지므로 그림 1에 합치지 않았다. 실제 UART/RTT 속도나 로그 손실률은 보드·백엔드·호출 빈도에 의존하며 여기서 측정하지 않았다.