핵심 정리
import diagnostics_channel from "node:diagnostics_channel";
const channel = diagnostics_channel.channel("myapp:http:request");
function publishRequest(message) {
if (channel.hasSubscribers) channel.publish(message);
}
diagnostics_channel.subscribe("myapp:http:request", (message) => {
console.log("[trace]", message.method, message.url, message.durationMs);
});진단 채널
먼저 구분해 둘 기본형은 아래입니다.
- 채널 생성:
diagnostics_channel.channel(name) - 구독 여부 확인:
hasSubscribers - 발행:
channel.publish(message) - 구독:
diagnostics_channel.subscribe(name, handler) - 채널 이름과 payload shape를 계약처럼 관리
"이벤트 처리"보다 "계측 신호"로 읽는다
핵심은 앱 로직을 구독자 구현과 느슨하게 분리하는 데 있습니다. 로직은 채널에 신호만 발행하고, 로그/APM/트레이스는 구독자가 처리합니다.
hasSubscribers가 중요한 이유
구독자가 없을 때 계측 비용을 거의 0에 가깝게 줄일 수 있습니다. 라이브러리 코드에 넣기 좋은 이유도 여기 있습니다.
채널 이름과 메시지 shape는 계약이다
myapp:http:request:start처럼 이름을 고정하고, 필드도 버전처럼 관리해야 합니다. 관측성 카드에서 제일 자주 깨지는 건 구현이 아니라 메시지 shape 변경입니다.
앱 로직과 관측성 로직을 섞지 않는 것이 핵심이다
이 패턴은 EventEmitter 대체재라기보다, 관측성 코드를 비즈니스 로직에서 떼어내는 데 가깝습니다. 핵심 로직은 publish만 하고, 로깅·트레이싱·메트릭은 구독자에서 처리하는 구조가 가장 읽기 쉽습니다.
const channel = diagnostics_channel.channel("myapp:db:query");
export async function runQuery(sql) {
const startedAt = Date.now();
try {
return await db.query(sql);
} finally {
if (channel.hasSubscribers) {
channel.publish({ sql, durationMs: Date.now() - startedAt });
}
}
}핵심 함수는 쿼리 실행만 책임지고, 로그 수집이나 APM 연결은 구독자 쪽으로 미루면 관측성 요구가 바뀌어도 비즈니스 로직을 덜 건드리게 됩니다.
언제 계측할까
체크포인트
-
계측 채널 생성:
diagnostics_channel.channel(name) -
무구독 오버헤드 회피:
hasSubscribers -
채널 이름:
namespace:event -
로거/APM 연결: 구독자에서 처리
-
메시지 구조: 필드 shape 고정
-
비즈니스 로직은 publish만 하고 관측성 처리는 구독자로 분리
-
테스트나 일회성 계측에서는
unsubscribe정리도 같이 본다
주의할 점
관측성 신호는 API처럼 다뤄야 한다. 이름과 payload shape를 자주 바꾸면 로깅과 APM 파이프라인이 조용히 깨진다.
const onQuery = (message) => console.log(message.sql);
diagnostics_channel.subscribe("myapp:db:query", onQuery);
// ... 테스트/임시 계측 ...
diagnostics_channel.unsubscribe("myapp:db:query", onQuery);참고 링크
1 sources