Mundarija (23)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Darajalar
- 2.2. Arxitektura
- 2.3. Ierarxiya va propagatsiya
- 2.4. basicConfig
- 2.5. Dangasa formatlash
- 2.6. Xatolar va kontekst
- 2.7. dictConfig
- 2.8. Handlerlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Darajalar, ierarxiya va propagatsiya
- Misol 2 — Formatlash, xatolar va kontekst
- Misol 3 — Konfiguratsiya va handlerlar
- Misol 4 — Amaliy: tuzilgan JSON jurnal va so'rov identifikatori
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
- 15-qism yakuni
15.16-dars: logging
15-QISM — STANDART KUTUBXONA · 16-dars
1. Kirish va motivatsiya
Kichik skriptda print yetadi. Lekin serverda ishlayotgan dasturda savollar boshqa: kechasi soat 3 da nima bo'ldi? Qaysi so'rovda xato chiqdi? Qaysi foydalanuvchi uchun? Nega bu xabar faqat ishlab chiqarishda ko'rinadi, ishlab chiqishda esa yo'q?
print bu savollarga javob bermaydi: darajasi yo'q (xato va oddiy xabar bir xil), manbasi yo'q (qaysi modul), vaqti yo'q, o'chirib bo'lmaydi va faqat stdout ga boradi (15.11-dars: diagnostika stderr ga). logging — standart kutubxonadagi to'liq jurnal tizimi.
Real vaziyat. Jamoa to'lov xizmatiga batafsil jurnal qo'shdi:
logger.debug(f"to'lov so'rovi: {sorov}")Ishlab chiqarishda DEBUG o'chiq edi — lekin f-string baribir har so'rovda hisoblanardi va sorov ning __repr__ i bazaga murojaat qilardi: javob vaqti 40% oshdi. Keyinroq kimdir sozlamani dictConfig ga ko'chirdi va eski modullarning jurnallari butunlay yo'qoldi — disable_existing_loggers sukut bo'yicha True. Uchinchi muammo: xato xabarlari ikki marta yozilardi va jurnal hajmi ikki barobar edi. Karta raqamlari ham to'liq holda jurnalga tushardi.
Bu darsda logging arxitekturasini (logger, handler, formatter, filter) va shunday tuzoqlarni o'rganamiz. Bu dars 15-qismni yakunlaydi.
Bu darsda:
- Nega
printemas; darajalar - Arxitektura:
Logger→Handler→Formatter,Filter - Logger ierarxiyasi va
getLogger(__name__), propagatsiya -
basicConfigbir marta ishlaydi;force=True - Dangasa formatlash:
%sva f-string exception(),exc_info,stack_infoextra,LoggerAdapter, filtrlar — kontekst va maxfiy ma'lumotni yashirish-
dictConfigvadisable_existing_loggers - Handlerlar: fayl, aylanma fayl,
QueueHandler - Kutubxonalarda:
NullHandler - Amaliy: tuzilgan (JSON) jurnal va so'rov identifikatori
- 15-qism yakuni
2. Nazariya — chuqur tushuntirish
2.1. Darajalar
| Daraja | Son | Qachon |
|---|---|---|
DEBUG |
10 | Ishlab chiqishda batafsil tafsilot |
INFO |
20 | Muhim normal voqealar: ishga tushdi, buyurtma yaratildi |
WARNING |
30 | Kutilmagan, lekin ish davom etadi: qayta urinish, eskirgan API |
ERROR |
40 | Amal bajarilmadi |
CRITICAL |
50 | Dastur ishlay olmaydi |
Logger darajasi va handler darajasi — ikki alohida filtr: xabar ikkalasidan o'tishi kerak.
Sozlanmagan dasturda (basicConfig yo'q) faqat WARNING va undan yuqorisi chiqadi — logging.lastResort handler orqali stderr ga, formatsiz.
2.2. Arxitektura
logger.info("...") → LogRecord → Logger darajasi va filtrlari
↓
Handler lar (har biri: daraja, filtr, Formatter)
↓
ota logger lar handlerlari (propagate=True bo'lsa)| Komponent | Vazifasi | Misollar |
|---|---|---|
Logger |
Xabar manbai, nomli, ierarxik | logging.getLogger(__name__) |
Handler |
Qayerga yozish | StreamHandler, FileHandler, RotatingFileHandler, QueueHandler |
Formatter |
Qanday ko'rinishda | "%(asctime)s %(levelname)s %(name)s: %(message)s" |
Filter |
O'tkazish/o'zgartirish | Maxfiy ma'lumotni yashirish, kontekst qo'shish |
LogRecord |
Bitta xabar | name, levelname, msg, args, exc_info, created, ... |
2.3. Ierarxiya va propagatsiya
logger = logging.getLogger(__name__) # ⭐ "ilova.db.sorovlar"- Nomlar nuqta bilan ierarxiya hosil qiladi:
ilova.db—ilovaning bolasi, hammasi —rootning avlodi. - Darajasi berilmagan logger ota darajasini oladi (
getEffectiveLevel()). - Xabar o'z handlerlaridan keyin ota logger handlerlariga ham boradi (
propagate=True).
Ikki marta yozish: handler ham ilova ga, ham root ga qo'shilsa — har xabar ikki marta chiqadi. Qoida: handlerlar faqat root da (yoki ilovaning eng yuqori loggerida) va propagate=False faqat aniq sabab bilan.
Modul ichida handler qo'shmang — faqat getLogger(__name__). Handler va formatlarni dasturning kirish nuqtasi sozlaydi.
2.4. basicConfig
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")| Fakt | Tafsilot |
|---|---|
| Root da handler bo'lsa | Hech narsa qilmaydi — jim |
force=True |
Mavjud root handlerlarini olib tashlab, qayta sozlaydi |
| Qachon chaqirish | Dastur boshida, bir marta, main() ichida |
| Modul importida chaqirish | Boshqa dasturning sozlamasini buzadi |
2.5. Dangasa formatlash
logger.debug("to'lov: %s", sorov) # ✅ DEBUG o'chiq bo'lsa — str(sorov) chaqirilmaydi
logger.debug(f"to'lov: {sorov}") # ❌ har doim hisoblanadi
if logger.isEnabledFor(logging.DEBUG): # juda qimmat hisob uchun
logger.debug("statistika: %s", ogir_hisob())| Usul | O'chiq darajada narx | Boshqa farq |
|---|---|---|
%s argumentlar |
Deyarli nol | Bir xil xabar shabloni — jurnal tizimlarida guruhlash oson |
| f-string | To'liq hisob | Har xabar noyob satr |
2.6. Xatolar va kontekst
| Vosita | Nima beradi |
|---|---|
logger.exception("...") |
ERROR + traceback — faqat except ichida |
logger.error("...", exc_info=True) |
Istalgan darajada traceback |
stack_info=True |
Joriy chaqiruv steki (xatosiz) |
extra={"sorov_id": ...} |
Yozuvga maydon qo'shish (formatda %(sorov_id)s) |
LoggerAdapter(logger, {"sorov_id": ...}) |
Kontekstni har chaqiruvda takrorlamaslik |
Filter |
Har yozuvni o'zgartirish yoki rad etish |
Formatda %(sorov_id)s bo'lsa-yu, yozuvda extra bo'lmasa — logging istisno ko'tarmaydi, stderr ga "--- Logging error ---" yozadi va xabar yo'qoladi. Yechim: filtr orqali sukut qiymat qo'shish.
Maxfiy ma'lumot (parol, token, karta raqami) jurnalga tushmasligi uchun — handler darajasidagi filtr va repr larni ehtiyotkor yozish.
2.7. dictConfig
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False, # ⭐ doim aniq yozing
"formatters": {"oddiy": {"format": "%(levelname)s %(name)s: %(message)s"}},
"handlers": {"konsol": {"class": "logging.StreamHandler", "formatter": "oddiy", "stream": "ext://sys.stderr"}},
"root": {"handlers": ["konsol"], "level": "INFO"},
}) disable_existing_loggers sukut bo'yicha True: dictConfig chaqirilishidan oldin yaratilgan loggerlar (odatda — import qilingan modullarning getLogger(__name__) i) o'chiriladi, ularning xabarlari jim yo'qoladi.
3.12+: logging.getHandlerByName("konsol"), logging.getHandlerNames().
2.8. Handlerlar
| Handler | Qachon |
|---|---|
StreamHandler() |
stderr (sukut) — konteyner va 12-faktor ilovalar uchun |
FileHandler(yol, encoding="utf-8") |
Faylga |
RotatingFileHandler(yol, maxBytes, backupCount) |
Hajm bo'yicha aylanish |
TimedRotatingFileHandler(yol, when="midnight") |
Vaqt bo'yicha aylanish |
QueueHandler + QueueListener |
Sekin handlerlarni (tarmoq, fayl) alohida ipga ko'chirish |
NullHandler |
Kutubxonalarda: "sozlanmagan" ogohlantirishining oldini olish |
handlers.SysLogHandler, SMTPHandler, HTTPHandler |
Tashqi tizimlar |
Kutubxona qoidasi: kutubxona hech qachon basicConfig chaqirmaydi va handler qo'shmaydi — faqat getLogger(__name__) va kerak bo'lsa addHandler(NullHandler()). Ilova qanday yozishni o'zi hal qiladi.
Konteyner va bulutda: faylga emas, stderr ga, bir qatorli JSON formatda yozing — jurnal tizimi (Loki, ELK, CloudWatch) uni yig'adi va maydonlar bo'yicha qidiradi.
3. Tez ma'lumotnoma
import logging
logger = logging.getLogger(__name__) # modulda — faqat shu
def main() -> int:
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s")
logger.info("ishga tushdi: port=%s", port)
try:
...
except Exception:
logger.exception("buyurtma bajarilmadi: id=%s", buyurtma_id)
return 1
return 0
log = logging.LoggerAdapter(logger, {"sorov_id": sid})
logging.config.dictConfig({..., "disable_existing_loggers": False})Qoidalar
modulda: getLogger(__name__), handler yo'q
sozlash — kirish nuqtasida, bir marta
%s argumentlar, f-string emas
except ichida — logger.exception
dictConfig — disable_existing_loggers: False
handler — root da; ikki marta chiqsa, propagatsiyani tekshiring
maxfiy ma'lumot — filtr bilan yashiring4. Batafsil misollar
Misol 1 — Darajalar, ierarxiya va propagatsiya
"""Sozlanmagan holat (lastResort); logger va handler darajalari; getLogger ierarxiyasi; getEffectiveLevel; ikki marta yozish va uni tuzatish; basicConfig va force."""
import contextlib
import io
import logging
def handler(nishon: str, oqim: io.StringIO, daraja: int = logging.NOTSET) -> logging.Handler:
h = logging.StreamHandler(oqim)
h.setLevel(daraja)
h.setFormatter(logging.Formatter(f"{nishon} %(levelname)s %(name)s: %(message)s"))
return h
def main() -> None:
print("=== 1. Sozlanmagan dastur ===")
xato = io.StringIO()
with contextlib.redirect_stderr(xato):
log = logging.getLogger("sozlanmagan")
log.debug("debug"); log.info("info"); log.warning("warning"); log.error("error")
print(f" stderr: {xato.getvalue().splitlines()} ← faqat WARNING+, formatsiz (lastResort)")
print("\n=== 2. Logger va handler darajalari ===")
oqim = io.StringIO()
root = logging.getLogger()
root.handlers.clear()
root.setLevel(logging.DEBUG)
root.addHandler(handler("[konsol]", oqim, logging.INFO))
db = logging.getLogger("ilova.db")
db.debug("SQL: SELECT 1")
db.info("ulanish ochildi")
logging.getLogger("ilova.tolov").setLevel(logging.ERROR)
logging.getLogger("ilova.tolov.karta").warning("urinish 2/3")
logging.getLogger("ilova.tolov.karta").error("rad etildi")
for qator in oqim.getvalue().splitlines():
print(f" {qator}")
karta = logging.getLogger("ilova.tolov.karta")
print(f" ilova.tolov.karta: o'z darajasi={karta.level}, samarali={logging.getLevelName(karta.getEffectiveLevel())}, ota={karta.parent.name}")
print("\n=== 3. ⚠️ Ikki marta yozish ===")
oqim = io.StringIO()
root.handlers.clear()
root.addHandler(handler("[root]", oqim))
ilova = logging.getLogger("ilova")
ilova.addHandler(handler("[ilova]", oqim))
logging.getLogger("ilova.api").warning("sekin javob: 2.3 s")
print(f" {oqim.getvalue().splitlines()}")
ilova.handlers.clear()
oqim.truncate(0); oqim.seek(0)
logging.getLogger("ilova.api").warning("sekin javob: 2.3 s")
print(f" handler faqat root da: {oqim.getvalue().splitlines()}")
print("\n=== 4. ⚠️ basicConfig bir marta ===")
root.handlers.clear()
birinchi, ikkinchi = io.StringIO(), io.StringIO()
logging.basicConfig(stream=birinchi, level=logging.INFO, format="1: %(message)s")
logging.basicConfig(stream=ikkinchi, level=logging.DEBUG, format="2: %(message)s")
logging.getLogger("x").info("salom")
print(f" ikkinchi basicConfig jim e'tiborsiz: birinchi={birinchi.getvalue().strip()!r}, ikkinchi={ikkinchi.getvalue()!r}")
logging.basicConfig(stream=ikkinchi, level=logging.DEBUG, format="2: %(message)s", force=True)
logging.getLogger("x").debug("endi ishladi")
print(f" force=True: ikkinchi={ikkinchi.getvalue().strip()!r}")
root.handlers.clear()
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Sozlanmagan dastur ===
stderr: ['warning', 'error'] ← faqat WARNING+, formatsiz (lastResort)
=== 2. Logger va handler darajalari ===
[konsol] INFO ilova.db: ulanish ochildi
[konsol] ERROR ilova.tolov.karta: rad etildi
ilova.tolov.karta: o'z darajasi=0, samarali=ERROR, ota=ilova.tolov
=== 3. ⚠️ Ikki marta yozish ===
['[ilova] WARNING ilova.api: sekin javob: 2.3 s', '[root] WARNING ilova.api: sekin javob: 2.3 s']
handler faqat root da: ['[root] WARNING ilova.api: sekin javob: 2.3 s']
=== 4. ⚠️ basicConfig bir marta ===
ikkinchi basicConfig jim e'tiborsiz: birinchi='1: salom', ikkinchi=''
force=True: ikkinchi='2: endi ishladi'Nima ko'rsatdi: 2.1, 2.2, 2.3, 2.4-bo'limlar.
Misol 2 — Formatlash, xatolar va kontekst
"""Dangasa %s va f-string narxi; isEnabledFor; exception va exc_info; extra va yetishmayotgan maydon xatosi; LoggerAdapter; maxfiy ma'lumotni yashiruvchi filtr."""
import contextlib
import io
import logging
import re
class Sorov:
def __init__(self) -> None:
self.repr_chaqiruvlari = 0
def __repr__(self) -> str:
self.repr_chaqiruvlari += 1 # masalan, bazaga murojaat
return "Sorov(id=42)"
class KontekstFiltri(logging.Filter):
def filter(self, yozuv: logging.LogRecord) -> bool:
if not hasattr(yozuv, "sorov_id"):
yozuv.sorov_id = "-"
return True
class MaxfiyFiltr(logging.Filter):
NAQSHLAR = [
(re.compile(r"(?<![0-9])([0-9]{4})[0-9 ]{8,11}([0-9]{4})(?![0-9])"), r"\1 **** **** \2"),
(re.compile(r"(parol|token)=\S+", re.IGNORECASE), r"\1=***"),
]
def filter(self, yozuv: logging.LogRecord) -> bool:
matn = yozuv.getMessage()
for naqsh, almashtirma in self.NAQSHLAR:
matn = naqsh.sub(almashtirma, matn)
yozuv.msg, yozuv.args = matn, None
return True
def logger_yarat(nom: str, oqim: io.StringIO, fmt: str, daraja: int = logging.DEBUG) -> logging.Logger:
log = logging.getLogger(nom)
log.handlers.clear()
log.propagate = False
log.setLevel(daraja)
h = logging.StreamHandler(oqim)
h.setFormatter(logging.Formatter(fmt))
log.addHandler(h)
return log
def main() -> None:
print("=== 1. ⚠️ Dangasa formatlash ===")
log = logger_yarat("tolov", io.StringIO(), "%(message)s", daraja=logging.INFO)
sorov = Sorov()
for _ in range(1000):
log.debug("so'rov: %r", sorov)
print(f" %r argument, DEBUG o'chiq: repr chaqiruvlari = {sorov.repr_chaqiruvlari}")
for _ in range(1000):
log.debug(f"so'rov: {sorov!r}")
print(f" f-string, DEBUG o'chiq: repr chaqiruvlari = {sorov.repr_chaqiruvlari}")
print(f" isEnabledFor(DEBUG): {log.isEnabledFor(logging.DEBUG)}")
print("\n=== 2. exception ===")
oqim = io.StringIO()
log = logger_yarat("hisob", oqim, "%(levelname)s %(message)s")
try:
{"narx": 100}["chegirma"]
except KeyError:
log.exception("buyurtma hisoblanmadi: id=%s", 1234)
qatorlar = oqim.getvalue().splitlines()
print(f" birinchi qator: {qatorlar[0]!r}")
print(f" traceback bor: {qatorlar[1] == 'Traceback (most recent call last):'}, oxirgi: {qatorlar[-1]!r}")
print("\n=== 3. ⚠️ extra va yetishmayotgan maydon ===")
oqim = io.StringIO()
log = logger_yarat("api", oqim, "%(levelname)s [%(sorov_id)s] %(message)s")
log.info("buyurtma yaratildi", extra={"sorov_id": "r-7f3a"})
xato = io.StringIO()
with contextlib.redirect_stderr(xato):
log.info("sorov_id siz xabar")
print(f" jurnal: {oqim.getvalue().splitlines()}")
print(f" stderr: {xato.getvalue().splitlines()[0]!r} — xabar yo'qoldi")
log.handlers[0].addFilter(KontekstFiltri())
log.info("filtr bilan")
print(f" filtr sukut qo'ydi: {oqim.getvalue().splitlines()[-1]!r}")
print("\n=== 4. LoggerAdapter ===")
adapter = logging.LoggerAdapter(log, {"sorov_id": "r-9b21"})
adapter.info("to'lov boshlandi")
adapter.warning("bank javobi sekin")
print(f" {oqim.getvalue().splitlines()[-2:]}")
print("\n=== 5. Maxfiy ma'lumotni yashirish ===")
oqim = io.StringIO()
log = logger_yarat("xavfsiz", oqim, "%(message)s")
log.handlers[0].addFilter(MaxfiyFiltr())
log.info("karta %s bilan to'lov", "8600 1234 5678 9012")
log.warning("ulanish: host=db parol=Maxfiy123 token=sk_live_abc")
for qator in oqim.getvalue().splitlines():
print(f" {qator}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. ⚠️ Dangasa formatlash ===
%r argument, DEBUG o'chiq: repr chaqiruvlari = 0
f-string, DEBUG o'chiq: repr chaqiruvlari = 1000
isEnabledFor(DEBUG): False
=== 2. exception ===
birinchi qator: 'ERROR buyurtma hisoblanmadi: id=1234'
traceback bor: True, oxirgi: "KeyError: 'chegirma'"
=== 3. ⚠️ extra va yetishmayotgan maydon ===
jurnal: ['INFO [r-7f3a] buyurtma yaratildi']
stderr: '--- Logging error ---' — xabar yo'qoldi
filtr sukut qo'ydi: 'INFO [-] filtr bilan'
=== 4. LoggerAdapter ===
["INFO [r-9b21] to'lov boshlandi", 'WARNING [r-9b21] bank javobi sekin']
=== 5. Maxfiy ma'lumotni yashirish ===
karta 8600 **** **** 9012 bilan to'lov
ulanish: host=db parol=*** token=***Nima ko'rsatdi: 2.5, 2.6-bo'limlar.
Misol 3 — Konfiguratsiya va handlerlar
"""dictConfig va disable_existing_loggers tuzog'i; getHandlerByName; RotatingFileHandler; QueueHandler va QueueListener; kutubxonada NullHandler."""
import io
import logging
import logging.config
import logging.handlers
import queue
import tempfile
import time
from pathlib import Path
def konfiguratsiya(oqim: io.StringIO, disable: bool | None) -> dict:
sozlama = {
"version": 1,
"formatters": {"oddiy": {"format": "%(levelname)s %(name)s: %(message)s"}},
"handlers": {"konsol": {"class": "logging.StreamHandler", "formatter": "oddiy", "stream": oqim}},
"root": {"handlers": ["konsol"], "level": "INFO"},
}
if disable is not None:
sozlama["disable_existing_loggers"] = disable
return sozlama
class SekinHandler(logging.Handler):
def __init__(self) -> None:
super().__init__()
self.yozilganlar: list[str] = []
def emit(self, yozuv: logging.LogRecord) -> None:
time.sleep(0.02) # tarmoqqa yuborish kabi
self.yozilganlar.append(yozuv.getMessage())
def main() -> None:
print("=== 1. ⚠️ dictConfig va avval yaratilgan loggerlar ===")
for disable in (None, False):
eski = logging.getLogger(f"eski_modul_{disable}") # import paytida yaratilgan kabi
oqim = io.StringIO()
logging.config.dictConfig(konfiguratsiya(oqim, disable))
eski.warning("muhim ogohlantirish")
logging.getLogger("yangi").warning("yangi logger")
nom = "sukut (True)" if disable is None else "False"
print(f" disable_existing_loggers={nom:12} eski.disabled={eski.disabled!s:5} jurnal={oqim.getvalue().splitlines()}")
print(f" getHandlerByName('konsol'): {type(logging.getHandlerByName('konsol')).__name__}")
print("\n=== 2. RotatingFileHandler ===")
with tempfile.TemporaryDirectory() as katalog:
yol = Path(katalog) / "ilova.log"
h = logging.handlers.RotatingFileHandler(yol, maxBytes=200, backupCount=2, encoding="utf-8")
h.setFormatter(logging.Formatter("%(message)s"))
log = logging.getLogger("aylanma")
log.propagate = False
log.addHandler(h)
log.setLevel(logging.INFO)
for i in range(20):
log.info("buyurtma #%03d qayta ishlandi — oʻzbekcha matn", i)
h.close()
fayllar = sorted(p.name for p in Path(katalog).iterdir())
print(f" fayllar: {fayllar}")
print(f" har biri 200 baytdan oshmaydi: {all(p.stat().st_size <= 200 for p in Path(katalog).iterdir())}")
print(f" eng yangi yozuv asosiy faylda: {'#019' in yol.read_text(encoding='utf-8')}")
print(f" eng eski yozuvlar o'chdi (backupCount=2): {not any('#000' in p.read_text(encoding='utf-8') for p in Path(katalog).iterdir())}")
print("\n=== 3. QueueHandler — sekin handler asosiy ipni to'xtatmasin ===")
sekin = SekinHandler()
to_g_ridan = logging.getLogger("togridan")
to_g_ridan.propagate = False
to_g_ridan.addHandler(sekin)
boshlanish = time.perf_counter()
for i in range(10):
to_g_ridan.warning("xabar %d", i)
togridan_vaqt = time.perf_counter() - boshlanish
navbat: queue.Queue = queue.Queue()
sekin2 = SekinHandler()
tinglovchi = logging.handlers.QueueListener(navbat, sekin2)
tinglovchi.start()
navbatli = logging.getLogger("navbatli")
navbatli.propagate = False
navbatli.addHandler(logging.handlers.QueueHandler(navbat))
boshlanish = time.perf_counter()
for i in range(10):
navbatli.warning("xabar %d", i)
navbat_vaqt = time.perf_counter() - boshlanish
tinglovchi.stop()
print(f" to'g'ridan: 10 xabar 0.15 s dan ko'p: {togridan_vaqt > 0.15}")
print(f" navbat orqali: 10 barobardan ko'p tez: {togridan_vaqt / navbat_vaqt > 10}")
print(f" hammasi yozildi va tartib saqlandi: {sekin2.yozilganlar == [f'xabar {i}' for i in range(10)]}")
print("\n=== 4. Kutubxonada NullHandler ===")
kutubxona = logging.getLogger("mening_kutubxonam")
kutubxona.addHandler(logging.NullHandler())
print(f" kutubxona faqat NullHandler qo'shadi: {[type(h).__name__ for h in kutubxona.handlers]}")
print(" ⭐ basicConfig, format, fayl — ilovaning ishi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. ⚠️ dictConfig va avval yaratilgan loggerlar ===
disable_existing_loggers=sukut (True) eski.disabled=True jurnal=['WARNING yangi: yangi logger']
disable_existing_loggers=False eski.disabled=False jurnal=['WARNING eski_modul_False: muhim ogohlantirish', 'WARNING yangi: yangi logger']
getHandlerByName('konsol'): StreamHandler
=== 2. RotatingFileHandler ===
fayllar: ['ilova.log', 'ilova.log.1', 'ilova.log.2']
har biri 200 baytdan oshmaydi: True
eng yangi yozuv asosiy faylda: True
eng eski yozuvlar o'chdi (backupCount=2): True
=== 3. QueueHandler — sekin handler asosiy ipni to'xtatmasin ===
to'g'ridan: 10 xabar 0.15 s dan ko'p: True
navbat orqali: 10 barobardan ko'p tez: True
hammasi yozildi va tartib saqlandi: True
=== 4. Kutubxonada NullHandler ===
kutubxona faqat NullHandler qo'shadi: ['NullHandler']
⭐ basicConfig, format, fayl — ilovaning ishiNima ko'rsatdi: 2.7, 2.8-bo'limlar.
Misol 4 — Amaliy: tuzilgan JSON jurnal va so'rov identifikatori
Kirishdagi vaziyatni to'g'ri hal qilamiz. Veb-xizmat uchun jurnal: stderr ga bir qatorli JSON, har yozuvda sorov_id (contextvars orqali — asyncio vazifalari orasida aralashmaydi), maxfiy maydonlar yashiriladi, xatolar traceback bilan, sozlama dictConfig orqali (disable_existing_loggers: False), darajani LOG_DARAJASI muhit o'zgaruvchisi belgilaydi. Bir nechta bir vaqtdagi so'rov simulyatsiya qilinadi, keyin jurnal JSON sifatida tahlil qilinadi. Vaqt sinov uchun qat'iy qilingan.
"""JSON formatter; contextvars bilan so'rov identifikatori; asyncio vazifalarida kontekst; maxfiy maydonlar; exception maydoni; dictConfig; jurnalni tahlil qilish."""
import asyncio
import contextvars
import io
import json
import logging
import logging.config
from collections import Counter
SOROV_ID: contextvars.ContextVar[str] = contextvars.ContextVar("sorov_id", default="-")
MAXFIY = {"parol", "token", "karta"}
QATIY_VAQT = 1_789_635_600.0
class KontekstFiltri(logging.Filter):
def filter(self, yozuv: logging.LogRecord) -> bool:
yozuv.sorov_id = SOROV_ID.get()
yozuv.created = QATIY_VAQT # faqat misol uchun — natija takrorlansin
return True
class JsonFormatter(logging.Formatter):
def format(self, yozuv: logging.LogRecord) -> str:
malumot = {
"vaqt": self.formatTime(yozuv, "%Y-%m-%dT%H:%M:%S"),
"daraja": yozuv.levelname,
"logger": yozuv.name,
"xabar": yozuv.getMessage(),
"sorov_id": getattr(yozuv, "sorov_id", "-"),
}
for kalit, qiymat in getattr(yozuv, "maydonlar", {}).items():
malumot[kalit] = "***" if kalit in MAXFIY else qiymat
if yozuv.exc_info:
malumot["xato"] = self.formatException(yozuv.exc_info).splitlines()[-1]
return json.dumps(malumot, ensure_ascii=False)
def sozla(oqim: io.StringIO, daraja: str) -> None:
JsonFormatter.converter = __import__("time").gmtime
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False,
"filters": {"kontekst": {"()": KontekstFiltri}},
"formatters": {"json": {"()": JsonFormatter}},
"handlers": {"stderr": {"class": "logging.StreamHandler", "stream": oqim,
"formatter": "json", "filters": ["kontekst"]}},
"root": {"handlers": ["stderr"], "level": daraja},
})
log = logging.getLogger("dokon.buyurtma")
async def buyurtma(sorov_id: str, mahsulot: str, soni: int, karta: str) -> None:
SOROV_ID.set(sorov_id)
log.info("buyurtma qabul qilindi", extra={"maydonlar": {"mahsulot": mahsulot, "soni": soni, "karta": karta}})
await asyncio.sleep(0.01 * soni)
log.debug("ombor tekshirildi")
try:
if soni > 5:
raise ValueError(f"omborda {mahsulot} yetarli emas")
log.info("buyurtma yaratildi", extra={"maydonlar": {"summa": soni * 45_000}})
except ValueError:
log.exception("buyurtma rad etildi")
async def xizmat() -> None:
await asyncio.gather(
buyurtma("r-001", "daftar", 3, "8600123456789012"),
buyurtma("r-002", "kitob", 9, "8600987654321098"),
buyurtma("r-003", "ruchka", 1, "9860111122223333"),
)
def main() -> None:
muhit = {"LOG_DARAJASI": "INFO"}
oqim = io.StringIO()
sozla(oqim, muhit["LOG_DARAJASI"])
asyncio.run(xizmat())
yozuvlar = [json.loads(q) for q in oqim.getvalue().splitlines()]
print("=== 1. Jurnal (har qator — JSON) ===")
for yozuv in sorted(yozuvlar, key=lambda y: (y["sorov_id"], y["xabar"])):
print(f" {json.dumps(yozuv, ensure_ascii=False)}")
print("\n=== 2. Tahlil ===")
print(f" darajalar: {dict(Counter(y['daraja'] for y in yozuvlar))}")
print(f" DEBUG yozilmadi (LOG_DARAJASI=INFO): {all(y['daraja'] != 'DEBUG' for y in yozuvlar)}")
print(f" har so'rov o'z yozuvlariga ega: {dict(Counter(y['sorov_id'] for y in yozuvlar))}")
xatolar = [y for y in yozuvlar if y["daraja"] == "ERROR"]
print(f" xatolar: {[(y['sorov_id'], y['xato']) for y in xatolar]}")
print(f" karta raqami hech qayerda yo'q: {not any('8600' in json.dumps(y) for y in yozuvlar)}")
print(f" jami summa: {sum(y.get('summa', 0) for y in yozuvlar):,} so'm".replace(",", " "))
print("\n=== 3. Daraja muhitdan ===")
oqim2 = io.StringIO()
sozla(oqim2, "DEBUG")
asyncio.run(xizmat())
debug = [json.loads(q) for q in oqim2.getvalue().splitlines() if json.loads(q)["daraja"] == "DEBUG"]
print(f" LOG_DARAJASI=DEBUG: dokon.buyurtma DEBUG yozuvlari {sum(1 for y in debug if y['logger'] == 'dokon.buyurtma')} ta")
print(f" ⚠️ boshqa kutubxonalar ham DEBUG yozadi: {sorted({y['logger'] for y in debug} - {'dokon.buyurtma'})}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Jurnal (har qator — JSON) ===
{"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma qabul qilindi", "sorov_id": "r-001", "mahsulot": "daftar", "soni": 3, "karta": "***"}
{"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma yaratildi", "sorov_id": "r-001", "summa": 135000}
{"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma qabul qilindi", "sorov_id": "r-002", "mahsulot": "kitob", "soni": 9, "karta": "***"}
{"vaqt": "2026-09-17T09:00:00", "daraja": "ERROR", "logger": "dokon.buyurtma", "xabar": "buyurtma rad etildi", "sorov_id": "r-002", "xato": "ValueError: omborda kitob yetarli emas"}
{"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma qabul qilindi", "sorov_id": "r-003", "mahsulot": "ruchka", "soni": 1, "karta": "***"}
{"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma yaratildi", "sorov_id": "r-003", "summa": 45000}
=== 2. Tahlil ===
darajalar: {'INFO': 5, 'ERROR': 1}
DEBUG yozilmadi (LOG_DARAJASI=INFO): True
har so'rov o'z yozuvlariga ega: {'r-001': 2, 'r-002': 2, 'r-003': 2}
xatolar: [('r-002', 'ValueError: omborda kitob yetarli emas')]
karta raqami hech qayerda yo'q: True
jami summa: 180 000 so'm
=== 3. Daraja muhitdan ===
LOG_DARAJASI=DEBUG: dokon.buyurtma DEBUG yozuvlari 3 ta
⚠️ boshqa kutubxonalar ham DEBUG yozadi: ['asyncio']Nima ko'rsatdi: 2.3, 2.5, 2.6, 2.7, 2.8-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"logger.info sozlamasiz ham chiqadi" |
Faqat WARNING+ (lastResort) |
"basicConfig ni qayta chaqirsam sozlama yangilanadi" |
Jim e'tiborsiz — force=True |
"f-string va %s bir xil" |
f-string o'chiq darajada ham hisoblanadi |
| "Handler qo'shgan joyimdagina yoziladi" | Propagatsiya — ota handlerlarga ham |
"dictConfig faqat yangi sozlamani qo'shadi" |
Sukut bo'yicha mavjud loggerlarni o'chiradi |
| "Formatdagi maydon yetishmasa istisno bo'ladi" | "--- Logging error ---" va xabar yo'qoladi |
"Kutubxona basicConfig qilishi mumkin" |
Faqat NullHandler; sozlash — ilova ishi |
| "Jurnal faylga yozilishi kerak" | Konteynerda — stderr + JSON |
6. Keng tarqalgan xatolar va yechimlari
1. print bilan diagnostika
print("xato:", e) # ❌ daraja, manba, vaqt yo'q
logger.exception("buyurtma bajarilmadi") # ✅2. f-string
logger.debug(f"so'rov: {sorov!r}") # ❌
logger.debug("so'rov: %r", sorov) # ✅3. Modulda sozlash
# ilova/db.py
logging.basicConfig(level=logging.DEBUG) # ❌ import qilgan dasturni buzadi
logger = logging.getLogger(__name__) # ✅ faqat shu4. Ikki marta yozish
logging.getLogger("ilova").addHandler(h)
logging.basicConfig() # ⚠️ root da ham handler5. dictConfig sukuti
logging.config.dictConfig({"version": 1, ...}) # ⚠️ eski loggerlar o'chadi
logging.config.dictConfig({"version": 1, "disable_existing_loggers": False, ...}) # ✅6. Traceback yo'qotish
except Exception as e:
logger.error(f"xato: {e}") # ❌ qayerda — noma'lum
logger.exception("xato") # ✅7. Maxfiy ma'lumot
logger.info("login: %s parol=%s", foydalanuvchi, parol) # ❌8. root loggerga to'g'ridan-to'g'ri yozish
logging.info("...") # ⚠️ manba noma'lum, root sozlamasiga bog'liq
logger = logging.getLogger(__name__); logger.info("...") # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 14.6-dars (o'tilgan):
asyncio—contextvarsvazifalar orasida - 15.11-dars (o'tilgan):
stdout/stderr, muhit o'zgaruvchilari - 15.15-dars (o'tilgan):
-vbayroqlarini jurnal darajasiga bog'lash - 17-qism: testlarda jurnalni tekshirish (
caplog) - 28.11–28.12-darslar: markazlashgan loglar va xatolarni kuzatish (Sentry)
- 20.7-dars: bog'liqliklar — so'rov identifikatori va middleware
- 28.10-dars: monitoring va metrikalar — kuzatuvchanlik
8. Eng yaxshi amaliyotlar
Har modulda
logger = logging.getLogger(__name__).Sozlash — faqat kirish nuqtasida, bir marta.
%sargumentlar; qimmat hisob —isEnabledFor.Xatolar —
logger.exception.dictConfigdadisable_existing_loggers: False.Konteynerda —
stderrva JSON.So'rov identifikatori —
contextvarsva filtr.Maxfiy ma'lumotni filtr bilan yashiring va testlang.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import logging
1. print(logging.getLevelName(logging.WARNING))
2. print(logging.DEBUG < logging.INFO < logging.WARNING)
3. print(logging.getLogger("a.b").parent.name)
4. print(logging.getLogger("x") is logging.getLogger("x"))
5. print(logging.getLogger().name)
6. l = logging.getLogger("yangi_q"); print(l.level)
7. print(logging.getLevelNamesMapping()["ERROR"])
8. print(logging.getLogger("p.q").propagate)
9. print(isinstance(logging.NullHandler(), logging.Handler))
10. l = logging.getLogger("z_q"); l.setLevel(logging.ERROR)
print(l.isEnabledFor(logging.WARNING))
11. print(logging.getLevelName(25))
12. print(logging.LoggerAdapter(logging.getLogger("m"), {"k": 1}).extra)Javoblar
WARNINGTrueroot—aloggeri hali yaratilmagan; ota — eng yaqin mavjud logger (akeyinroq yaratilsa, bog'lanish yangilanadi)Trueroot0—NOTSET, ota darajasi ishlatiladi40TrueTrueFalseLevel 25{'k': 1}
Vazifa 2: Xatolarni tuzating
1. # kutubxona/__init__.py
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("kutubxona")
2. def ishla(buyurtma):
logger.debug(f"buyurtma: {buyurtma.toliq_malumot()}")
3. try:
tolov()
except Exception as e:
logger.error("xato: " + str(e))
4. logging.config.dictConfig({"version": 1, "root": {"level": "INFO", "handlers": ["h"]},
"handlers": {"h": {"class": "logging.StreamHandler"}}})
5. logger.info("foydalanuvchi kirdi: %s, token=%s", ism, token)Javoblar
1. # kutubxona/__init__.py
import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
2. def ishla(buyurtma):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("buyurtma: %s", buyurtma.toliq_malumot())
3. try:
tolov()
except Exception:
logger.exception("to'lov bajarilmadi")
4. logging.config.dictConfig({"version": 1, "disable_existing_loggers": False,
"root": {"level": "INFO", "handlers": ["h"]},
"handlers": {"h": {"class": "logging.StreamHandler"}}})
5. logger.info("foydalanuvchi kirdi: %s", ism) # token umuman yozilmaydi
# yoki MaxfiyFiltr bilan yashirishVazifa 3: CLI va jurnal
15.15 dagi vazifalar menejeriga jurnal qo'shing:
-v—INFO,-vv—DEBUG, sukut —WARNING--log-fayl— qo'shimcha ravishdaRotatingFileHandler- Foydalanuvchi xatolari —
WARNING, kutilmagan xatolar —logger.exceptionva kod 1 stdoutnatija uchun toza qolsincaplogsiz (17-qismgacha) —StringIOhandler bilan testlang
Vazifa 4: Jurnal tahlilchisi
Misol 4 dagi JSON jurnal (kamida 1000 qator, generator bilan) uchun:
- Darajalar va loggerlar bo'yicha statistika
- Har
sorov_iduchun yozuvlar zanjiri va davomiyligi - Eng ko'p uchragan xato turlari
- Buzilgan (JSON bo'lmagan) qatorlarni hisoblab, o'tkazib yuboring
argparsebilan filtrlar:--daraja,--logger,--sorov
Vazifa 5: Kontekst bilan jurnal
contextvarsasosidawith jurnal_konteksti(foydalanuvchi_id=..., sorov_id=...)kontekst menejeri- Ichma-ich kontekstlar birlashsin, chiqishda tiklansin
asyncio.gatherdagi vazifalar bir-biriga aralashmasligini testlang- Iplarda (
ThreadPoolExecutor) kontekstnicontextvars.copy_context()bilan uzating
Vazifa 6: Jurnal darajasini ishlab turgan dasturda o'zgartirish
- Dastur har 1 soniyada ish bajaradi va jurnalga yozadi
SIGUSR1(Linux) yoki maxsus fayl paydo bo'lganda (log_daraja.txt) daraja o'zgarsin- Faqat ma'lum logger darajasini o'zgartirish (
ilova.db→DEBUG) - Qayta ishga tushirmasdan ishlashini ko'rsating
Vazifa 7: O'ylash
Python'ning logging moduli 2002-yilda Java'ning log4j dizaynidan ilhomlanib yaratilgan: ierarxik loggerlar, handlerlar, formatterlar. Ko'pchilik uni "murakkab" deb biladi va loguru kabi soddaroq kutubxonalarni tanlaydi. Zamonaviy kuzatuvchanlik (OpenTelemetry, tuzilgan jurnallar) davrida bu arxitektura qanchalik o'rinli va qanday kelishuvlar bor?
Javob
Qisqa javob: logging murakkabligi — ajratish (decoupling) narxi: kutubxonalar faqat "nima bo'ldi"ni aytadi, ilova esa "qayerga va qanday yozish"ni hal qiladi. Bu minglab kutubxonali ekotizimda juda muhim va shuning uchun arxitektura hali ham o'rinli. Kamchiliklari — noqulay sozlash API si va tuzilgan jurnallar uchun o'rnatilgan qo'llab-quvvatlashning yo'qligi; ular JSON formatterlar, structlog va OpenTelemetry integratsiyasi bilan to'ldiriladi.
1. Arxitekturaning kuchi
| Xususiyat | Nima beradi |
|---|---|
| Ierarxik nomlar | urllib3 ni WARNING ga, o'z ilovangizni DEBUG ga — alohida |
| Kutubxona va ilova ajratilgan | Kutubxona chiqish joyini bilmaydi va bilishi shart emas |
| Handlerlar | Bir xabar — konsol, fayl, tashqi tizim |
| Standart | Har bir Python kutubxonasi bir xil mexanizmdan foydalanadi |
| Kechiktirilgan formatlash | O'chiq darajada deyarli bepul |
2. Kamchiliklari
| Kamchilik | Oqibat |
|---|---|
| Sozlash API si murakkab | basicConfig tuzoqlari, dictConfig sukutlari |
| Global holat | Testlarda va kutubxonalarda chalkashlik |
| Tuzilgan maydonlar | extra noqulay, formatda maydon yetishmasa xato |
| Kontekst | So'rov identifikatori uchun qo'lda filtr va contextvars |
% formatlash |
Zamonaviy {} sintaksisi bilan nomuvofiqlik |
3. Muqobillar va to'ldiruvchilar
| Vosita | Yondashuv |
|---|---|
loguru |
Bitta tayyor logger, oson sozlash, chiroyli chiqish — kichik ilovalar uchun qulay |
structlog |
Tuzilgan jurnal birinchi o'rinda, kontekst bog'lash; logging ustida ishlay oladi |
python-json-logger |
logging uchun JSON formatter |
| OpenTelemetry | Jurnal, metrika va tracing ni birlashtiradi; logging handler orqali ulanadi |
Muhim nuqta: ko'p muqobillar logging bilan birga ishlaydi — chunki uchinchi tomon kutubxonalari baribir standart logging ga yozadi.
4. Zamonaviy kuzatuvchanlik talablari
- Tuzilgan jurnal: matn emas, maydonlar (JSON) — qidirish va agregatsiya
- Korrelyatsiya:
trace_id/span_id/sorov_id— bir so'rovning butun yo'lini ko'rish - Namunalash va narx: har
DEBUGni saqlash qimmat - Uch ustun: jurnallar, metrikalar, tracing — biri ikkinchisini almashtirmaydi
5. Amaliy tavsiya
| Vaziyat | Tanlov |
|---|---|
| Kutubxona | Faqat logging.getLogger(__name__) + NullHandler |
| Kichik skript | basicConfig yoki loguru |
| Xizmat, konteyner | logging + JSON formatter + contextvars (Misol 4) yoki structlog |
| Taqsimlangan tizim | Yuqoridagi + OpenTelemetry |
6. Xulosa
loggingmurakkabligi — kutubxonalar va ilovalarni ajratish narxi- Bu ajratish ekotizim uchun zarur, shuning uchun arxitektura eskirmagan
- Tuzilgan jurnal va kontekst — qo'shimcha qatlamlar bilan hal qilinadi
- Muqobil tanlasangiz ham, u standart
loggingbilan hamkorlik qilishi kerak
Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.
Xulosa
Bu darsda logging arxitekturasini va uni ishlab chiqarishda to'g'ri ishlatishni o'rgandik.
Eng muhim uch fikr:
Arxitektura: logger — manba, handler — joy, formatter — ko'rinish. Har modulda
getLogger(__name__), sozlash esa faqat kirish nuqtasida. Loggerlar ierarxik: darajasi yo'q logger otasinikini oladi, xabarlar ota handlerlariga ham boradi — handler ham bola, ham root loggerda bo'lsa, har xabar ikki marta yoziladi. Sozlanmagan dastur faqatWARNING+ ni formatsiz chiqaradi,basicConfigesa ikkinchi marta jim e'tiborsiz qoladi (force=True).Jim tuzoqlar. f-string o'chiq darajada ham hisoblanadi —
%sargumentlar esa yo'q (misolda 1000 ta chaqiruv farqi). Formatdagi maydon yozuvda bo'lmasa, xabar "--- Logging error ---" bilan yo'qoladi.dictConfigsukut bo'yicha avval yaratilgan loggerlarni o'chiradi —disable_existing_loggers: Falseni doim yozing. Kutubxona esa faqatNullHandlerqo'shadi.Ishlab chiqarish jurnali — tuzilgan va xavfsiz.
logger.exceptiontraceback bilan,extra/LoggerAdapter/filtr — kontekst uchun,contextvars—asynciovazifalari orasida aralashmaydigan so'rov identifikatori uchun. Konteynerdastderrga bir qatorli JSON, maxfiy maydonlar filtr bilan yashiriladi, sekin handlerlarQueueHandlerorqali alohida ipga, fayllar esaRotatingFileHandlerbilan aylanadi.
15-qism yakuni
| Dars | Mavzu | Asosiy g'oya |
|---|---|---|
| 15.1 | Muntazam ifodalar: asoslar | fullmatch validatsiyada; \d, \w — Unicode; re da \p{L} yo'q |
| 15.2 | Guruh va almashtirish | Nomli guruhlar; findall guruhlarga bog'liq; count/flags nomi bilan |
| 15.3 | Amaliy naqshlar | Lookaround; chegara tuzoqlari; ReDoS va atomik guruhlar; tokenizator |
| 15.4 | datetime |
timedelta — fizik davomiylik; oy qo'shish; ISO 8601; utcnow eskirgan |
| 15.5 | Vaqt mintaqalari | Siljish ≠ mintaqa; yozgi vaqtda bo'shliq va fold; UTC da saqlash |
| 15.6 | Counter, defaultdict |
Sanash va guruhlash; update qo'shadi; o'qish kalit qo'shadi |
| 15.7 | deque, OrderedDict |
Chetlar O(1); maxlen; move_to_end va LRU |
| 15.8 | itertools: asosiy |
Dangasa va bir martalik; groupby faqat ketma-ket; batched |
| 15.9 | Kombinatorika | Avval sanang; portlovchi o'sish; kirish oldindan o'qiladi |
| 15.10 | functools |
Kesh va konkurentlik; singledispatch noaniqligi; vositalar birgalikda |
| 15.11 | os va sys |
Chiqish kodlari va oqimlar; muhit — faqat satrlar; atomar yozish |
| 15.12 | pathlib |
Yo'l — obyekt; resolve + is_relative_to; 3.14 copy/move |
| 15.13 | shutil |
copy2; himoyalangan rmtree; arxiv xavfsizligi |
| 15.14 | subprocess |
Ro'yxat, check, timeout; shell=True in'ektsiyasi; deadlock |
| 15.15 | argparse |
type=bool tuzog'i; ichki buyruqlar; main(argv) -> int |
| 15.16 | logging |
Ierarxiya va propagatsiya; dangasa formatlash; JSON va kontekst |
15-qism bilan kurs 4-bosqich — "Kutubxona va professional vositalar" ning birinchi qismi tugadi. Endi siz matn, vaqt, kolleksiyalar, iteratorlar, fayl tizimi, jarayonlar, CLI va jurnallar bilan professional darajada ishlay olasiz — va har birining jim tuzoqlarini bilasiz.
Keyingi qism — ma'lumot formatlari: matn va binar fayllar, json, csv, pickle va uning xavfi, XML, YAML va TOML, sqlite3 va katta fayllarni oqim bilan o'qish.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!