FastAPI बैकएंड डेवलपमेंट बूटकैंप · पाठ

संरचित JSON लॉगिंग और सहसंबंध ID

अनुरोध-स्कोप वाले सहसंबंध ID के साथ संरचित लॉग उत्पन्न कीजिए, जो अतुल्यकालिक सीमाओं और सेवाओं के पार बने रहें।

पाठ 1, कुल 4 में से13 चरण

संरचित JSON लॉगिंग और सहसंबंध ID, CoddyKit पर FastAPI बैकएंड डेवलपमेंट बूटकैंप का एक निःशुल्क पाठ है। यह 4 में से 1वाँ पाठ है। इस अध्ययन पथ के 3 तक कोई भी पाठ पूरा पढ़ना निःशुल्क है — इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ व्यावहारिक अभ्यास भी उपलब्ध कराता है। यह FastAPI बैकएंड डेवलपमेंट बूटकैंप सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

संरचित लॉग क्यों

उत्पादन परिवेश में लॉग डेटा होते हैं, गद्य नहीं। User 42 failed login from 10.0.0.3 जैसी पंक्ति मनुष्य को तो ठीक लगती है, लेकिन मशीनों के लिए असुविधाजनक है: आप उस पर विश्वसनीय रूप से फ़िल्टर, समुच्चय या चेतावनी नहीं चला सकते।

संरचित लॉगिंग हर घटना को स्थिर, क्वेरी किए जा सकने वाले फ़ील्ड वाले JSON ऑब्जेक्ट के रूप में जारी करती है:

  • timestamp, level, message
  • request_id / correlation_id
  • user_id, path, status_code, duration_ms जैसे संदर्भ

इसके बाद लॉग एकत्रक (Loki, Elasticsearch, Datadog) उन फ़ील्डों को अनुक्रमित करते हैं, जिससे आप level=ERROR AND path=/checkout जैसी क्वेरी चला सकते हैं।

एक पंक्ति में JSON लॉग

सबसे सरल संरचित लॉग एक डिक्शनरी है, जिसे एक ही पंक्ति में JSON के रूप में क्रमबद्ध किया गया हो। प्रति पंक्ति एक JSON ऑब्जेक्ट वाला प्रारूप JSON पंक्तियाँ (NDJSON) है, जिसे लगभग हर लॉग भेजने वाला उपकरण समझता है।

यह स्वतंत्र उदाहरण उस संरचना को दिखाता है, जिसे हम बनाना चाहते हैं। ध्यान दें कि फ़ील्ड सपाट हैं और उनके नामों में सुसंगतता है।

import json
import time

def log(level, message, **fields):
    record = {
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "level": level,
        "message": message,
        **fields,
    }
    print(json.dumps(record))

log("INFO", "request completed", path="/checkout", status_code=200, duration_ms=42)
log("ERROR", "db timeout", path="/orders", correlation_id="abc-123")

एक कस्टम JSON फ़ॉर्मैटर

अपना print(json.dumps(...)) लिखने से Python का logging मॉड्यूल दरकिनार हो जाता है और स्तर, हैंडलर तथा लाइब्रेरी लॉग खो जाते हैं। इसके बजाय, मानक logging स्टैक में एक JSON फ़ॉर्मैटर जोड़ें।

फ़ॉर्मैटर का काम LogRecord को स्ट्रिंग में बदलना है। यहाँ हम JSON लौटाते हैं। record.__dict__ में कॉल वाली जगह पर भेजे गए extra={...} के सभी फ़ील्ड होते हैं।

import json
import logging

class JsonFormatter(logging.Formatter):
    def format(self, record):
        payload = {
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        if record.exc_info:
            payload["exc"] = self.formatException(record.exc_info)
        return json.dumps(payload)

handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[handler])

logging.getLogger("app").info("service started", extra={"port": 8000})

Correlation ID की समस्या

एकल उपयोगकर्ता अनुरोध अक्सर कई जगहों तक फैलता है: API हैंडलर → सेवा परत → डेटाबेस कॉल → किसी अन्य सेवा को आउटबाउंड HTTP कॉल। यदि हर लॉग पंक्ति अनाम हो, तो आप उन्हें जोड़कर एक पूरी कहानी नहीं बना सकते।

Correlation ID (जिसे अनुरोध ID या ट्रेस ID भी कहते हैं) एक अद्वितीय मान है, जो आने वाले प्रत्येक अनुरोध के लिए एक बार बनाया जाता है और उसे संभालते समय उत्पन्न हर लॉग पंक्ति से जोड़ दिया जाता है। इसके बाद correlation_id=abc-123 फ़ंक्शनों और यहाँ तक कि सेवाओं के बीच की पूरी समय-रेखा खोज लाता है।

चुनौती यह है: हर फ़ंक्शन के तर्क में इसे भेजे बिना कॉल स्टैक की गहराई में इस ID को उपलब्ध कैसे कराया जाए?

ContextVar: अनुरोध-सीमित स्थिति

इसका साफ़ समाधान contextvars.ContextVar है। वैश्विक चर के विपरीत, ContextVar ऐसा मान रखता है जो हर तार्किक निष्पादन संदर्भ में अलग होता है और, सबसे महत्वपूर्ण रूप से, async प्रतीक्षाओं के दौरान सही ढंग से आगे पहुँचता है।

हर समवर्ती अनुरोध अपने संदर्भ में चलता है, इसलिए एक अनुरोध में सेट किया गया correlation ID दूसरे अनुरोध में कभी नहीं पहुँचता, भले ही एक ही इवेंट लूप पर कई अनुरोधों का निष्पादन आपस में जुड़ा हुआ हो।

import asyncio
from contextvars import ContextVar

correlation_id: ContextVar[str] = ContextVar("correlation_id", default="-")

async def handle(name, cid):
    correlation_id.set(cid)
    await asyncio.sleep(0.01)
    # value survives the await and stays isolated per task
    print(name, "->", correlation_id.get())

async def main():
    await asyncio.gather(
        handle("req-A", "aaa"),
        handle("req-B", "bbb"),
    )

asyncio.run(main())

लॉग फ़िल्टर के माध्यम से ID डालना

हर लॉग पंक्ति में correlation ID अपने-आप जोड़ने के लिए, एक logging.Filter लगाएँ, जो ContextVar से ID पढ़कर उसे रिकॉर्ड पर कॉपी करे। फ़िल्टर हैंडलर से गुजरने वाले हर रिकॉर्ड पर चलता है, इसलिए किसी भी कॉल वाली जगह को ID भेजना याद रखने की आवश्यकता नहीं होती।

इसके बाद फ़ॉर्मैटर record.correlation_id को किसी अन्य फ़ील्ड की तरह पढ़ता है।

import json
import logging
from contextvars import ContextVar

correlation_id: ContextVar[str] = ContextVar("correlation_id", default="-")

class CorrelationFilter(logging.Filter):
    def filter(self, record):
        record.correlation_id = correlation_id.get()
        return True

class JsonFormatter(logging.Formatter):
    def format(self, record):
        return json.dumps({
            "level": record.levelname,
            "message": record.getMessage(),
            "correlation_id": getattr(record, "correlation_id", "-"),
        })

h = logging.StreamHandler()
h.addFilter(CorrelationFilter())
h.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[h])

correlation_id.set("abc-123")
logging.getLogger("app").info("order placed")

ID सेट करने के लिए FastAPI Middleware

FastAPI में correlation ID स्थापित करने का सही स्थान एक HTTP मिडलवेयर है, जो हर अनुरोध को घेरे रहता है। इसकी पद्धति:

  • यदि कॉलर, जैसे गेटवे या upstream सेवा, पहले से ID सेट कर चुका हो, तो आने वाले X-Request-ID / X-Correlation-ID हेडर को पढ़ें।
  • अन्यथा एक नया UUID बनाएँ।
  • उसे ContextVar में रखें, ताकि नीचे की सभी लॉग प्रविष्टियाँ उसे प्राप्त कर सकें।
  • उसे प्रतिक्रिया हेडर में वापस भेजें, ताकि क्लाइंट बग रिपोर्ट में उसका उल्लेख कर सकें।

यह ऐसा फ़्रेमवर्क कोड है, जिसके लिए चलता हुआ सर्वर आवश्यक है; इसलिए यह चलाने योग्य होने के बजाय समझाने के लिए दिया गया है।

import uuid
from fastapi import FastAPI, Request
from contextvars import ContextVar

correlation_id: ContextVar[str] = ContextVar("correlation_id", default="-")
app = FastAPI()

@app.middleware("http")
async def correlation_middleware(request: Request, call_next):
    cid = request.headers.get("X-Request-ID") or str(uuid.uuid4())
    token = correlation_id.set(cid)
    try:
        response = await call_next(request)
    finally:
        correlation_id.reset(token)
    response.headers["X-Request-ID"] = cid
    return response

reset() के साथ Token क्यों महत्वपूर्ण है

token = correlation_id.set(cid) के बाद finally ब्लॉक में correlation_id.reset(token) पर ध्यान दें। अनुरोध समाप्त होने पर यह टोकन पिछली वैल्यू को पुनर्स्थापित करता है।

ASGI सर्वर के अंतर्गत वर्कर टास्क और कॉन्टेक्स्ट का फिर से उपयोग किया जा सकता है। रीसेट करने से समाप्त हो चुके अनुरोध की बची हुई ID बाद के ऐसे अनुरोध में अनजाने में नहीं पहुँचती, जिसने अपनी ID सेट नहीं की हो। मिडलवेयर में हमेशा set() के साथ reset() का उपयोग करें और इसे finally में रखें, ताकि हैंडलर द्वारा अपवाद उठाए जाने पर भी यह चले।

from contextvars import ContextVar

cv: ContextVar[str] = ContextVar("cv", default="-")

print(cv.get())          # -
token = cv.set("req-1")
print(cv.get())          # req-1
cv.reset(token)
print(cv.get())          # back to -

बैकग्राउंड टास्क और थ्रेड में कॉन्टेक्स्ट बनाए रखना

ContextVar एक ही टास्क के भीतर await के दौरान अपने-आप आगे पहुँच जाता है, लेकिन किसी दूसरे थ्रेड में भेजे गए कार्य के साथ वैल्यू अपने-आप नहीं जाती (जैसे run_in_executor या अवरोधक DB ड्राइवर के मामले में)।

थ्रेड की सीमा पार कॉन्टेक्स्ट ले जाने के लिए contextvars.copy_context() से उसे कैप्चर करें और उसी कॉपी के भीतर कॉल करने योग्य कार्य चलाएँ। asyncio पहले ही create_task के लिए ऐसा करता है; कच्चे एक्जीक्यूटर के लिए आपको यह स्वयं करना होगा।

import contextvars
from concurrent.futures import ThreadPoolExecutor

cid = contextvars.ContextVar("cid", default="-")

def work():
    return cid.get()

cid.set("trace-9")
ctx = contextvars.copy_context()
with ThreadPoolExecutor() as pool:
    # ctx.run carries the ContextVar value into the worker thread
    result = pool.submit(ctx.run, work).result()

print("in thread:", result)  # trace-9

सेवाओं के बीच कॉन्टेक्स्ट पहुँचाना

कोरिलेशन ID तभी शुरू से अंत तक उपयोगी होती है, जब वह सेवा की सीमाओं को पार करे। जब आपकी FastAPI सेवा किसी दूसरी सेवा को कॉल करे, तो ID को HTTP हेडर के रूप में आगे भेजें, ताकि बाद की सेवा के लॉग में भी वही वैल्यू रहे।

इसे ContextVar से पढ़कर हर बाहर जाने वाले क्लाइंट कॉल में जोड़ें। प्राप्त करने वाली सेवा का मिडलवेयर नया ID बनाने के बजाय उस हेडर को पढ़ता है, इसलिए पूरी कॉल शृंखला में एक ही ID बनी रहती है।

import httpx
from contextvars import ContextVar

correlation_id: ContextVar[str] = ContextVar("correlation_id", default="-")

async def call_downstream(url: str):
    headers = {"X-Request-ID": correlation_id.get()}
    async with httpx.AsyncClient() as client:
        resp = await client.get(url, headers=headers)
        return resp.json()

structlog के साथ सब कुछ जोड़ना

फ़ॉर्मैटर को हाथ से बनाने के बजाय, कई टीमें स्ट्रक्टलॉग का उपयोग करती हैं, जो प्रोसेसरों की एक पाइपलाइन को संयोजित करता है और अंत में JSON तैयार करता है। कोई प्रोसेसर ContextVar से कोरिलेशन ID लेकर उसे हर घटना में अपने-आप मिला सकता है।

इसके लाभ एक-दूसरे को बढ़ाते हैं: एकसमान JSON आउटपुट, logger.bind(...) के ज़रिए हर घटना के लिए आसानी से कॉन्टेक्स्ट जोड़ना, और मानक लाइब्रेरी के logging मॉड्यूल के साथ साफ़ एकीकरण, जिससे लाइब्रेरी के लॉग भी कैप्चर हो जाते हैं।

import structlog
from contextvars import ContextVar

correlation_id: ContextVar[str] = ContextVar("correlation_id", default="-")

def add_correlation_id(logger, method_name, event_dict):
    event_dict["correlation_id"] = correlation_id.get()
    return event_dict

structlog.configure(
    processors=[
        add_correlation_id,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer(),
    ]
)

correlation_id.set("abc-123")
log = structlog.get_logger()
log.info("checkout_completed", amount=49.9, currency="EUR")

त्वरित जाँच

असिंक्रोनस FastAPI सेवाओं में कोरिलेशन ID के प्रसार की अपनी समझ जाँचें।

पुनरावलोकन

आपने FastAPI के लिए अनुरोध-स्कोप वाला, संरचित लॉगिंग बनाया:

  • संरचित JSON लॉग के लिए कस्टम logging.Formatter (या स्ट्रक्टलॉग) का उपयोग किया गया, जिससे लॉग में खोज करना आसान होता है।
  • कोरिलेशन ID एक अनुरोध की हर लॉग पंक्ति को फ़ंक्शन और सेवाओं के बीच आपस में जोड़ती हैं।
  • contextvars.ContextVar प्रति-अनुरोध अलगाव के साथ ID रखता है और await की सीमाओं के पार भी बना रहता है।
  • लॉगिंग फ़िल्टर हर रिकॉर्ड में ID जोड़ता है, इसलिए किसी भी कॉल स्थान को इसे याद रखकर जोड़ने की आवश्यकता नहीं होती।
  • FastAPI मिडलवेयर X-Request-ID पढ़ता है या UUID बनाता है, फिर finally में set() को reset(token) के साथ जोड़ा जाता है।
  • थ्रेड में कॉन्टेक्स्ट copy_context() के ज़रिए और सेवाओं के बीच ID हेडर आगे भेजकर पहुँचाएँ।

परिणाम यह है: आपके लॉग एग्रीगेटर में किसी एक ID को खोजने पर किसी भी अनुरोध की पूरी यात्रा दिखाई देती है।

शुरुआत निःशुल्क

एआई शिक्षक के साथ FastAPI बैकएंड डेवलपमेंट बूटकैंप सीखें — निःशुल्क

अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।

पाठ्यक्रम
21
पाठ
84

अक्सर पूछे जाने वाले प्रश्न

क्या “संरचित JSON लॉगिंग और सहसंबंध ID” पाठ निःशुल्क है?

हाँ — FastAPI बैकएंड डेवलपमेंट बूटकैंप अध्ययन पथ के 3 तक कोई भी पाठ, जिसमें “संरचित JSON लॉगिंग और सहसंबंध ID” भी शामिल है, यहाँ वेब पर पूरा पढ़ना निःशुल्क है। इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ इंटरैक्टिव अभ्यास भी उपलब्ध कराता है। FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

“संरचित JSON लॉगिंग और सहसंबंध ID” में मैं क्या सीखूँगा?

अनुरोध-स्कोप वाले सहसंबंध ID के साथ संरचित लॉग उत्पन्न कीजिए, जो अतुल्यकालिक सीमाओं और सेवाओं के पार बने रहें। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ FastAPI बैकएंड डेवलपमेंट बूटकैंप का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

क्या FastAPI बैकएंड डेवलपमेंट बूटकैंप शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?

पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर FastAPI बैकएंड डेवलपमेंट बूटकैंप शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 1वाँ पाठ है।

“संरचित JSON लॉगिंग और सहसंबंध ID” पाठ पूरा करने में कितना समय लगता है?

CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।

क्या मैं इस FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ में कोड लिख और चला सकता हूँ?

हाँ। हर FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।

इस पाठ्यक्रम के सभी पाठ

  1. संरचित JSON लॉगिंग और सहसंबंध ID
  2. OpenTelemetry के साथ वितरित ट्रेसिंग
  3. Prometheus मेट्रिक्स और RED/USE डैशबोर्ड
  4. SLO और त्रुटि बजट पर चेतावनी
← FastAPI बैकएंड डेवलपमेंट बूटकैंप पर वापस जाएँ