Intermediate Python 📂 Modules · 6 of 6 215 min read

Python Logging Mastery

Master Python's built-in logging module — the professional replacement for print(). Learn levels, handlers, formatters, the logger hierarchy, log rotation, structured JSON logs, and real-world Flask/production patterns. Every concept comes with runnable examples, diagrams, and battle-tested rules so you never lose another bug to a missing print statement.

🌐
SectionSection第 01 节

The Story That Explains Why Logging Exists ✈️Logging क्यों है — समझाने वाली कहानी ✈️一个讲清日志为何存在的故事 ✈️

The Black Box vs. a Sticky NoteBlack Box बनाम एक चिपकने वाली पर्ची黑匣子与便利贴
Imagine two pilots. Pilot A scribbles quick notes on sticky notes and throws them out of the window. Pilot B has a flight recorder — a black box that quietly records altitude, engine speed, warnings and errors, each with an exact time.

When something goes wrong at 30,000 feet, Pilot A has nothing. Pilot B replays the last minutes and sees exactly which valve failed, and when.

print() is the sticky note. Python's logging module is the black box: every message has a level, a time and a source, and you decide — in one place — which messages are kept and where they go.
दो pilots की कल्पना करें। Pilot A चिपकने वाली पर्चियों पर जल्दी-जल्दी notes लिखता है और खिड़की से बाहर फेंक देता है। Pilot B के पास flight recorder है — एक black box जो चुपचाप ऊँचाई, engine की रफ़्तार, चेतावनियाँ और errors रिकॉर्ड करता है, हर एक सटीक समय के साथ।

जब 30,000 feet पर कुछ गड़बड़ होती है, तो Pilot A के पास कुछ नहीं होता। Pilot B आख़िरी मिनट दोबारा चलाता है और ठीक-ठीक देखता है कि कौन-सा valve कब ख़राब हुआ।

print() वह पर्ची है। Python का logging module black box है: हर message का एक level, एक समय और एक स्रोत होता है, और आप — एक ही जगह — तय करते हैं कि कौन-से messages रखे जाएँ और वे कहाँ जाएँ।
想象两位飞行员。飞行员 A 把随手记的东西写在便利贴上,然后扔出窗外。飞行员 B 有一台 飞行记录仪 — 一个黑匣子,默默记录高度、发动机转速、警告和错误,每条都带精确时间。

当 30,000 英尺高空出问题时,飞行员 A 什么也没有。飞行员 B 回放最后几分钟,准确看到哪个阀门在何时失效。

print() 就是便利贴。Python 的 logging 模块就是黑匣子:每条消息都有 级别、时间 和 来源,你可以在一个地方决定保留哪些消息、送到哪里。
📡
The Core Insightमूल बात核心要点

Logging is not "printing with extra steps". It is a separate channel: your code only says what happened and how serious it is. Configuration — written once, at start-up — decides what is kept and where it goes: the screen, a file, a log server. Change the configuration, and not one line of your business code changes.

Logging "थोड़ी ज़्यादा मेहनत वाला print" नहीं है। यह एक अलग channel है: आपका code सिर्फ़ बताता है कि क्या हुआ और वह कितना गंभीर है। Configuration — start-up पर एक बार लिखी गई — तय करती है कि क्या रखा जाए और कहाँ जाए: screen, file, log server। Configuration बदलें, और आपके business code की एक भी line नहीं बदलती।

日志不是"多了几步的 print"。它是一条独立的通道:你的代码只说明 发生了什么、有多严重。配置 — 在启动时写一次 — 决定保留什么、送到哪里:屏幕、文件、日志服务器。修改配置,业务代码一行都不用动。

▶️
This Tutorial Has Four Simulators and a Quizइस Tutorial में चार Simulators और एक Quiz है本教程有四个模拟器和一个小测验

Look for the interactive boxes: the Volume Knob (Section 03), the Log Router (06) — checked against the real logging module on 3,840 cases — the Formatter Builder (07) and Rotating Files (08).

Interactive डिब्बे खोजें: Volume Knob (Section 03), Log Router (06) — असली logging module से 3,840 cases पर जाँचा गया — Formatter Builder (07) और Rotating Files (08)।

请留意这些互动框:音量旋钮(第 03 节)、日志路由器(06)— 在 3,840 个用例上与真实 logging 模块核对过 — 格式化器构建器(07)和 轮转文件(08)。


SectionSection第 02 节

Why print() Fails in Real Apps 🧻असली Apps में print() क्यों नाकाम है 🧻为什么 print() 在真实应用中不够用 🧻

❌ print()
Problemसमस्या问题Resultनतीजा后果
no levelकोई level नहीं没有级别debug noise mixes with real errorsdebug शोर असली errors में मिल जाता है调试噪音与真正的错误混在一起
no time, no sourceन समय, न स्रोत没有时间、没有来源you cannot tell when or whereपता नहीं चलता कब और कहाँ无法知道何时、何处
one destinationएक ही जगह只有一个去处cannot send errors to a fileerrors को file में नहीं भेज सकते无法把错误单独写入文件
no off switchबंद करने का switch नहीं没有开关delete them all before releaserelease से पहले सब हटाने पड़ते हैं发布前只能全部删掉
✅ logging
Featureसुविधा特性Benefitफ़ायदा好处
5 levels5 levels5 个级别turn noise down with one lineएक line से शोर कम करें一行配置降低噪音
automatic time, file, lineअपने आप समय, file, line自动记录时间、文件、行号every line tells its storyहर line अपनी कहानी बताती है每行都自带来龙去脉
many handlersकई handlers多个处理器screen + file + server at oncescreen + file + server एक साथ屏幕 + 文件 + 服务器同时输出
thread-safethread-safe线程安全lines from threads do not mixthreads की lines आपस में नहीं मिलतीं多线程的日志行不会交错
import logging

print("print: User 42 logged in")              # no level, no time, no source

logging.info("This INFO line is hidden")         # default level is WARNING
logging.warning("Disk usage at 82%")             # shown: WARNING and above
logging.error("Failed to send email")
OUTPUT
print: User 42 logged in WARNING:root:Disk usage at 82% ERROR:root:Failed to send email

Notice two things in the real output. The INFO line is missing — the default level is WARNING. And log lines go to stderr, not stdout, so you can send your program's normal output and its logs to different places.

असली output में दो बातें देखें। INFO line ग़ायब है — default level WARNING है। और log lines stdout पर नहीं, stderr पर जाती हैं, इसलिए आप program का सामान्य output और logs अलग-अलग जगह भेज सकते हैं।

注意真实输出中的两点。INFO 那一行不见了 — 默认级别是 WARNING。而且日志行输出到 stderr 而不是 stdout,所以你可以把程序的正常输出和日志送到不同的地方。


SectionSection第 03 节

The Five Log Levels 🎚️पाँच Log Levels 🎚️五个日志级别 🎚️

Every message carries a level, and every level is a number. A logger drops any message whose number is below its threshold.

हर message का एक level होता है, और हर level एक नंबर है। Logger हर उस message को गिरा देता है जिसका नंबर उसकी सीमा से नीचे है।

每条消息都带有一个级别,每个级别都是一个数字。记录器会丢弃数字 低于 阈值的所有消息。

Levels Are Numbers — the Threshold Cuts the LadderLevels नंबर हैं — सीमा सीढ़ी को काटती है级别就是数字 — 阈值切断阶梯
levels threshold🔬DEBUG 10step-by-step detail: values, SQL, cache hitsक़दम-दर-क़दम विवरण: values, SQL, cache hits逐步细节:变量值、SQL、缓存命中ℹ️INFO 20normal events: started, user logged in, job doneसामान्य घटनाएँ: शुरू हुआ, user login हुआ, काम पूरा正常事件:启动、用户登录、任务完成⚠️WARNING 30unexpected but still working: retry, slow, disk 85%अनपेक्षित पर अब भी चल रहा: retry, धीमा, disk 85%意外但仍在工作:重试、变慢、磁盘 85%❌ERROR 40one operation failed: a payment, a writeएक काम fail हुआ: payment, write某个操作失败:一次支付、一次写入🔥CRITICAL 50the app itself is going downapp ख़ुद बंद हो रहा है应用本身要挂了level=WARNING: everything below this line is droppedlevel=WARNING: इस line के नीचे का सब गिरा दिया जाता हैlevel=WARNING:这条线以下的全部丢弃

With level=WARNING (the default), DEBUG and INFO are dropped.

level=WARNING (default) के साथ DEBUG और INFO गिरा दिए जाते हैं।

在 level=WARNING(默认值)下,DEBUG 和 INFO 会被丢弃。

import logging

logging.basicConfig(level=logging.DEBUG)       # show everything from DEBUG up

logging.debug("Loop iteration i=5, value=42")
logging.info("Server started on port 8000")
logging.warning("Disk usage at 82%")
logging.error("Failed to send email to user@example.com")
logging.critical("Database connection pool exhausted!")

print(logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
OUTPUT
DEBUG:root:Loop iteration i=5, value=42 INFO:root:Server started on port 8000 WARNING:root:Disk usage at 82% ERROR:root:Failed to send email to user@example.com CRITICAL:root:Database connection pool exhausted! 10 20 30 40 50

Try It — The Volume Knobकरके देखें — Volume Knob动手试试 — 音量旋钮

Here is a real log from a small shop app, captured at DEBUG. Turn the knob and watch which lines survive at each level.

यह एक छोटे shop app का असली log है, DEBUG पर रिकॉर्ड किया गया। Knob घुमाएँ और देखें हर level पर कौन-सी lines बचती हैं।

这是一个小商店应用的真实日志,在 DEBUG 级别下采集。转动旋钮,看看每个级别下哪些行会留下来。

🎚️ Volume KnobOne line of config, very different logs.Config की एक line, बहुत अलग logs।一行配置,日志大不相同。
DEBUGCRITICAL
💡
A Level Rule of ThumbLevel का एक आसान नियम级别的经验法则

Your PC → DEBUG. Test server → INFO. Production → INFO or WARNING. Read the level from configuration (an environment variable or a config file), so changing it never needs a code change.

आपका PC → DEBUG। Test server → INFO। Production → INFO या WARNING। Level configuration (environment variable या config file) से पढ़ें, ताकि उसे बदलने के लिए कभी code न बदलना पड़े।

你的电脑 → DEBUG。测试服务器 → INFO。生产环境 → INFO 或 WARNING。从配置(环境变量或配置文件)读取级别,这样修改它永远不用改代码。


SectionSection第 04 节

The Anatomy of the Logging System 🔧Logging System की बनावट 🔧日志系统的构造 🔧

🔧 The Four Partsचार हिस्से四个组成部分
Logger
What you call. log.info(...). Loggers form a tree by dotted name: myapp.db is a child of myapp. जिसे आप बुलाते हैं। log.info(...)। Loggers बिंदु वाले नाम से tree बनाते हैं: myapp.db, myapp का child है। 你调用的对象。log.info(...)。记录器按点号名称组成树:myapp.db 是 myapp 的子节点。
Handler
Where it goes. Screen, file, rotating file, email, a log server. One logger can have many handlers, each with its own level. कहाँ जाता है। Screen, file, rotating file, email, log server। एक logger के कई handlers हो सकते हैं, हर एक का अपना level। 送到哪里。屏幕、文件、轮转文件、邮件、日志服务器。一个记录器可以有多个处理器,各有自己的级别。
Formatter
What it looks like. Turns a LogRecord into text: time, level, name, message. कैसा दिखता है। LogRecord को text में बदलता है: समय, level, नाम, message। 长什么样。把 LogRecord 变成文本:时间、级别、名称、消息。
Filter
Optional gatekeeper. Can drop or change a record — for example to hide passwords. वैकल्पिक द्वारपाल। Record को गिरा या बदल सकता है — जैसे passwords छिपाने के लिए। 可选的守门人。可以丢弃或修改记录 — 例如隐藏密码。
From log.info() to a Line in a Filelog.info() से File की एक Line तक从 log.info() 到文件里的一行
logging flowlog.info(...)your codeआपका code你的代码level checklogger's effective levellogger का effective level记录器有效级别LogRecordtime, name, line, thread…समय, नाम, line, thread…时间、名称、行号、线程…handlers ↑this logger, then parentsयह logger, फिर parents本记录器,再到父级Formatterrecord → textrecord → text记录 → 文本📄 🖥️ ☁️file · screen · serverfile · screen · server文件 · 屏幕 · 服务器too low → dropped, almost for freeबहुत नीचा → गिरा दिया, लगभग मुफ़्त太低 → 直接丢弃,几乎零开销

The logger checks its level once. After that, only handler levels and filters decide, at every logger on the way up.

Logger अपना level एक बार जाँचता है। उसके बाद ऊपर जाते हुए हर logger पर सिर्फ़ handler levels और filters फ़ैसला करते हैं।

记录器只检查一次自己的级别。之后,在向上经过的每个记录器上,只由 处理器 的级别和过滤器决定。


SectionSection第 05 节

Named Loggers — Use getLogger(__name__) 🌳नाम वाले Loggers — getLogger(__name__) इस्तेमाल करें 🌳命名记录器 — 使用 getLogger(__name__) 🌳

Calling logging.info(...) uses the root logger. In real code, every module creates its own logger with logging.getLogger(__name__). __name__ is the module's dotted path, so the loggers form a tree that matches your packages — and you can turn each branch up or down separately.

logging.info(...) बुलाने से root logger इस्तेमाल होता है। असली code में हर module logging.getLogger(__name__) से अपना logger बनाता है। __name__ module का बिंदु वाला path है, इसलिए loggers आपके packages जैसा ही tree बनाते हैं — और आप हर शाखा को अलग से ऊपर-नीचे कर सकते हैं।

调用 logging.info(...) 使用的是 根 记录器。真实代码中,每个模块都用 logging.getLogger(__name__) 创建自己的记录器。__name__ 是模块的点号路径,所以记录器会组成一棵与包结构一致的树 — 你可以分别调高或调低每个分支。

# myapp/db.py
import logging

log = logging.getLogger(__name__)      # becomes "myapp.db"

def query(sql):
    log.debug("Executing SQL: %s", sql)
    log.info("Query finished in %d ms", 42)
# myapp/api.py
import logging
from myapp import db

log = logging.getLogger(__name__)      # becomes "myapp.api"

def get_user(user_id):
    log.info("GET /users/%s", user_id)
    db.query(f"SELECT * FROM users WHERE id = {user_id}")
import logging
from myapp import api

logging.basicConfig(level=logging.DEBUG, format="%(levelname)-7s %(name)-10s %(message)s")
api.get_user(42)

# Turn ONE module down - no code change inside myapp
logging.getLogger("myapp.db").setLevel(logging.WARNING)
print("--- myapp.db set to WARNING ---")
api.get_user(7)
OUTPUT
INFO myapp.api GET /users/42 DEBUG myapp.db Executing SQL: SELECT * FROM users WHERE id = 42 INFO myapp.db Query finished in 42 ms --- myapp.db set to WARNING --- INFO myapp.api GET /users/7

The Logger Tree and Effective LevelsLogger Tree और Effective Levels记录器树与有效级别

import logging

pool = logging.getLogger("myapp.db.pool")
print(pool.parent.name)                       # "myapp" and "myapp.db" do not exist yet!

app = logging.getLogger("myapp")
db = logging.getLogger("myapp.db")
print(pool.parent.name, db.parent.name, app.parent.name)   # now the tree is complete

app.setLevel(logging.ERROR)
print(pool.level, logging.getLevelName(pool.getEffectiveLevel()))   # 0 = NOTSET -> inherits
db.setLevel(logging.DEBUG)
print(logging.getLevelName(pool.getEffectiveLevel()))               # the nearest set level wins
print(app.getChildren())                                            # Python 3.12+
OUTPUT
root myapp.db myapp root 0 ERROR DEBUG {<Logger myapp.db (DEBUG)>}
LoggerLogger记录器ParentParent父级If its level is NOTSET…अगर इसका level NOTSET है…若其级别为 NOTSET…
root—starts at WARNINGWARNING से शुरू初始为 WARNING
myapprootuses root's levelroot का level लेता है使用 root 的级别
myapp.dbmyappuses myapp's level, or root'smyapp का level लेता है, या root का使用 myapp 的级别,否则 root 的
myapp.db.poolmyapp.dbwalks up until a level is setlevel मिलने तक ऊपर चलता है一直向上找到设置了级别的祖先
🧩
Parents Can Appear LaterParents बाद में भी आ सकते हैं父记录器可以稍后出现

The first line of the example prints root: when myapp.db.pool was created, myapp and myapp.db did not exist yet. As soon as they are created, Python fixes the tree. You never need to create parents first.

Example की पहली line root छापती है: जब myapp.db.pool बना, तब myapp और myapp.db अभी मौजूद नहीं थे। जैसे ही वे बनते हैं, Python tree ठीक कर देता है। Parents पहले बनाने की कभी ज़रूरत नहीं।

示例第一行打印 root:创建 myapp.db.pool 时,myapp 和 myapp.db 还不存在。它们一旦被创建,Python 就会修正这棵树。你从不需要先创建父记录器。


SectionSection第 06 节

Try It — The Log Router 🚦करके देखें — Log Router 🚦动手试试 — 日志路由器 🚦

This is the heart of logging. Set the levels, switch propagate on or off, then send a record from any logger and watch where it goes. The router follows the same rules as Logger.callHandlers(), and it was checked against the real logging module on all 3,840 combinations.

यही logging का दिल है। Levels set करें, propagate चालू या बंद करें, फिर किसी भी logger से record भेजें और देखें वह कहाँ जाता है। Router वही नियम मानता है जो Logger.callHandlers(), और इसे असली logging module से सभी 3,840 combinations पर जाँचा गया है।

这是日志的核心。设置级别,打开或关闭 propagate,然后从任一记录器发送一条记录,看它去了哪里。路由器遵循与 Logger.callHandlers() 相同的规则,并在全部 3,840 种组合上与真实 logging 模块核对过。

🚦 Log RouterLevels, handlers and propagation, live.Levels, handlers और propagation, live।级别、处理器与传递,实时演示。
root
level
🖥️ console [INFO]
myapp
level
propagate
📄 app.log [DEBUG]🧯 errors.log [ERROR]
myapp.db
level
propagate
myapp.api
level NOTSET · propagate True
urllib3
level NOTSET · propagate True
logger
level
Press Send.भेजें दबाएँ।按"发送"。
🖥️ console (stderr)
📄 app.log
🧯 errors.log
🎯
Three Experiments Worth Doingतीन प्रयोग जो ज़रूर करें值得一做的三个实验

① Send myapp.db at DEBUG — dropped at once, because the effective level comes from root (WARNING). ② Set myapp.db to DEBUG and myapp to ERROR, then send DEBUG: it still reaches app.log! A parent's logger level is not checked during propagation. ③ Set myapp propagate to False: the console stops showing myapp lines — a common way to avoid duplicates.

① myapp.db से DEBUG भेजें — तुरंत गिरा, क्योंकि effective level root (WARNING) से आता है। ② myapp.db को DEBUG और myapp को ERROR करें, फिर DEBUG भेजें: यह फिर भी app.log तक पहुँचता है! Propagation के दौरान parent का logger level नहीं जाँचा जाता। ③ myapp का propagate False करें: console पर myapp की lines आनी बंद — duplicates से बचने का आम तरीका।

① 从 myapp.db 发送 DEBUG — 立即被丢弃,因为有效级别来自 root(WARNING)。② 把 myapp.db 设为 DEBUG、myapp 设为 ERROR,再发送 DEBUG:它仍然到达了 app.log!传递过程中不会检查父 记录器 的级别。③ 把 myapp 的 propagate 设为 False:控制台不再显示 myapp 的行 — 这是避免重复的常用办法。


SectionSection第 07 节

Formatters — Making Logs Readable 🖋️Formatters — Logs को पढ़ने लायक बनाना 🖋️格式化器 — 让日志易读 🖋️

PlaceholderPlaceholder占位符Meaningमतलब含义Exampleउदाहरण示例
%(asctime)stime (default has milliseconds)समय (default में milliseconds)时间(默认带毫秒)2026-07-12 14:32:07,213
%(name)slogger namelogger का नाम记录器名称shop.checkout
%(levelname)slevel as textlevel text के रूप में级别文本INFO
%(levelno)dlevel as numberlevel नंबर के रूप में级别数字20
%(module)smodule namemodule का नाम模块名checkout
%(filename)sfile namefile का नाम文件名checkout.py
%(funcName)sfunction namefunction का नाम函数名place_order
%(lineno)dline numberline नंबर行号42
%(threadName)sthread namethread का नाम线程名MainThread
%(process)dprocess IDprocess ID进程 ID4242
%(message)syour text with the args filled inआपका text, args भरकर填好参数的消息文本Order ORD-8821 placed for Rs 1299.50
import logging

FMT = "%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s"
logging.basicConfig(level=logging.INFO, format=FMT, datefmt="%Y-%m-%d %H:%M:%S")

log = logging.getLogger("shop.checkout")
log.info("Order %s placed for Rs %.2f", "ORD-8821", 1299.5)
log.warning("Coupon %s expired but was accepted", "DIWALI25")
OUTPUT (times from when this page was built)OUTPUT (यह page बनते समय का समय)输出(构建本页时的时间)
2026-11-03 14:23:57 | INFO | shop.checkout:7 | Order ORD-8821 placed for Rs 1299.50 2026-11-03 14:23:57 | WARNING | shop.checkout:8 | Coupon DIWALI25 expired but was accepted

Try It — Formatter Builderकरके देखें — Formatter Builder动手试试 — 格式化器构建器

Click placeholders or type your own format. The result is formatted exactly like Python does it, for two real LogRecords. Try a misspelt field, or a format with no field at all.

Placeholders पर click करें या अपना format लिखें। नतीजा दो असली LogRecords के लिए ठीक वैसे ही format होता है जैसे Python करता है। ग़लत spelling वाला field, या बिना field वाला format आज़माएँ।

点击占位符或输入你自己的格式。结果会按照 Python 的方式,为两条真实的 LogRecord 进行格式化。试试拼错的字段,或完全没有字段的格式。

🖋️ Formatter BuilderPlaceholders in, one log line out.Placeholders अंदर, एक log line बाहर।放入占位符,输出一行日志。
record
datefmt
presetspresets预设:

SectionSection第 08 节

Handlers — Sending Logs Everywhere 📮Handlers — Logs को हर जगह भेजना 📮处理器 — 把日志送到各处 📮

🖥️
StreamHandler
StreamHandler()
Writes to a stream — sys.stderr by default. The usual choice for the console, and for containers that collect stdout/stderr.
Stream में लिखता है — default रूप से sys.stderr। Console के लिए, और stdout/stderr इकट्ठा करने वाले containers के लिए आम पसंद।
写入一个流 — 默认是 sys.stderr。控制台以及收集 stdout/stderr 的容器的常用选择。
📄
FileHandler
FileHandler('app.log')
Appends to one file for ever. Fine for short scripts; on a server the file grows without limit.
एक file में हमेशा जोड़ता रहता है। छोटी scripts के लिए ठीक; server पर file बिना सीमा बढ़ती है।
永远向同一个文件追加。短脚本没问题;在服务器上文件会无限增长。
🔄
RotatingFileHandler
maxBytes + backupCount
Starts a new file when the size limit is reached and keeps N old files. See the simulator below.
आकार की सीमा पर नई file शुरू करता है और N पुरानी files रखता है। नीचे का simulator देखें।
达到大小上限时新建文件,并保留 N 个旧文件。见下方模拟器。
📅
TimedRotatingFileHandler
when='midnight'
Starts a new file every day (or hour) and keeps N days. Handy for daily reports.
हर दिन (या घंटे) नई file शुरू करता है और N दिन रखता है। रोज़ की reports के लिए काम का।
每天(或每小时)新建文件并保留 N 天。适合日报。
📧
SMTPHandler
CRITICAL only
Emails a record. Use only for rare, critical events — a crash loop can send thousands of mails.
Record email करता है। सिर्फ़ दुर्लभ, critical घटनाओं के लिए — crash loop हज़ारों mails भेज सकता है।
把记录发邮件。只用于少见的严重事件 — 崩溃循环可能发出成千上万封邮件。
🧵
QueueHandler + QueueListener
slow sinks, many processesधीमे sinks, कई processes慢速目标、多进程
Puts records on a queue and writes them in a background thread — the safe way when several processes share one log file, or a handler is slow.
Records को queue में रखता है और background thread में लिखता है — जब कई processes एक log file साझा करें, या handler धीमा हो, तब सुरक्षित तरीका।
把记录放进队列,由后台线程写出 — 当多个 进程 共用一个日志文件或处理器较慢时的安全做法。

Console + Rotating FileConsole + Rotating File控制台 + 轮转文件

import logging
from logging.handlers import RotatingFileHandler

log = logging.getLogger("myapp")
log.setLevel(logging.DEBUG)                        # the logger lets everything in

fmt = logging.Formatter("%(levelname)-8s | %(name)s | %(message)s")

console = logging.StreamHandler()                  # stderr by default
console.setLevel(logging.WARNING)                  # the screen: WARNING and up
console.setFormatter(fmt)

file_h = RotatingFileHandler("myapp.log", maxBytes=5_000_000, backupCount=5, encoding="utf-8")
file_h.setLevel(logging.DEBUG)                     # the file: everything
file_h.setFormatter(fmt)

log.addHandler(console)
log.addHandler(file_h)

log.debug("goes to the FILE only")
log.info("goes to the FILE only")
log.warning("goes to BOTH")
log.error("goes to BOTH")

file_h.close()
print("--- myapp.log ---")
print(open("myapp.log", encoding="utf-8").read(), end="")
OUTPUT
WARNING | myapp | goes to BOTH ERROR | myapp | goes to BOTH --- myapp.log --- DEBUG | myapp | goes to the FILE only INFO | myapp | goes to the FILE only WARNING | myapp | goes to BOTH ERROR | myapp | goes to BOTH
🔐
Two Gates: Logger Level, Then Handler Levelदो दरवाज़े: पहले Logger Level, फिर Handler Level两道门:先是记录器级别,再是处理器级别

Set the logger wide open (DEBUG) and let each handler choose what it keeps: the file takes everything, the screen only WARNING and up. That is the standard pattern.

Logger को पूरा खुला (DEBUG) रखें और हर handler को तय करने दें कि वह क्या रखे: file सब कुछ लेती है, screen सिर्फ़ WARNING और ऊपर। यही मानक तरीका है।

把 记录器 完全放开(DEBUG),让每个 处理器 自己选择保留什么:文件全收,屏幕只显示 WARNING 及以上。这是标准做法。

Try It — Rotating Filesकरके देखें — Rotating Files动手试试 — 轮转文件

A RotatingFileHandler with a tiny limit: 100 bytes and 3 backups. Step through 14 writes and watch the files move down when the limit is reached. Every snapshot was taken from the real files on disk.

बहुत छोटी सीमा वाला RotatingFileHandler: 100 bytes और 3 backups। 14 writes में कदम-दर-कदम चलें और सीमा पर files को नीचे खिसकते देखें। हर snapshot disk की असली files से लिया गया है।

一个上限极小的 RotatingFileHandler:100 字节、3 个备份。逐步执行 14 次写入,看看达到上限时文件如何依次后移。每个快照都取自磁盘上的真实文件。

🔄 Rotating FilesBounded disk use, for ever.Disk का सीमित इस्तेमाल, हमेशा।磁盘占用永远有上限。
🧑‍🤝‍🧑
Many Processes, One File? No.कई Processes, एक File? नहीं।多个进程写同一个文件?不行。

Logging is thread-safe, but several processes writing (and rotating) the same file can lose or mix lines. With multiprocessing or several web workers, send records through a QueueHandler to one listener, give each process its own file, or log to stderr and let the platform collect it.

Logging thread-safe है, पर एक ही file में लिखने (और rotate करने) वाले कई processes lines खो या मिला सकते हैं। multiprocessing या कई web workers के साथ records को QueueHandler से एक listener तक भेजें, हर process को अपनी file दें, या stderr पर log करें और platform को इकट्ठा करने दें।

日志是线程安全的,但多个 进程 写入(并轮转)同一个文件,可能丢行或串行。使用 multiprocessing 或多个 Web worker 时,请通过 QueueHandler 把记录发给同一个监听器,或给每个进程单独的文件,或输出到 stderr 由平台收集。


SectionSection第 09 节

Logging Exceptions — Keep the Traceback 🧯Exceptions log करना — Traceback रखें 🧯记录异常 — 保留回溯 🧯

import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(name)s:%(message)s")
log = logging.getLogger("calc")

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError as e:
        log.error("Bad: %s", e)                         # message only - no traceback
        log.exception("Failed to divide %s by %s", a, b) # ERROR + full traceback
        return None

divide(10, 0)
OUTPUT
ERROR:calc:Bad: division by zero ERROR:calc:Failed to divide 10 by 0 Traceback (most recent call last): File "demo.py", line 7, in divide return a / b ~~^~~ ZeroDivisionError: division by zero
🔑
Three Ways to Attach a TracebackTraceback जोड़ने के तीन तरीके附加回溯的三种方式

1. log.exception("msg") — ERROR level + traceback; use it inside except.
2. log.warning("msg", exc_info=True) — any level + traceback.
3. log.error("msg", exc_info=err) — attach a specific exception object, even outside except.

1. log.exception("msg") — ERROR level + traceback; except के अंदर इस्तेमाल करें।
2. log.warning("msg", exc_info=True) — कोई भी level + traceback।
3. log.error("msg", exc_info=err) — कोई ख़ास exception object जोड़ें, except के बाहर भी।

1. log.exception("msg") — ERROR 级别 + 回溯;在 except 中使用。
2. log.warning("msg", exc_info=True) — 任意级别 + 回溯。
3. log.error("msg", exc_info=err) — 附加指定的异常对象,即使在 except 之外。


SectionSection第 10 节

dictConfig — The Production Pattern 🏭dictConfig — Production का तरीका 🏭dictConfig — 生产环境的做法 🏭

In a real app you describe the whole setup — formatters, handlers, loggers — in one dictionary, and apply it once at start-up. It can be loaded from a JSON or YAML file, and changed per environment.

असली app में आप पूरा setup — formatters, handlers, loggers — एक dictionary में लिखते हैं, और start-up पर एक बार लागू करते हैं। इसे JSON या YAML file से load किया जा सकता है, और हर environment के लिए बदला जा सकता है।

在真实应用中,你把整套配置 — 格式化器、处理器、记录器 — 写进一个字典,启动时应用一次。它可以从 JSON 或 YAML 文件加载,并按环境区分。

import logging, logging.config

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {"format": "%(levelname)-8s | %(name)s | %(message)s"},
        "detailed": {"format": "%(levelname)-8s | %(name)s | %(filename)s:%(lineno)d | %(message)s"},
    },
    "handlers": {
        "console": {"class": "logging.StreamHandler", "level": "INFO", "formatter": "standard"},
        "file": {"class": "logging.handlers.RotatingFileHandler", "level": "DEBUG", "formatter": "detailed",
                 "filename": "app.log", "maxBytes": 5_000_000, "backupCount": 5, "encoding": "utf-8"},
        "errors": {"class": "logging.handlers.RotatingFileHandler", "level": "ERROR", "formatter": "detailed",
                   "filename": "errors.log", "maxBytes": 5_000_000, "backupCount": 10, "encoding": "utf-8"},
    },
    "loggers": {
        "myapp": {"handlers": ["console", "file", "errors"], "level": "DEBUG", "propagate": False},
        "urllib3": {"level": "WARNING"},                  # silence a chatty library
    },
    "root": {"handlers": ["console"], "level": "WARNING"},
}
logging.config.dictConfig(LOGGING)

log = logging.getLogger("myapp.orders")
log.debug("Validating order #100")
log.info("Order #100 processed")
log.error("Payment gateway timeout for order #101")
logging.getLogger("urllib3").info("Starting new HTTPS connection")   # dropped

print(sorted(logging.getHandlerNames()))             # Python 3.12+
for name in ["app.log", "errors.log"]:
    print(f"--- {name} ---")
    print(open(name, encoding="utf-8").read(), end="")
OUTPUT
INFO | myapp.orders | Order #100 processed ERROR | myapp.orders | Payment gateway timeout for order #101 ['console', 'errors', 'file'] --- app.log --- DEBUG | myapp.orders | demo.py:26 | Validating order #100 INFO | myapp.orders | demo.py:27 | Order #100 processed ERROR | myapp.orders | demo.py:28 | Payment gateway timeout for order #101 --- errors.log --- ERROR | myapp.orders | demo.py:28 | Payment gateway timeout for order #101
📈
Why dictConfig WinsdictConfig क्यों बेहतर है为什么 dictConfig 更好

All of the setup is in one place and reviewed as one unit. Keep "disable_existing_loggers": False, or loggers created before the call (for example by imported libraries) are switched off. Since Python 3.12, logging.getHandlerByName("file") gives you a configured handler back by its name.

पूरा setup एक जगह है और एक इकाई के रूप में review होता है। "disable_existing_loggers": False रखें, वरना call से पहले बने loggers (जैसे imported libraries के) बंद हो जाते हैं। Python 3.12 से logging.getHandlerByName("file") configured handler को उसके नाम से वापस देता है।

所有配置集中在一处,作为一个整体审查。请保留 "disable_existing_loggers": False,否则调用之前创建的记录器(例如被导入的库创建的)会被关闭。从 Python 3.12 起,logging.getHandlerByName("file") 可以按名称取回已配置的处理器。


SectionSection第 11 节

Real-World Example — Logging a Flask App 🌐असली उदाहरण — Flask App की Logging 🌐实战示例 — 为 Flask 应用记录日志 🌐

This example really runs: Flask's test client sends three requests without starting a server. You see the access lines, a warning, and a full traceback for the crash — all from one dictConfig.

यह उदाहरण सच में चलता है: Flask का test client बिना server शुरू किए तीन requests भेजता है। आप access lines, एक warning, और crash का पूरा traceback देखते हैं — सब एक dictConfig से।

这个示例是真实运行的:Flask 的测试客户端在不启动服务器的情况下发送了三个请求。你会看到访问日志、一条警告,以及崩溃的完整回溯 — 全部来自一个 dictConfig。

import logging, logging.config, time
from flask import Flask, request, g

logging.config.dictConfig({
    "version": 1,
    "formatters": {"default": {"format": "%(levelname)-8s | %(name)s | %(message)s"}},
    "handlers": {"console": {"class": "logging.StreamHandler", "formatter": "default"}},
    "root": {"handlers": ["console"], "level": "INFO"},
})

app = Flask(__name__)
log = logging.getLogger("shop.api")

@app.before_request
def start_timer():
    g.start = time.perf_counter()

@app.after_request
def log_request(response):
    ms = (time.perf_counter() - g.start) * 1000
    log.info("%s %s -> %d (%s ms)", request.method, request.path, response.status_code,
             "<1" if ms < 1 else round(ms))
    return response

@app.route("/order/<int(signed=True):order_id>")
def get_order(order_id):
    if order_id < 0:
        log.warning("Rejecting negative order id: %s", order_id)
        return {"error": "bad id"}, 400
    if order_id == 13:
        raise RuntimeError("database unavailable")
    return {"id": order_id, "total": 42.0}

@app.errorhandler(Exception)
def unhandled(err):
    log.exception("Unhandled error on %s", request.path)
    return {"error": "internal"}, 500

client = app.test_client()          # send requests without starting a server
for path in ["/order/42", "/order/-1", "/order/13"]:
    client.get(path)
OUTPUT
INFO | shop.api | GET /order/42 -> 200 (<1 ms) WARNING | shop.api | Rejecting negative order id: -1 INFO | shop.api | GET /order/-1 -> 400 (<1 ms) ERROR | shop.api | Unhandled error on /order/13 Traceback (most recent call last): File "/usr/local/lib/python3.13/dist-packages/flask/app.py", line 917, in full_dispatch_request rv = self.dispatch_request() File "/usr/local/lib/python3.13/dist-packages/flask/app.py", line 902, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ File "demo.py", line 31, in get_order raise RuntimeError("database unavailable") RuntimeError: database unavailable INFO | shop.api | GET /order/13 -> 500 (2 ms)

SectionSection第 12 节

Structured (JSON) Logging 🧾Structured (JSON) Logging 🧾结构化(JSON)日志 🧾

Log tools such as Elasticsearch, Loki or Datadog work best with one JSON object per line: every key becomes a searchable field. Use the python-json-logger package, or write a small formatter yourself.

Elasticsearch, Loki या Datadog जैसे log tools हर line पर एक JSON object के साथ सबसे अच्छा काम करते हैं: हर key खोजने लायक field बन जाती है। python-json-logger package इस्तेमाल करें, या ख़ुद एक छोटा formatter लिखें।

Elasticsearch、Loki、Datadog 等日志工具最适合每行一个 JSON 对象:每个键都成为可搜索的字段。可以用 python-json-logger 包,也可以自己写一个小格式化器。

import logging, json
from pythonjsonlogger.json import JsonFormatter     # pip install python-json-logger

log = logging.getLogger("shop")
log.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter("%(levelname)s %(name)s %(message)s"))
log.addHandler(handler)

log.info("Order placed", extra={"order_id": "ORD-8821", "user_id": 42, "amount": 1299.5, "currency": "INR"})

# No package? A tiny JSON formatter with only the standard library:
class SimpleJson(logging.Formatter):
    def format(self, record):
        data = {"level": record.levelname, "logger": record.name, "msg": record.getMessage()}
        data.update(getattr(record, "ctx", {}))
        return json.dumps(data, ensure_ascii=False)

handler.setFormatter(SimpleJson())
log.info("Refund issued", extra={"ctx": {"order_id": "ORD-8821", "amount": 1299.5}})
OUTPUT
{"levelname": "INFO", "name": "shop", "message": "Order placed", "order_id": "ORD-8821", "user_id": 42, "amount": 1299.5, "currency": "INR"} {"level": "INFO", "logger": "shop", "msg": "Refund issued", "order_id": "ORD-8821", "amount": 1299.5}
📦
Note the New Import Pathनया Import Path ध्यान दें注意新的导入路径

In python-json-logger 3.x and later, import JsonFormatter from pythonjsonlogger.json. The old from pythonjsonlogger import jsonlogger still appears in many tutorials but is deprecated.

python-json-logger 3.x और बाद में JsonFormatter को pythonjsonlogger.json से import करें। पुराना from pythonjsonlogger import jsonlogger अब भी बहुत से tutorials में दिखता है पर deprecated है।

在 python-json-logger 3.x 及以后版本中,请从 pythonjsonlogger.json 导入 JsonFormatter。旧写法 from pythonjsonlogger import jsonlogger 仍出现在许多教程里,但已被弃用。


SectionSection第 13 节

Performance — Lazy Formatting ⚡Performance — आलसी Formatting ⚡性能 — 延迟格式化 ⚡

import logging
logging.basicConfig(level=logging.INFO)          # DEBUG is OFF
log = logging.getLogger("perf")

class User:
    def __str__(self):
        print("  (building the user text...)")
        return "Priya"

def expensive_call():
    print("  (expensive_call ran!)")
    return [1, 2, 3]

user = User()
print("1. f-string:")
log.debug(f"User {user}")                      # text is built anyway, then thrown away
print("2. %-style:")
log.debug("User %s", user)                     # str() never runs
print("3. %-style with a function call:")
log.debug("Result %s", expensive_call())       # the ARGUMENT still runs!
print("4. isEnabledFor guard:")
if log.isEnabledFor(logging.DEBUG):
    log.debug("Result %s", expensive_call())   # nothing runs
print("done")
OUTPUT
1. f-string: (building the user text...) 2. %-style: 3. %-style with a function call: (expensive_call ran!) 4. isEnabledFor guard: done
❌ Eagerतुरंत急切
PatternPattern写法
log.debug(f"User {user}")
log.debug("User " + str(user))
The text is built even when DEBUG is off.DEBUG बंद हो तब भी text बनता है।即使 DEBUG 关闭,文本也会被构建。
✅ Lazyआलसी延迟
PatternPattern写法
log.debug("User %s", user)
if log.isEnabledFor(logging.DEBUG): ...
Formatting happens only if the record is emitted.Formatting तभी होती है जब record निकलता है।只有记录真正输出时才格式化。
⚠️
%-Style Delays Formatting — Not Your Function Calls%-Style Formatting टालता है — आपकी Function Calls नहीं%-风格延迟的是格式化 — 不是你的函数调用

Example 3 above shows it: log.debug("Result %s", expensive_call()) still runs expensive_call(), because Python evaluates arguments before calling any function. Only the string building is skipped. For truly expensive values, wrap the call in if log.isEnabledFor(logging.DEBUG):.

ऊपर का उदाहरण 3 यही दिखाता है: log.debug("Result %s", expensive_call()) फिर भी expensive_call() चलाता है, क्योंकि Python कोई भी function बुलाने से पहले arguments निकालता है। सिर्फ़ string बनाना छूटता है। सचमुच महँगी values के लिए call को if log.isEnabledFor(logging.DEBUG): में लपेटें।

上面的第 3 个例子说明了这一点:log.debug("Result %s", expensive_call()) 仍然会运行 expensive_call(),因为 Python 在调用任何函数之前都会先求值参数。被跳过的只是 字符串拼接。对真正昂贵的值,请把调用包在 if log.isEnabledFor(logging.DEBUG): 里。


SectionSection第 14 节

Libraries, Noise, Secrets and Context 🧰Libraries, शोर, Secrets और Context 🧰库、噪音、机密与上下文 🧰

Silencing a Chatty Libraryबातूनी Library को शांत करना让啰嗦的库安静下来

import logging
logging.basicConfig(level=logging.DEBUG, format="%(levelname)-7s %(name)s: %(message)s")

noisy = logging.getLogger("urllib3.connectionpool")   # pretend this is the library
noisy.debug("Starting new HTTPS connection (1): api.example.com:443")
noisy.debug('"GET /v1/users HTTP/1.1" 200 512')

logging.getLogger("urllib3").setLevel(logging.WARNING)   # one line in YOUR app
noisy.debug("Starting new HTTPS connection (2): api.example.com:443")
noisy.warning("Retrying after connection broken")
OUTPUT
DEBUG urllib3.connectionpool: Starting new HTTPS connection (1): api.example.com:443 DEBUG urllib3.connectionpool: "GET /v1/users HTTP/1.1" 200 512 WARNING urllib3.connectionpool: Retrying after connection broken

If You Write a Library: NullHandler, Never basicConfigअगर आप Library लिखते हैं: NullHandler, कभी basicConfig नहीं如果你在写库:用 NullHandler,绝不用 basicConfig

# Inside a LIBRARY you publish (e.g. mylib/__init__.py):
import logging
logging.getLogger("mylib").addHandler(logging.NullHandler())   # never basicConfig()!

def work():
    logging.getLogger("mylib").info("working...")   # silent unless the APP configures logging

work()
print("an app that never configured logging sees nothing from mylib")

logging.basicConfig(level=logging.INFO, format="%(name)s: %(message)s")   # the APP decides
work()
OUTPUT
an app that never configured logging sees nothing from mylib mylib: working...

Hiding Passwords with a FilterFilter से Passwords छिपाना用过滤器隐藏密码

import logging, re

class Redact(logging.Filter):
    PATTERN = re.compile(r"(password|token)=\S+", re.IGNORECASE)
    def filter(self, record):
        record.msg = self.PATTERN.sub(r"\1=***", record.getMessage())
        record.args = ()
        return True                                 # keep the (cleaned) record

handler = logging.StreamHandler()
handler.addFilter(Redact())
handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
log = logging.getLogger("auth")
log.addHandler(handler)
log.setLevel(logging.INFO)

log.info("Login user=%s password=%s", "priya", "hunter2")
log.info("Calling API with token=abc123XYZ")
OUTPUT
INFO Login user=priya password=*** INFO Calling API with token=***
🆕
Python 3.12: Filters May Return a New RecordPython 3.12: Filters नया Record लौटा सकते हैंPython 3.12:过滤器可以返回新记录

Since Python 3.12, a filter can return a new LogRecord instead of changing the original. A filter on one handler can then clean the record without affecting the other handlers.

Python 3.12 से filter मूल record बदलने के बजाय एक नया LogRecord लौटा सकता है। तब एक handler का filter दूसरे handlers पर असर डाले बिना record साफ़ कर सकता है।

从 Python 3.12 起,过滤器可以返回一个 新的 LogRecord,而不是修改原记录。这样某个处理器上的过滤器可以清理记录,而不影响其他处理器。

Adding Context to Every Lineहर Line में Context जोड़ना为每一行添加上下文

import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s [req=%(request_id)s] %(message)s")

base = logging.getLogger("shop")
log = logging.LoggerAdapter(base, {"request_id": "a1b2c3"})   # adds context to every line
log.info("Cart loaded")
log.warning("Item %s is out of stock", "SKU-77")
OUTPUT
INFO [req=a1b2c3] Cart loaded WARNING [req=a1b2c3] Item SKU-77 is out of stock

SectionSection第 15 节

Common Pitfalls 🕳️आम ग़लतियाँ 🕳️常见陷阱 🕳️

import logging

# 1. No configuration at all: the "last resort" handler prints WARNING+ to stderr
log = logging.getLogger("app")
log.info("lost - nobody shows INFO")
log.warning("shown by the last-resort handler")

# 2. Adding a handler on every call = duplicate lines
def get_logger():
    lg = logging.getLogger("dup")
    lg.addHandler(logging.StreamHandler())       # BUG: runs every time
    return lg
get_logger(); get_logger().warning("printed twice!")

# 3. basicConfig() does nothing once the root logger has handlers
logging.warning("this call quietly runs basicConfig() first")
logging.basicConfig(level=logging.DEBUG, format=">> %(message)s")   # ignored!
logging.debug("not shown")
logging.basicConfig(level=logging.DEBUG, format=">> %(message)s", force=True)
logging.debug("shown after force=True")
OUTPUT
shown by the last-resort handler printed twice! printed twice! WARNING:root:this call quietly runs basicConfig() first >> shown after force=True
Pitfallग़लती陷阱Symptomलक्षण症状Fixहल解决办法
Handler added on every callहर call पर handler जोड़ना每次调用都添加处理器every line appears 2, 3, 4… timesहर line 2, 3, 4… बार每行出现 2、3、4……次configure once at start-up; or check if not log.handlersstart-up पर एक बार configure करें; या if not log.handlers जाँचें只在启动时配置一次;或检查 if not log.handlers
Parent and child both have handlersParent और child दोनों पर handlers父子记录器都有处理器duplicate linesदोहरी lines重复的行put handlers on one logger, or set propagate=Falsehandlers एक ही logger पर रखें, या propagate=False只在一个记录器上放处理器,或设置 propagate=False
No configurationकोई configuration नहीं没有配置INFO/DEBUG vanish; WARNING+ printed bareINFO/DEBUG ग़ायब; WARNING+ सादा छपता हैINFO/DEBUG 消失;WARNING+ 只打印消息本身call basicConfig() or dictConfig() at start-upstart-up पर basicConfig() या dictConfig()启动时调用 basicConfig() 或 dictConfig()
basicConfig() after a log calllog call के बाद basicConfig()在日志调用之后才调用 basicConfig()your settings are ignoredआपकी settings अनदेखी你的设置被忽略configure first, or pass force=True (3.8+)पहले configure करें, या force=True (3.8+)先配置,或传入 force=True(3.8+)
log.error(str(e))log.error(str(e))log.error(str(e))no tracebacktraceback नहीं没有回溯log.exception(...) inside exceptexcept में log.exception(...)在 except 中使用 log.exception(...)
log.warn(...)log.warn(...)log.warn(...)DeprecationWarningDeprecationWarningDeprecationWarninguse log.warning(...)log.warning(...) इस्तेमाल करें使用 log.warning(...)
Plain FileHandler on a serverServer पर सादा FileHandler服务器上使用普通 FileHandlerdisk slowly fills updisk धीरे-धीरे भरती है磁盘慢慢被写满Rotating or TimedRotating handlerRotating या TimedRotating handler使用 Rotating 或 TimedRotating 处理器

SectionSection第 16 节

logging vs print vs loguru ⚖️logging बनाम print बनाम loguru ⚖️logging、print 与 loguru ⚖️

Featureसुविधा特性print()logging (stdlib)loguru (pip)
LevelsLevels级别❌✅ 5 + custom✅ 7 + custom
Time, file, lineसमय, file, line时间、文件、行号❌✅✅
Many destinationsकई जगहें多个去处❌✅ handlers✅ sinks
Set-up effortSetup की मेहनत配置成本noneकोई नहीं无medium (dictConfig)मध्यम (dictConfig)中等(dictConfig)one lineएक line一行
Ships with PythonPython के साथ आता है随 Python 自带✅✅❌
Used by frameworks and librariesFrameworks और libraries इस्तेमाल करते हैं框架和库使用—✅ everywhere✅ हर जगह✅ 无处不在your app onlyसिर्फ़ आपका app仅限你的应用
Best forकिसके लिए बेहतर最适合quick experimentsजल्दी के प्रयोग快速试验any real projectहर असली project任何真实项目scripts and small toolsscripts और छोटे tools脚本和小工具
🏆
The Practical Ruleव्यावहारिक नियम实用法则

For libraries, frameworks and team projects, use the standard logging module — every library you install already logs through it. loguru is pleasant for personal scripts, and it can capture standard logging records too.

Libraries, frameworks और team projects के लिए standard logging module इस्तेमाल करें — आपकी install की हर library पहले से इसी से log करती है। loguru निजी scripts के लिए सुखद है, और वह standard logging records भी पकड़ सकता है।

对于库、框架和团队项目,请使用标准的 logging 模块 — 你安装的每个库本来就通过它记录日志。loguru 适合个人脚本,它也能接收标准 logging 的记录。


SectionSection第 17 节

Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆

🛡️ Python Logging — Rules to RememberPython Logging — याद रखने वाले नियमPython 日志 — 必须记住的规则
1
Write log = logging.getLogger(__name__) at the top of every module.
हर module के ऊपर log = logging.getLogger(__name__) लिखें।
在每个模块顶部写 log = logging.getLogger(__name__)。
2
Configure once, at start-up, in the application (dictConfig). Libraries only add a NullHandler.
Application में start-up पर एक बार configure करें (dictConfig)। Libraries सिर्फ़ NullHandler जोड़ती हैं।
只在应用启动时配置一次(dictConfig)。库只添加 NullHandler。
3
Use %-style arguments: log.debug("x=%s", x). Guard expensive values with isEnabledFor.
%-style arguments इस्तेमाल करें: log.debug("x=%s", x)। महँगी values को isEnabledFor से बचाएँ।
使用 %-风格参数:log.debug("x=%s", x)。昂贵的值用 isEnabledFor 保护。
4
Inside except, call log.exception(...) so the traceback is kept.
except के अंदर log.exception(...) बुलाएँ ताकि traceback रहे।
在 except 中调用 log.exception(...),保留回溯。
5
Logger wide open, handlers choose: DEBUG to the file, WARNING to the screen.
Logger पूरा खुला, handlers चुनें: file में DEBUG, screen पर WARNING।
记录器全开,由处理器挑选:文件收 DEBUG,屏幕显示 WARNING。
6
Use rotating handlers on servers, and a QueueHandler when several processes log.
Servers पर rotating handlers इस्तेमाल करें, और कई processes log करें तो QueueHandler।
服务器上使用轮转处理器,多进程记录时使用 QueueHandler。
7
Never log passwords, tokens or personal data — add a redaction filter.
Passwords, tokens या निजी data कभी log न करें — redaction filter जोड़ें।
绝不记录密码、令牌或个人数据 — 添加脱敏过滤器。
8
Read the level from configuration (DEBUG on your PC, INFO or WARNING on the server) and ship JSON to stdout in containers.
Level configuration से पढ़ें (PC पर DEBUG, server पर INFO या WARNING) और containers में JSON को stdout पर भेजें।
从配置读取级别(电脑上 DEBUG,服务器上 INFO 或 WARNING),在容器中把 JSON 输出到 stdout。
🆕
What Changed in Recent Pythonsहाल के Python में क्या बदला近期 Python 版本的变化

3.12: Logger.getChildren(), getHandlerByName(), getHandlerNames(), the taskName attribute for asyncio, and filters that return a new record. 3.13: LoggerAdapter(..., merge_extra=True) merges your extra= with the adapter's. 3.15: basicConfig(formatter=...) accepts a ready Formatter.

3.12: Logger.getChildren(), getHandlerByName(), getHandlerNames(), asyncio के लिए taskName attribute, और नया record लौटाने वाले filters। 3.13: LoggerAdapter(..., merge_extra=True) आपके extra= को adapter वाले से मिलाता है। 3.15: basicConfig(formatter=...) तैयार Formatter स्वीकार करता है।

3.12:Logger.getChildren()、getHandlerByName()、getHandlerNames()、用于 asyncio 的 taskName 属性,以及可返回新记录的过滤器。3.13:LoggerAdapter(..., merge_extra=True) 会合并你的 extra= 与适配器自带的。3.15:basicConfig(formatter=...) 可直接接受一个 Formatter。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

Your code says what happened through getLogger(__name__) and five levels; configuration — written once with dictConfig — decides what is kept and where it goes. A record is checked once against the logger's effective level, then offered to every handler on the way up the tree. Keep tracebacks with log.exception, format lazily, rotate your files, and never log secrets.

आपका code getLogger(__name__) और पाँच levels से बताता है कि क्या हुआ; configuration — dictConfig से एक बार लिखी गई — तय करती है कि क्या रखा जाए और कहाँ जाए। Record को logger के effective level से एक बार जाँचा जाता है, फिर tree में ऊपर जाते हुए हर handler को दिया जाता है। log.exception से tracebacks रखें, आलसी formatting करें, files rotate करें, और secrets कभी log न करें।

你的代码通过 getLogger(__name__) 和五个级别说明 发生了什么;配置 — 用 dictConfig 写一次 — 决定保留什么、送到哪里。一条记录只按记录器的有效级别检查一次,然后在沿树向上的途中交给每个处理器。用 log.exception 保留回溯,延迟格式化,轮转文件,绝不记录机密。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.

You have completed Modules. View all sections →