सादा पाठ लॉग तब ठीक था जब एक मशीन पर एक ऐप चलता था। जब कई सेवाएँ, कई रेप्लिका, और हर पंक्ति को खोजने योग्य इवेंट बनाने वाला शिपपर हो, तो खुली स्ट्रिंग महँगा शोर बन जाती है। संरचित लॉग आकार ठीक करते हैं: हर पंक्ति स्थिर कुंजियों वाला छोटा दस्तावेज़ है। गद्य पर रेगेक्स नहीं, फ़ील्ड से खोजते हैं।
हर प्रोडक्शन सेवा पर यही सूची चाहिए: जेसन (या समकक्ष), सहसंबंध आईडी, ईमानदार स्तर, कच्चा व्यक्तिगत डेटा नहीं, और उच्च कार्डिनैलिटी फ़ील्ड पर सख्त नियम। सिद्धांत सूची नहीं। वे आदतें जो समझने का औसत समय घटाती हैं।
क्यों संरचना नारों से जीतती है
Error processing payment for user 42 जैसी पंक्ति टर्मिनल में पढ़ने लायक है। सुबह ३ बजे, पाँच माइक्रोसर्विस और अस्थिर निर्भरता के साथ चाहिए:
- सेवाओं के आर-पार एक अनुरोध से जुड़े सारे इवेंट
- कविता पर ग्रिप किए बिना
status_code,error_code,service,envपर फ़िल्टर - डैशबोर्ड और अलर्ट जो संदेश दोबारा लिखने पर न टूटें
संरचित लॉगिंग फ़ील्ड देती है। मानवीय संदेश रहता है, पर अब वही एकमात्र इंटरफ़ेस नहीं।
सामान्य आकार (फ़ील्ड नाम टीम पर निर्भर; एक परंपरा चुनें और रखें):
{
"ts": "2026-01-31T14:22:01.234Z",
"level": "error",
"msg": "payment capture failed",
"service": "billing-api",
"env": "prod",
"version": "1.8.3",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"request_id": "req_8f3c2a",
"user_id": "usr_9k2m",
"order_id": "ord_441",
"error_code": "GATEWAY_TIMEOUT",
"duration_ms": 3201,
"http": {
"method": "POST",
"path": "/v1/payments/capture",
"status": 504
}
}
सफलता और विफलता पर एक जैसी कुंजियाँ। एक जैसा नेस्टिंग अंदाज़। इसी से क्वेरी और अलर्ट नियम उबाऊ बनते हैं, और यही लक्ष्य है।
जेसन निकालें (या स्थिर कुंजी-मान प्रारूप)
एक लॉग पंक्ति = एक जेसन ऑब्जेक्ट स्टडआउट/स्टडरर पर। प्लेटफ़ॉर्म (फ्लूएंट बिट, वेक्टर, क्लाउडवॉच एजेंट, डाटाडॉग एजेंट आदि) भेजे और इंडेक्स करे। बिना कठिन कारण के कस्टम बहु-पंक्ति प्रारूप न बनाएँ।
व्यावहारिक नियम:
१. प्रति पंक्ति एक इवेंट। बहु-पंक्ति स्टैक ट्रेस या तो एक जेसन स्ट्रिंग फ़ील्ड (stack) में जाएँ, या शिपपर जोड़े। जेसन के बीच में टूटी पंक्तियाँ बेकार भार हैं।
२. यूटीसी में आईएसओ-८६०१ टाइमस्टैंप (ts या @timestamp)। लॉग में स्थानीय समय हर डेलाइट शिफ्ट पर एक घंटा खोने का तरीका है।
३. कोर फ़ील्ड के लिए स्थिर स्कीमा। level, msg, service, env, version, सहसंबंध आईडी। डोमेन फ़ील्ड उनके बगल में जोड़ें, हर स्प्रिंट नए यादृच्छिक टॉप-लेवल नाम नहीं।
४. लाइब्रेरी, print नहीं। structlog, zap, slog, pino, logback का जेसन एनकोडर, या स्टैक का मानक। प्रोसेस स्टार्ट पर एक बार कॉन्फ़िगर करें।
structlog शैली का न्यूनतम पायथन स्केच (समान विचार वाली कोई भी लाइब्रेरी चलेगी):
import logging
import structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
)
log = structlog.get_logger()
log.info("order_created", order_id=order.id, amount_cents=amount)
pino वाला नोड वही अनुबंध है: ऑब्जेक्ट अंदर, जेसन बाहर, अनुरोध संदर्भ के लिए चाइल्ड लॉगर।
मुख्य रिकॉर्ड के रूप में मुक्त स्ट्रिंग इंटरपोलेशन से बचें:
# Bad: only a string; hard to filter safely
logger.info(f"order {order_id} created for {email}")
# Better: message + fields
logger.info("order_created", order_id=order_id, user_id=user_id)
msg छोटा और स्थिर रखें (order_created, payment_capture_failed)। चर फ़ील्ड में डालें। इससे संदेश की कार्डिनैलिटी कम रहती है, और क्वेरी के आयाम मिलते हैं।
सहसंबंध आईडी: मेश में एक अनुरोध का पीछा
जब सेवा ए, बी को बुलाती है और बी, सी को, हर होस्ट पर सादे लॉग कहानी नहीं सुनाते। ऐसा आईडी चाहिए जो अनुरोध के साथ यात्रा करे।
दो आम पैटर्न (अक्सर दोनों):
| आईडी | भूमिका |
|---|---|
trace_id / span_id |
ओपनटेलिमेट्री / वितरित ट्रेसिंग। जब ट्रेस पहले से हों तो सबसे अच्छा। |
request_id / correlation_id |
ऐप स्तर यूयूआईडी किनारे पर (एपीआई गेटवे या पहली सेवा) सेट, हर हॉप पर आगे। |
व्यवहार में:
१. किनारा (या पहली सेवा) X-Request-Id / traceparent पढ़े। न हो तो यूयूआईडी बनाएँ।
२. उस आईडी को अनुरोध के लॉगिंग संदर्भ में डालें (कॉन्टेक्स्ट वार, एमडीसी, मिडलवेयर चाइल्ड लॉगर)।
३. उसी हेडर (और ओटेल हो तो डब्ल्यू३सी traceparent) को आउटबाउंड एचटीटीपी/जीआरपीसी/कतार संदेशों पर भेजें।
४. उस अनुरोध पथ की हर लॉग पंक्ति में आईडी अपने आप आएँ। कॉल साइट हर बार हाथ से न भेजें।
# Middleware pseudo-code
request_id = request.headers.get("x-request-id") or str(uuid4())
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
request_id=request_id,
trace_id=extract_trace_id(request),
)
# All logs in this request now carry request_id / trace_id
कतार वर्कर और क्रॉन को भी वही अनुशासन चाहिए। असिंक काम पर एन्क्यू करते समय request_id / trace_id पेलोड या हेडर पर रखें। क्रॉन में शुरुआत पर job_run_id बनाकर रन के लिए बाँधें।
इस तिमाही अगर एक ही काम करें: सिरे से सिरे तक सहसंबंध आईडी। बाकी अच्छा है; यही घटना प्रतिक्रिया बदलता है।
अर्थ रखने वाले लॉग स्तर
स्तर तभी मदद करते हैं जब टीम अर्थ पर सहमत हो। सेवाओं के लिए उपयोगी डिफ़ॉल्ट:
| स्तर | कब |
|---|---|
| एरर | अनुरोध या जॉब ऐसे फेल कि ध्यान चाहिए। ऑपरेटर या ऑन-कॉल कार्रवाई कर सकता है। |
| वार्न | गिरावट पर भी ठीक या ठीक करने योग्य: रिट्राई सफल, फ़ॉलबैक, सीमा के पास। |
| इन्फो | व्यवसाय के माइलस्टोन: अनुरोध पूरा, जॉब खत्म, कॉन्फ़िग लोड। |
| डीबग | लोकल या अस्थायी प्रोड निदान का विस्तार। प्रोड में डिफ़ॉल्ट बंद (या सैंपल)। |
अलर्ट थकान रोकने वाले नियम:
- अपेक्षित क्लाइंट गलतियों पर एरर न लिखें (वैलिडेशन ४००, खराब टोकन पर ऑथ ४०१)।
statusऔरerror_codeके साथ इन्फो या वार्न। एरर तब जब आपका सिस्टम फेल हो या अपस्ट्रीम अनुरोध तोड़े जब नहीं तोड़ना चाहिए। - प्रति फेल पथ एक एरर, हर रिट्राई पर एक और आखिर में एक नहीं। रिट्राई डीबग/वार्न हो सकते हैं; अंतिम विफलता एरर।
- इन्फो बाढ़ न बने। "शुरुआत", "बीच", "अंत" शोर की जगह प्रति अनुरोध एक समापन लॉग
duration_msऔरstatusके साथ। ऊँचे क्यूपीएस सेवाएँ अक्सर इन्फो सैंपल करती हैं या वॉल्यूम के लिए मेट्रिक, फेल और सफलता के नमूने के लिए लॉग। - प्रोड में डीबग केवल फ़्लैग, हेडर, या अल्पकालिक कॉन्फ़िग के पीछे। बातूनी पथ पर डीबग छोड़ना दोपहर में लॉग बजट जलाने का तरीका है।
लाइब्रेरी स्तर सावधानी से मैप करें। कुछ फ्रेमवर्क डिप्रिकेशन के लिए "वार्न" डिफ़ॉल्ट रखते हैं। डैशबोर्ड मानवीय अपेक्षा से मेल खाएँ, वैसे ट्यून करें।
व्यक्तिगत डेटा और रहस्य: कभी कच्चा मान नहीं
लॉग अनुरोध से ज़्यादा जीते हैं। तीसरे पक्ष सास, कोल्ड स्टोरेज, सपोर्ट टूल, लैपटॉप एक्सपोर्ट तक पहुँचते हैं। साबित होने तक इन्हें कमज़ोर पहुँच नियंत्रण वाला डेटा स्टोर समझें।
कभी न लॉग करें:
- पासवर्ड, एपीआई कुंजी, टोकन, सेशन कुकी, ऑथराइज़ेशन हेडर
- पूरे कार्ड नंबर, सीवीवी, बैंक खाता
- राष्ट्रीय पहचान, पासपोर्ट, जहाँ नियम लगे पूरे स्वास्थ्य रिकॉर्ड
- ऊपर वाला कुछ भी रख सकने वाले कच्चे अनुरोध/प्रतिक्रिया बॉडी
अक्सर छिपाएँ या टोकनाइज़ करें:
- ईमेल, फ़ोन, पूरा नाम (जॉइन कुंजी के रूप में
user_idबेहतर) - आईपी पता (नीति पर निर्भर; अक्सर हैश या काटा हुआ)
- सड़क पता, सटीक भू-स्थान
काम करने वाले पैटर्न:
# Prefer stable opaque IDs
log.info("login_ok", user_id=user.id)
# If you must include an email for support tooling, hash or partial-mask
log.info("invite_sent", email_domain=email.split("@")[-1]) # or hmac_sha256(email, pepper)
गहरा बचाव:
१. एचटीटीपी बॉडी और हेडर के लिए लॉगर सीमा पर फ़ील्ड अलाउलिस्ट। डिफ़ॉल्ट इनकार।
२. आम हेडर नामों (authorization, cookie, x-api-key) पर रेडैक्शन मिडलवेयर।
३. कोड रिव्यू चेकलिस्ट: payload, body, headers या उपयोगकर्ता प्रोफ़ाइल वाली नई लॉग पंक्तियों पर दूसरी नज़र।
४. रिटेंशन और पहुँच: डीबग-भारी इंडेक्स पर छोटी रिटेंशन; प्रोड लॉग खोज पर सीमित भूमिकाएँ।
लॉग में लीक भी लीक है। "हम लॉग सिर्फ ७ दिन रखते हैं" इंडेक्स होकर कॉपी हुए रहस्य को नहीं सुधारता।
कार्डिनैलिटी: चुपचाप बढ़ती लागत
कार्डिनैलिटी यानी फ़ील्ड कितने अनोखे मान ले सकता है। लॉग प्लेटफ़ॉर्म अक्सर वॉल्यूम पर बिल करते हैं और फ़ील्ड पर इंडेक्स/फेसेट बनाते हैं। उच्च कार्डिनैलिटी लागत उड़ाती है और क्वेरी प्रदर्शन मारती है।
| फ़ील्ड | कार्डिनैलिटी | नोट |
|---|---|---|
env, service, level, http.method |
कम | सुरक्षित फेसेट |
http.status, error_code, region |
कम-मध्यम | आमतौर पर ठीक |
user_id, order_id, request_id |
ऊँची | खोज के लिए रखें; मेट्रिक लेबल न बनाएँ |
इंटरपोलेटेड आईडी वाला msg |
चरम | payment failed for order 123 × लाखों |
| क्वेरी स्ट्रिंग सहित पूरा यूआरएल | चरम | पथ टेम्पलेट: /users/{id} |
नियम:
१. स्थिर msg मान। payment_capture_failed लिखें, payment capture failed for order {id} नहीं। आईडी order_id में।
२. कच्चा यूआरएल नहीं, पथ टेम्पलेट। मिडलवेयर /users/42 को चार्ट वाले फ़ील्ड में /users/:id (या फ्रेमवर्क रूट पैटर्न) बनाए। कच्चा पथ अलग फ़ील्ड में तभी जब लागत स्वीकार हो।
३. हर लॉग फ़ील्ड को मेट्रिक लेबल न बनाएँ। मेट्रिक को कम कार्डिनैलिटी आयाम चाहिए। लॉग user_id खोज के लिए रख सकते हैं; प्रोमेथियस लेबल नहीं।
४. असीमित स्ट्रिंग बाँधें। अपवाद संदेश और तीसरे पक्ष त्रुटि बॉडी काटें (जैसे २ केबी)। लॉग फ़ील्ड में २ एमबी एचटीएमएल त्रुटि पेज शिपपर के लिए स्वयं-घटाया आउटेज है।
५. गर्म पथ सैंपल करें। हर रेप्लिका से हर सेकंड हेल्थ चेक: ज़ोर से ड्रॉप या सैंपल। रीड-भारी एंडपॉइंट पर सफल जीईटी भी, अगर मेट्रिक ट्रैफिक पहले से ढकती हों।
कार्डिनैलिटी गलतियाँ "बेहतर लॉगिंग के बाद बिल दोगुना" के रूप में दिखती हैं। बेहतर लॉगिंग ज़्यादा अनोखी स्ट्रिंग नहीं। बेहतर फ़ील्ड हैं।
अनुरोध समापन लॉग में क्या रखें
अनुरोध के अंत पर एक ठोस इन्फो (या सैंपल पर डीबग) पाँच अधूरी पंक्तियों से बेहतर:
request_id/trace_idhttp.method, रूट टेम्पलेट,http.statusduration_ms- लागू हो तो
user_idया ऑथ सब्जेक्ट (अपारदर्शी आईडी) - स्टेटस >= ४०० पर
error_code - शायद
bytes_out,db_queries, या फ़ीचर फ़्लैग जब आम डीबग हुक हों
त्रुटियों पर दूसरी पंक्ति या वही एरर पर error.type, error.message (साफ़), वैकल्पिक stack।
आउटबाउंड कॉल: क्लाइंट नाम, लक्ष्य सेवा, स्टेटस, अवधि, सहसंबंध आईडी। तीन रेपो खोले बिना दिखता है किस निर्भरता ने विलंब बजट खाया।
संदर्भ बाइंडिंग पैरामीटर सूप से बेहतर
थ्रेड या अनुरोध-स्कोप्ड संदर्भ कॉल साइट साफ़ रखता है:
request start -> bind request_id, user_id, route
service code -> log.info("inventory_reserved", sku=sku, qty=qty)
...
request end -> log completion; clear context
बिना बाइंडिंग हर हेल्पर सहसंबंध फ़ील्ड छोड़ता है या हमेशा log_ctx तर्क लेता है। अनुरोध अंत पर संदर्भ साफ़ करें ताकि वर्कर थ्रेड या असिंक टास्क अगले जॉब में आईडी न लीक करें।
लॉग को मेट्रिक और ट्रेस से जोड़ें
लॉग जवाब देते हैं "इस आईडी पर क्या हुआ।" मेट्रिक "कितनी बार / कितना धीमा।" ट्रेस "सेवाओं के आर-पार समय कहाँ गया।"
- दर, विलंब, संतृप्ति (आरईडी/यूएसई) के लिए मेट्रिक। केवल बाद में गिनने के लिए लॉग पंक्ति न गढ़ें।
- बहु-हॉप विलंब के लिए ट्रेस। लॉग में
trace_idरहे ताकि लॉग हिट से ट्रेस यूआई पर जाएँ। - दुर्लभ घटना, त्रुटि विस्तार, और ऑडिट जैसे तथ्य जिन पर पेलोड संदर्भ चाहिए (फिर भी छिपा) के लिए लॉग।
अगर सिग्नल सिर्फ लॉग हैं, हर सवाल फुल-टेक्स्ट खोज बन जाता है। जब तक चलता है चलता है।
छोटी प्रोडक्शन चेकलिस्ट
कोड रिव्यू या नई सेवा टेम्पलेट में:
१. स्टडआउट पर जेसन (या समकक्ष), प्रति पंक्ति एक इवेंट, यूटीसी टाइमस्टैंप।
२. हर पंक्ति पर कोर फ़ील्ड: service, env, version, level, msg।
३. सहसंबंध: हर हॉप पर request_id और/या डब्ल्यू३सी / ओटेल ट्रेस संदर्भ, कतार सहित।
४. स्तर: कार्रवाई योग्य सिस्टम फेल पर एरर; नियमित ४००-श्रेणी पर नहीं।
५. रहस्य या कच्चा व्यक्तिगत डेटा नहीं; अपारदर्शी आईडी; डिफ़ॉल्ट हेडर/बॉडी रेडैक्ट।
६. कम कार्डिनैलिटी संदेश और रूट; ऊँची कार्डिनैलिटी केवल खोज फ़ील्ड, मेट्रिक लेबल नहीं।
७. स्टेटस और अवधि वाला समापन लॉग; वॉल्यूम दुखे तो गर्म पथ पर सफलता सैंपल।
८. लाइब्रेरी एक बार कॉन्फ़िग; नए कोड में अस्थायी print या केवल-स्ट्रिंग लॉगिंग नहीं।
९. रिटेंशन और पहुँच दस्तावेज़ी; डीबग इंडेक्स मुफ़्त डेटा लेक नहीं।
आम विफलता मोड
| लक्षण | संभावित कारण |
|---|---|
| उपयोगकर्ता शिकायत सेवाओं के आर-पार नहीं जुड़ती | गायब या गिरा सहसंबंध हेडर |
| "छोटे" बदलाव के बाद लॉग बिल उछलता है | डीबग खुला, हेल्थ चेक लॉग, या इंटरपोलेटेड msg |
| उपयोगकर्ता टाइपो पर अलर्ट | ४००-श्रेणी को एरर लिखा |
| सुरक्षा समीक्षा में ईएलके में टोकन | बिना अलाउलिस्ट बॉडी/हेडर लॉगिंग |
user_id फेसेट पर क्वेरी टाइमआउट |
डिफ़ॉल्ट इंडेक्स/फेसेट के रूप में उच्च कार्डिनैलिटी |
| स्टैक ट्रेस पंक्तियों में बँट जेसन तोड़ते हैं | बिना एक-फ़ील्ड स्टैक बहु-पंक्ति आउटपुट |
इनमें नया विक्रेता नहीं चाहिए। परंपरा और कुछ पंक्ति मिडलवेयर चाहिए।
समापन
संरचित लॉगिंग प्रारूप बहस नहीं। यह संचालन अनुबंध है: हर सेवा एक ही फ़ील्ड भाषा बोलती है, सीमाओं के आर-पार आईडी ले जाती है, रहस्य नहीं डालती, और कार्डिनैलिटी काबू में रखती है ताकि खोज तेज़ और बिल उबाऊ रहे।
जेसन + सहसंबंध आईडी + स्तर नीति + रेडैक्शन से शुरू करें। जब बुनियाद लोड सहे, तब बढ़िया सैंपलिंग और स्कीमा रजिस्ट्री जोड़ें। अगली घटना में एक request_id क्वेरी चाहिए, पाँच ग्रिप और उम्मीद नहीं।
