Skip to main content

SWR баг — Logging стандарт

Хамрах хүрээ: төвлөрсөн цуглуулагч руу лог илгээдэг бүх сервис · Формат: stdout руу JSON · Талбарын нэршил: OpenTelemetry semantic conventions · Цуглуулга: Loki / ELK / Datadog

Энэ бол гаралтад тавих шаардлага болохоос logging library-ийн заавар биш. Ямар хэл, ямар library ашиглахыг баг өөрөө сонгоно (Go log/slog, Rust tracing, Node pino — ялгаагүй). Гаралт нь §3-ын формат, §4-ийн талбартай таарч байвал стандарт хангагдсан. Кодын жишээ энд байхгүй нь санаатай: library-ийн API өөрчлөгддөг, гаралтад тавих шаардлага өөрчлөгддөггүй.


1. Үндсэн зарчим​

  • Лог бол машин уншдаг өгөгдөл, хүнд зориулсан өгүүлбэр биш. Түүнийг grep-ээр биш, query-гээр уншина.
  • Нэг үйл явдал = нэг JSON мөр. Олон мөрт текст, stack trace нь тусдаа талбар болно.
  • message тогтмол, утга нь талбар. Ингэснээр групплэх, тоолох, дохиолол тавих боломжтой.
  • Нэг хүсэлт = нэг лог мөр (§7). Алхам бүрийг INFO-гоор бичих нь өртгийг нэмж, уншихад хэцүү болгоно.
  • Нууц мэдээлэл хэзээ ч логонд орохгүй (§10). Гаргаснаа буцааж авах арга байхгүй.
  • Лог бол хамгийн үнэтэй, хамгийн бүтэц муутай сигнал. Тоолж болох зүйлийг metric, гүйцэтгэлийн замыг trace, үлдсэнийг нь лог болго.

Хэмжсэн үндэслэл (2026 оны 09 дүгээр сарын 21): twelve-factor (12factor.net/logs), OpenTelemetry logs data model ба semantic conventions, W3C Trace Context, Stripe-ийн canonical log lines, Kubernetes-ийн logging conventions, OWASP Logging Cheat Sheet, Grafana Loki-гийн label best practices — эдгээр анхдагч эх сурвалжийг шалгаж бичсэн (§15).


2. Хамрах хүрээ​

ХамаарнаХамаарахгүй
stdout руу лог гаргаж, цуглуулагч (Loki/ELK/Datadog) руу орж байгаа бүх сервисMetric ба trace — өөр сигнал, өөр стандарт
Batch job, worker, cron, CLI хэрэгсэлТөхөөрөмж дээрх клиент (Android/logcat) — нэршлийг нь дагана, харин pipeline-ын дүрэм (§3, §12) хамаарахгүй
Дотоод сервис хоорондын дуудлагаAudit log — албан ёсны эх сурвалж нь БД дэх audit хүснэгт байна. Лог бол зөвхөн хуулбар (§11)

3. Гаралтын формат​

  • stdout руу бич. Файл руу бичих, log rotation хийх, лог илгээх код бичих шаардлагагүй — эдгээрийг цуглуулагч гүйцэтгэнэ.
  • Мөр бүр нэг бүтэн JSON объект, UTF-8, \n-ээр төгсөнө. Хагас мөр, олон мөрт JSON байж болохгүй.
  • Талбарын түлхүүр нэг түвшинд (flat) байна; цэгтэй нэрийг шууд түлхүүр болгоно: "service.name": "payment-api". Үүрлэсэн объект ашиглавал цуглуулагч дээр нэг түвшинд задардаг байх ёстой.
  • Тоог тоогоор, true/false-ийг boolean-оор бич. "duration_ms": "42" гэж тэмдэгт мөр болговол дундаж, percentile тооцох боломжгүй болно.
  • Өнгө (ANSI), баннер, progress bar production-д байхгүй.
{
"@timestamp": "2026-09-21T04:19:27.337Z",
"log.level": "INFO",
"message": "Handled HTTP request",
"service.name": "payment-api",
"service.version": "a1b2c3d",
"deployment.environment.name": "production",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"http.request.method": "POST",
"url.path": "/v1/charges",
"http.route": "/v1/charges",
"http.response.status_code": 201,
"duration_ms": 42,
"user.id": "usr_18f3",
"payment.order_id": "ord_92kd"
}

4. Талбарууд​

4.1 Заавал​

ТалбарУтга
@timestampRFC3339 / ISO-8601, UTC, миллисекундтэй
log.levelDEBUG / INFO / WARN / ERROR (том үсгээр)
messageТогтмол, богино тайлбар — хувьсагч оруулахгүй (§5)
service.nameСервисийн нэр, ж: payment-api. Бүх орчинд ижил бичигдэнэ
service.versionBuild хувилбар эсвэл git SHA
deployment.environment.namedev / staging / production

4.2 Нөхцөлт (боломжтой үед заавал)​

ТалбарХэзээ
trace_id, span_idХүсэлт/ажлын контекст доторх бүх лог (§9)
http.request.method, url.path, http.route, http.response.status_codeHTTP хүсэлт боловсруулсан бол
duration_msХугацаа хэмжсэн үйлдэл (тоогоор)
error.type, error.message, error.stack_traceБүх ERROR мөрөнд (§8)
user.idХэрэглэгчийн нэрийн өмнөөс хийсэн үйлдэл — ID, нэр/имэйл биш
client.address, user_agent.originalГаднаас ирсэн хүсэлт, аюулгүй байдлын үйл явдал

4.3 Нэршлийн дүрэм​

  • OpenTelemetry-д нэр нь тодорхойлогдсон бол түүнийг ашигла — http.request.method (method биш), url.path (path биш), http.response.status_code (status биш). Ингэснээр нэршлийн талаар маргах шаардлагагүй болж, хожим OTel collector эсвэл trace рүү шилжихэд лог аль хэдийн нийцсэн байна.
  • OTel-д байхгүй домэйн талбарыг <домэйн>.<талбар> хэлбэрээр, snake_case-ээр: payment.order_id, vms.machine_id, tether.device_serial. Ерөнхий id, name, type гэж дангаар бүү нэрлэ.
  • Нэг түлхүүр = нэг утга, бүх сервист адил. user.id гэж нэг газар, uid гэж нөгөө газар бичих нь хоёр өөр талбар үүсгэж, хайлт ажиллахгүй болно.
  • Хэмжих нэгжийг нэрэнд нь бич: duration_ms, size_bytes, amount_mnt.
  • Талбарын утга нь хязгааргүй урт биш — том агуулгыг (request body, HTML, base64) логонд бүү хий. Шаардлагатай бол уртыг нь хязгаарлаад ..._truncated: true тэмдэглэ.

5. message бичих​

message бол лог мөрүүдийг групплэх түлхүүр. Динамик утга орвол групп бүрдэхээ болино.

ЗөвБурууШалтгаан
"message": "Handled HTTP request" + талбарууд"message": "GET /v1/charges 201 in 42ms"Бүх мөр өөр — тоолох, дохиолох боломжгүй
"message": "Charge failed" + payment.order_id"message": "Charge failed for order ord_92kd"ID нь мөрийн текст дотор орвол индексжихгүй
"message": "Deleted expired session""message": "done"Юу болсныг хэлээгүй

Найруулгын дүрэм (Kubernetes-ийн конвенцийг дагасан):

  • Том үсгээр эхэл, төгсгөлд нь цэг бүү тавь.
  • Идэвхтэй хэлбэр, өнгөрсөн цаг: "Could not delete pod", "Deleted pod".
  • Объектыг нэрлэхдээ төрлийг нь хэл: "Deleted session", зүгээр "Deleted" биш.
  • Англи хэлээр бич — grep, дохиолол, гадаад хэрэгсэлтэй нийцтэй байна.

6. Түвшин​

ТүвшинХэзээЖишээ
DEBUGХөгжүүлэлт, алдаа хайх нарийн мэдээлэл. Production-д байнга идэвхтэй байхгүйзавсрын утга, гадаад дуудлагын payload (нууцгүй)
INFOХэвийн боловч ач холбогдолтой үйл явдалхүсэлт боловсруулсан, төлбөр амжилттай, ажил эхэлсэн
WARNГэнэтийн боловч систем үргэлжилсэн нөхцөлretry хийсэн, fallback ажилласан, quota-д ойртсон
ERRORҮйлдэл амжилтгүй болсон бөгөөд хүн арга хэмжээ авах ёстойБД-тэй холболт тасарсан, гадаад төлбөрийн систем хариу өгөхгүй

Дүрэм:

  • ERROR нь дохиоллын түвшин. Хэн ч хариу арга хэмжээ авахгүй бол WARN болго — эсвэл бүү бич. Хэт олон давтагдсан ERROR-ыг хэсэг хугацааны дараа хэн ч үзэхээ болино.
  • Хэрэглэгчийн буруугаас үүссэн хүлээгдэж буй алдаа (400, 404, баталгаажуулалт амжилтгүй) нь ERROR биш — INFO/WARN.
  • FATAL/panic-ийг зөвхөн эхлэх үед (тохиргоо уншиж чадаагүй, порт нээгдэхгүй) ашигла. Хүсэлт боловсруулах явцад процессоо бүү зогсоо.
  • Production-ийн default: INFO. Түвшинг дахин build хийхгүйгээр (env, config reload) сольж чаддаг байх ёстой.
  • Шинэ түвшин бүү нэм. Дөрөв хангалттай — TRACE, NOTICE, CRITICAL нэмбэл хэн ч ялгааг нь мэдэхгүй.

7. Нэг хүсэлт = нэг лог мөр​

Хүсэлт бүрийн төгсгөлд бүх мэдээллийг нэгтгэсэн (canonical) нэг мөр бич: тухайн хүсэлтийн талаар мэдэх хэрэгтэй бүх зүйл нэг мөрөнд.

  • Багтаах талбарууд: http.request.method, http.route, http.response.status_code, duration_ms, trace_id, user.id, домэйн ID (payment.order_id), алдаа гарсан бол error.type, тоолуур (db.duration_ms, upstream.duration_ms).
  • Алхам бүрт INFO бүү бич. Алхмууд нь DEBUG эсвэл span.
  • Ашиг: нэг хүсэлтийн явцыг сэргээхийн тулд олон мөр нийлүүлэх шаардлагагүй, лог хэмжээ хэд дахин буурч, тоолох болон групплэх нь ганц query болж хялбарчилна.
  • Health check, readiness probe, static asset-ийн хүсэлтийг бүү логло — зөвхөн хэрэггүй мөр нэмнэ.

8. Алдааны лог​

  • Алдааг нэг удаа, хамгийн дээд түвшинд (handler / boundary) логло. Доод давхарга нь алдаагаа буцаана, логлохгүй: дуудагч нь алдааг хүлээж аваад өөрөө зохицуулж магадгүй, тэгвэл доор бичсэн ERROR худал дохиолол болно.
  • Алдааг бүтэцтэй талбарт хий: error.type (класс/төрөл), error.message, error.stack_trace. message-тэй нийлүүлж бүү бич.
  • Алдааг буцаахдаа контекст нэм (Go-д %w, Rust-д anyhow/thiserror, Java-д cause) — тэгвэл boundary дээрх ганц лог алдаа хаанаас эхэлснийг бүрэн харуулна.
  • Контекст талбарыг (payment.order_id гэх мэт) тухайн лог мөрөнд заавал нэм. "Charge failed" гэсэн ганц мөрөөр юу болсныг тогтоох боломжгүй.
  • Process унасан тохиолдолд (panic/crash) — нэг мөр, stack trace error.stack_trace талбарт.

9. Trace холболт ба контекст​

  • Сервис хооронд W3C Trace Context header дамжуулна: traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 (version-trace_id-span_id-flags). Header байхгүй бол шинээр үүсгэ. Байгаа бол дараагийн сервис рүү дамжуул; traceparent-ийг өөрчлөөгүй бол tracestate-ийг ч бүү өөрчил.
  • Хүсэлтийн туршид trace_id, span_id-г контекстэд (Go context.Context, MDC, task-local) хадгалж, лог бүрт автоматаар нэмэгддэг байхаар тохируул. Гар аргаар талбар нэмэх нь мартагдана.
  • Thread/goroutine дахин ашиглагддаг — хүсэлт дуусахад контекстээ цэвэрлэ, эс бөгөөс өмнөх хэрэглэгчийн ID дараагийн хүсэлтийн лог дээр буруугаар бичигдэнэ.
  • Асинхрон ажил, дараалал (queue, cron): traceparent-ийг мессежийн header-т дамжуулж, ажлын лог эх хүсэлттэйгээ холбогдоно.
  • request_id (эсвэл X-Request-ID) хэрэглэсээр байж болно, гэхдээ trace_id нь үндсэн холбоос — trace болон логийг хооронд нь холбодог цорын ганц талбар юм.

10. Нууцлал — юу логлохгүй вэ​

Хэзээ ч логлохгүй:

  • Нууц үг, token, API key, refresh token, session ID, cookie, Authorization header, JWT-ийн бүтэн агуулга
  • Картын дугаар, CVV, банкны дансны дугаар (шаардлагатай бол ****1234)
  • Хувийн мэдээлэл: овог нэр, утас, и-мэйл, регистрийн дугаар, хаяг — ID-гаар орлуул (user.id)
  • Шифрлэлтийн түлхүүр, холболтын мөр (connection string), дотоод тохиргооны нууц

Дүрэм:

  • Эх үүсвэр дээрээ хас. Цуглуулагч дээрх шүүлтүүр бол зөвхөн нөөц хамгаалалт — library солигдох, шинэ талбар нэмэгдэх бүрт ажиллахаа болино.
  • Нууц утгыг тусгай төрлөөр илэрхийл: тухайн төрөл логлогдоход REDACTED гаргадаг байх (Go slog.LogValuer, Rust Debug-ийг гараар бичих, Java toString()). Тэгвэл мартах боломжгүй болно.
  • Бүтэн объект, request/response body-г бүхлээр нь бүү логло — шаардлагатай талбарыг нь сонгож ав.
  • Log injection: хэрэглэгчээс ирсэн текстийг message-тэй бүү нийлүүл. Тусдаа талбарт хийвэл JSON encoding нь CR/LF, хиймэл лог мөр үүсгэх оролдлогыг саармагжуулна.
  • Эргэлзвэл бүү логло. Нэг удаагийн дебаг хийхийн тулд нэмсэн нууц талбар production-д үлддэг.

11. Юуг заавал логлох вэ​

Аюулгүй байдал, санхүүгийн шалгалтад дараах үйл явдлууд заавал лог болно (OWASP-ийн жагсаалтыг тус багийн нөхцөлд тохируулсан):

  • Нэвтрэлт амжилттай/амжилтгүй болсон, системээс гарсан, нууц үг сэргээсэн, MFA үйлдэл
  • Эрх шалгалтад амжилтгүй болсон оролдлого (403), эрх/роль өөрчлөлт, хэрэглэгч үүсгэх/идэвхгүй болгох
  • Админы үйлдэл: тохиргоо өөрчлөх, өгөгдөл устгах, бөөнөөр export хийх
  • Мөнгөн гүйлгээ: төлбөр, буцаалт, гар аргаар залруулга — payment.order_id, дүн, үр дүнтэйгээр
  • token олгосон/хүчингүй болгосон, төхөөрөмж bind/unclaim хийсэн (tether)
  • Сервис эхэлсэн/зогссон, тохиргоо ачаалсан (хувилбар, орчинтой нь)

Албан ёсны audit бичлэг нь лог биш. Хэн юу хийснийг нотлох шаардлагатай бол БД дэх audit хүснэгтэд бич — лог нь хадгалах хугацаа дуусахад устдаг бөгөөд өөрчлөгдөөгүй гэдэг нь баталгаагүй.


12. Хэмжээ, cardinality, хадгалалт​

Лог нь хязгааргүй биш — өртөг нь эзлэхүүн, индексийн cardinality-тэй шууд хамааралтай.

  • Label (Loki stream) нь зөвхөн тогтмол, бага утгатай талбар байна: service.name, deployment.environment.name, namespace/cluster. trace_id, request_id, user.id, payment.order_id-г label болгож болохгүй. Эдгээрийг label болгох нь Loki-гийн баримт бичигт тусгайлан нэрлэсэн алдаа юм: индекс томорч, chunk жижигрэнэ.
  • Бусад бүх талбар лог мөрийн дотор байна; шүүлтийг query дээр хий ({service_name="payment-api"} | json | user_id="usr_18f3").
  • Өндөр давтамжтай, хэвийн үйл явдалд sampling тавь (ж: 1/100), алдааг 100% үлдээ.
  • Давтагдсан алдааг мөр бүрээр бүү бич — нэгтгэж тоолуураар (occurrences) эсвэл metric-ээр гарга.
  • Хадгалах хугацааг баг бүр тодорхойлж, энэ хуудсанд бичнэ. Default: 30 хоног. Үүнээс урт шаардлагатай бол тэр өгөгдөл лог биш — audit хүснэгт эсвэл архивт хадгалагдана.
  • DEBUG-ийг production-д идэвхжүүлбэл хугацаатай, зорилготой идэвхжүүл (нэг сервис, нэг асуудал) — идэвхтэй хэвээр орхивол өртөг шууд хэдэн дахин өснө.

13. Хөгжүүлэлтийн орчин​

  • Хөгжүүлэгчийн машин дээр хүн уншихад ээлтэй (текст, өнгөтэй) гаралтыг зөвшөөрнө — LOG_FORMAT=text гэх мэт нэг л тохиргоогоор солигддог байх.
  • staging, production дээр үргэлж JSON. Хоёр орчны ялгаа зөвхөн формат, түвшинд байна — логлох газар, талбар нь ижил.
  • System.out.println, fmt.Println, console.log, println! — сервисийн код дотор байж болохгүй. Эдгээр нь цуглуулагч дээр бүтэцгүй мөр болж, хайлтад олдохоо болино.

14. Шалгах жагсаалт​

Хийх (Do)

  • stdout руу нэг мөр JSON · message тогтмол · утгыг талбарт, тоог тоогоор
  • OTel нэршил (http.request.method, url.path) · домэйн талбар <домэйн>.<талбар>
  • trace_id/span_id контекстээс автоматаар · traceparent-ийг дамжуулах
  • Хүсэлт бүрт нэг canonical мөр · алдааг boundary дээр нэг удаа, error.* талбартай
  • Нууцыг эх үүсвэр дээр хасах · §11-ийн үйл явдлыг заавал логлох

Бүү хий (Don't)

  • message-д утга оруулах · "duration_ms": "42" гэж тэмдэгт мөр болгох · чөлөөт текст, println
  • Нууц, PII, бүтэн body логлох · хэрэглэгчийн текстийг message-тэй нийлүүлэх
  • Алдааг давхар логлох · арга хэмжээ шаардахгүй зүйлийг ERROR бичих
  • trace_id, user_id-г Loki label болгох · health check логлох
  • Production-д DEBUG-ийг идэвхтэй хэвээр орхих · хүсэлтийн явцад процессоо зогсоох

15. Эх сурвалж​

Энэ стандартын дүрэм бүр дараах анхдагч эх сурвалжаас гаралтай (2026 оны 09 дүгээр сарын 21-нд шалгасан):

Эх сурвалжЮуг тодорхойлсон
The Twelve-Factor App — Logsstdout руу бичих, чиглүүлэлт/хадгалалтыг апп хийхгүй (§3)
OpenTelemetry semantic conventions · logs data modelТалбарын нэршил, түвшний утга, trace холбоос (§4, §6)
W3C Trace Contexttraceparent формат ба дамжуулалт (§9)
Stripe — Canonical log linesХүсэлт бүрт нэгтгэсэн нэг мөр (§7)
Kubernetes logging conventionsmessage-ийн хэв маяг, алдааг буцаах уу логлох уу (§5, §8)
OWASP Logging Cheat SheetЗаавал логлох үйл явдал, логлохгүй өгөгдөл, log injection (§10, §11)
Grafana Loki — label best practicesLabel-ын cardinality, юуг label болгохгүй (§12)

16. Нэр томьёо​

ҮгЮу гэсэн үг вэ
structured logМашин уншиж, indexlэж чаддаг форматтай лог. Энд JSON (§3)
collector (цуглуулагч)Loki / ELK / Datadog — контейнерын stdout-оос логийг цуглуулж, хадгалж, хайх боломжтой болгодог систем
stdoutПроцессын үндсэн гаралтын урсгал. Лог энд бичигдэнэ, файл руу биш (§3)
trace / spantrace нь нэг хүсэлтийн бүх сервисээр дамжсан бүтэн зам; span нь тэр замын нэг алхам. trace_id, span_id нь тэдгээрийн таних дугаар (§9)
traceparentW3C-ийн стандарт header. trace_id, span_id-г сервис хооронд дамжуулна (§9)
canonical log lineХүсэлт бүрийн төгсгөлд бичих, тухайн хүсэлтийн талаарх бүх мэдээллийг агуулсан ганц мөр (§7)
cardinalityТалбарын ялгаатай утгын тоо. service.name — цөөн, trace_id — бараг хязгааргүй. Өндөр cardinality-тай талбарыг label болгож болохгүй (§12)
label (Loki)Loki-гийн индексждэг түлхүүр. Утгын тоо цөөн байх ёстой. JSON доторх талбараас өөр зүйл (§12)
samplingИжил төрлийн лог мөрийн зөвхөн тодорхой хувийг бичих (ж: 1/100) — хэмжээ, өртгийг барих арга (§12)
boundaryХүсэлтийг хүлээж авдаг хамгийн гадна давхарга (HTTP handler, controller). Алдааг энд нэг удаа логлоно (§8)
context (MDC, context.Context)Хүсэлтийн туршид зөөгддөг талбаруудын сав. trace_id үүнд сууж, лог бүрт автоматаар нэмэгдэнэ (§9)
PIIХувийн мэдээлэл — нэр, утас, и-мэйл, регистрийн дугаар. Логонд хэзээ ч бичихгүй (§10)
redactНууц утгыг логонд гарахаас нь өмнө хасах, эсвэл REDACTED болгож солих (§10)