@point3/observability
v0.3.1
Published
OpenTelemetry SDK wrapper for Point3 services
Readme
@point3/observability
Point3 서비스용 OpenTelemetry SDK 래퍼. import 한 줄로 traces, metrics, logs 수집이 시작된다.
설치
npm install @point3/observability@opentelemetry/api는 peer dependency로 자동 설치된다 (npm v7+).
사용법
기본 (제로 설정)
main.ts 최상단에 side-effect import 추가. 반드시 dotenv/config 다음, 모든 애플리케이션 import 전에 위치해야 한다. OTel의 monkey-patching이 모듈 로드 전에 적용되어야 하기 때문.
import "dotenv/config";
import "@point3/observability";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";서비스명은 package.json의 name 필드에서 자동으로 읽는다. 별도 설정 불필요.
커스텀 설정
instrumentation 추가, exporter 교체 등이 필요한 경우 register() 함수를 사용한다. ESM import hoisting 때문에 반드시 별도 파일로 분리해야 한다.
// src/observability.ts
import { register } from "@point3/observability/register";
register({
instrumentations: (defaults) => [...defaults, new PrismaInstrumentation()],
});// src/main.ts
import "dotenv/config";
import "./observability";
import { NestFactory } from "@nestjs/core";로깅
register() 호출 시 console.log, console.error 등 표준 콘솔 메서드가 자동으로 패치된다. 패치된 console은 두 가지 일을 동시에 수행한다:
- stdout/stderr 출력 — 터미널에서 로그를 바로 확인할 수 있다
- OTel LogRecord 전송 — OTLP를 통해 Alloy → Loki로 수집된다
active span이 있으면 trace_id와 span_id가 자동으로 LogRecord에 주입되어, Loki에서 Tempo 트레이스로 바로 연결할 수 있다.
console 메서드 → 로그 레벨 매핑
| console 메서드 | 로그 레벨 | 출력 대상 |
|---------------|----------|----------|
| console.log() | info | stdout |
| console.info() | info | stdout |
| console.warn() | warn | stdout |
| console.error() | error | stderr |
| console.debug() | debug | stdout (기본 LOG_LEVEL=info에서 필터됨) |
구조화 로깅
첫 번째 인자가 객체면 message 필드를 로그 메시지로, 나머지 필드를 OTel LogRecord attributes로 자동 추출한다.
// 단순 문자열
console.log("서버 시작됨");
// 구조화 로깅 — orderId, amount가 OTel attributes로 추출됨
console.log({ message: "주문 생성", orderId: "123", amount: 50000 });
// Error 객체 — exception.stacktrace, exception.type이 자동 추가됨
console.error(new Error("결제 실패"));레벨 필터링
LOG_LEVEL 환경변수로 출력할 최소 레벨을 지정한다. 기본값은 info.
# error, fatal만 출력
LOG_LEVEL=error node main.js
# debug 이상 전부 출력
LOG_LEVEL=debug node main.js레벨 우선순위: fatal < error < warn < info < debug < verbose
콘솔 패치 비활성화
OTel SDK는 사용하되 console 몽키패치를 원하지 않는 경우:
import { register } from "@point3/observability/register";
register({ patchConsole: false });커스텀 로거 통합
console 패치 대신 직접 로깅을 제어해야 하는 경우 (예: 컴플라이언스 요구사항으로 PII 로그를 별도 파이프라인으로 분리), 필요 시 emitLog를 직접 사용할 수 있다.
import { register } from "@point3/observability/register";
import { emitLog } from "@point3/observability/log";
import type { LogLevel } from "@point3/observability/log";
register({ patchConsole: false }); // console 자동 패치 비활성화
// 관측 로그 → OTLP → Loki
emitLog("info", ["이체 요청 수신", { requestId: "abc" }]);
// PII 로그 → 별도 처리 (emitLog를 호출하지 않으므로 Loki에 안 감)
auditLogger.write({ accountNumber: "...", amount: 50000 });Alloy 설정
@point3/observability가 전송하는 OTLP 로그를 Loki로 라우팅하기 위해 Alloy 설정(config.alloy)을 수정한다.
기존 config에서 3줄만 추가하면 된다:
- receiver output에
logs추가 - batch processor output에
logs추가 otelcol.exporter.loki컴포넌트 추가
전체 표준 config
// ─── OTLP 수신 ───
otelcol.receiver.otlp "app_service" {
grpc {
endpoint = "0.0.0.0:4317"
}
http {
endpoint = "0.0.0.0:4318"
}
output {
metrics = [otelcol.processor.batch.default.input]
traces = [otelcol.processor.batch.default.input]
logs = [otelcol.processor.batch.default.input]
}
}
// ─── 배치 처리 ───
otelcol.processor.batch "default" {
output {
metrics = [otelcol.exporter.prometheus.app_service.input]
traces = [otelcol.exporter.otlp.tempo.input]
logs = [otelcol.exporter.loki.default.input]
}
}
// ─── Metrics → Prometheus ───
otelcol.exporter.prometheus "app_service" {
forward_to = [prometheus.remote_write.default.receiver]
}
// ─── Traces → Tempo ───
otelcol.exporter.otlp "tempo" {
client {
endpoint = "<TEMPO_HOST>:4317"
tls {
insecure = true
}
}
}
otelcol.exporter.loki "default" {
forward_to = [loki.write.default.receiver]
}
prometheus.remote_write "default" {
endpoint {
url = "http://<PROMETHEUS_HOST>:9090/api/v1/write"
}
}
loki.write "default" {
endpoint {
url = "http://<LOKI_HOST>:3100/loki/api/v1/push"
}
}변경 요약
| # | 위치 | 변경 |
|---|------|------|
| 1 | otelcol.receiver.otlp output | logs = [otelcol.processor.batch.default.input] 추가 |
| 2 | otelcol.processor.batch output | logs = [otelcol.exporter.loki.default.input] 추가 |
| 3 | 새 컴포넌트 | otelcol.exporter.loki "default" → 기존 loki.write.default 재사용 |
otelcol.exporter.loki는 OTel 리소스 속성 service.name을 Loki 레이블 service_name으로 자동 변환한다. 별도 transform 프로세서가 필요 없다.
내보내기 목록
| export | 설명 |
|--------|------|
| emitLog(level, args) | OTel LogRecord 전송 + stdout/stderr 출력 |
| shouldLog(level) | LOG_LEVEL 기준 해당 레벨 출력 여부 |
| getConfiguredLogLevel() | 현재 설정된 로그 레벨 반환 |
| getOriginalConsole() | 패치 이전의 원본 console 메서드 |
| patchConsole() / unpatchConsole() | console 패치 수동 제어 |
| LogLevel, LogEntry | 타입 |
환경변수
| 변수 | 설명 | 기본값 |
|------|------|--------|
| OTEL_SERVICE_NAME | 서비스 이름 (최우선) | package.json name |
| OTEL_SERVICE_VERSION | 서비스 버전 | package.json version |
| OTEL_EXPORTER_OTLP_ENDPOINT | OTLP 수신 엔드포인트 | http://alloy:4318 |
| LOG_LEVEL | 로그 출력 레벨 (error, warn, info, debug, verbose) | info |
| NODE_ENV | 실행 환경 | — |
설정 우선순위: 환경변수 > register() 옵션 > package.json > 기본값
기본 동작
설정 없이 import만 하면 아래가 자동으로 구성된다:
- Exporters: OTLP HTTP (traces, metrics, logs) →
http://alloy:4318 - Instrumentations:
getNodeAutoInstrumentations()+RuntimeNodeInstrumentation - Metric 전송 간격: 2000ms
- 런타임 계측 정밀도: 2000ms
- Graceful Shutdown:
SIGINT,SIGTERM시 SDK 종료 후process.exit()
진입점
| import 경로 | 동작 |
|------------|------|
| @point3/observability | side-effect — import 시 즉시 SDK 시작 |
| @point3/observability/register | register() 함수만 export, side-effect 없음 |
| @point3/observability/log | emitLog, patchConsole 등 로그 유틸리티 export |
@point3/observability를 import하면 내부적으로 register()를 옵션 없이 호출한다. 커스텀이 필요하면 @point3/observability/register에서 직접 호출.
빌드
npm run build # tsup으로 ESM + CommonJS 듀얼 빌드출력: dist/ — .js(ESM), .cjs(CJS), .d.ts(타입), .map(소스맵)
