43 lines
3.6 KiB
Markdown
43 lines
3.6 KiB
Markdown
---
|
|
title: interview / deterministic-logback-asyncappender-drop-metric-test-2026-06-14
|
|
source_type: interview-prep
|
|
status: raw
|
|
related_branches: [feature-log-management-contract]
|
|
related_projects: [ca-skeleton]
|
|
tags: [interview, ca-skeleton, logback, asyncappender, micrometer, testing, determinism, observability, masking]
|
|
created: 2026-06-14
|
|
status_label: captured
|
|
---
|
|
|
|
# interview: deterministic-logback-asyncappender-drop-metric-test-2026-06-14
|
|
|
|
> Layer: `raw/interviews/` — 작업에서 파생된 면접/구두설명 질문 원석.
|
|
|
|
## Parent / 부모
|
|
|
|
- [[raw/branch-notes/feature-log-management-contract]] — DRIFT-5(`log.appender.dropped.total`) + DRIFT-2(Layer 1 masking) 구현에서 파생.
|
|
|
|
## Q1. Logback `AsyncAppender` 의 드롭(discard)을 어떻게 *결정론적으로* 테스트하나?
|
|
|
|
`AsyncAppender` 는 queue 잔여 용량이 `discardingThreshold` 밑으로 떨어지면 ≤INFO 이벤트를 조용히 버린다. 이 드롭은 worker 스레드 drain 타이밍에 의존 → 단순 burst 테스트는 flaky.
|
|
|
|
**트릭**: `discardingThreshold > queueSize` 로 설정하면 `getRemainingCapacity()`(최대 queueSize) `< discardingThreshold` 가 **항상 true** → `isQueueBelowDiscardingThreshold()` 항상 참 → 모든 discardable(≤INFO) 이벤트가 **호출 스레드에서 동기 드롭**. async worker 타이밍이 식에서 제거되어 카운터 단언이 결정론적. (logback `AsyncAppenderBase.start()` 는 `discardingThreshold == -1` 일 때만 `queueSize/5` 로 기본값 설정 — 명시값을 상한 캡 하지 않음을 바이트코드로 확인.) WARN/ERROR 는 `isDiscardable()==false` 라 같은 조건에서도 드롭/카운트 안 됨을 같은 테스트로 검증.
|
|
|
|
## Q2. Logback 이 Spring 보다 먼저 초기화되는데 custom appender 가 Micrometer 카운터를 어떻게 발행하나?
|
|
|
|
`io.micrometer.core.instrument.Metrics.globalRegistry`(정적 composite)로 발행. Spring Boot 가 애플리케이션 `MeterRegistry` 를 글로벌 composite 에 추가하므로 logback 이 먼저 떠도 결국 actuator/metrics 에 노출. 테스트는 `SimpleMeterRegistry` 를 `Metrics.addRegistry` 로 붙였다 `removeRegistry` 로 떼며 격리. 태그 cardinality 는 레지스트리 SSOT(`metrics.yaml`)의 `level∈{INFO,DEBUG}` 로 제한.
|
|
|
|
## Q3. 구조화 JSON 로그에서 secret 마스킹은 왜 `%replace`(PatternLayout converter)로 부족한가?
|
|
|
|
`%replace` 는 PatternLayout 단계 converter. 그러나 `LogstashEncoder` 는 PatternLayout 을 **우회**해 JSON 을 직접 생성 → `%replace` 미적용(마스킹 누락). JSON 경로는 `MaskingJsonGeneratorDecorator`(JSON 생성 시점 value masker), pattern 경로는 별도 converter(`%maskedMsg`)로 같은 정규식. 정규식 catalog 를 단일 SSOT 로 두어 양 경로 일관. 상세: [[raw/blog-topics/logback-layer1-secret-masking-json-vs-pattern-2026-06-14]].
|
|
|
|
## Q4. `javax.crypto.Mac` 이 thread-safe 하지 않은데 singleton pseudonymizer bean 에서 어떻게 다루나?
|
|
|
|
`Mac` 은 상태를 가져 thread-safe 하지 않다. 옵션: (a) 호출마다 `Mac.getInstance` 새로 생성(단순·안전), (b) `ThreadLocal<Mac>`, (c) 인스턴스 풀. 본 구현은 (a) — `SecretKeySpec`(불변)만 필드로 보관, `pseudonymize()` 마다 `Mac` 생성+init. HMAC-SHA-256 은 JDK 보장 알고리즘이라 checked 예외는 unchecked 로 래핑(사실상 도달 불가). salt 는 생성자에서 방어적 clone.
|
|
|
|
## 관련 / Related
|
|
|
|
- [[raw/branch-notes/feature-log-management-contract]]
|
|
- [[raw/blog-topics/logback-layer1-secret-masking-json-vs-pattern-2026-06-14]]
|
|
- [[raw/errors/webmvctest-component-filter-constructor-dep-breaks-slice-2026-06-14]]
|