Jurnal (logging): print dan chiqish
print nima uchun yetarli emas
print — ishlab chiqish paytidagi asbob. Dastur boshqa kompyuterda ishlay boshlagach, u to'rt
muammo tug'diradi:
| Muammo | Nima bo'ladi |
|---|---|
| O'chirib bo'lmaydi | "Batafsil" va "faqat xatolar" rejimini ajratib bo'lmaydi |
| Qayerdan kelgani noma'lum | Qaysi modul, qaysi qator yozgan — ko'rinmaydi |
| Vaqt yo'q | Muammo qachon boshlangani bilinmaydi |
| Faqat ekran | Faylga yozish, serverga yuborish uchun kodni o'zgartirish kerak |
logging shu to'rttasini ham yechadi va u standart kutubxonada.
Beshta daraja
import logging, sys logging.basicConfig(level=logging.DEBUG, format="%(levelname)-8s %(message)s", stream=sys.stdout, force=True) log = logging.getLogger("namuna") log.debug("tafsilot: 3-qator o'qildi") log.info("hisobot tayyor: 6 qator") log.warning("2 qator tashlab yuborildi") log.error("fayl o'qilmadi") log.critical("ma'lumotlar bazasi javob bermayapti")
DEBUG tafsilot: 3-qator o'qildi INFO hisobot tayyor: 6 qator WARNING 2 qator tashlab yuborildi ERROR fayl o'qilmadi CRITICAL ma'lumotlar bazasi javob bermayapti
Darajani ko'tarsak, pastdagilar yo'qoladi:
import logging, sys logging.basicConfig(level=logging.WARNING, format="%(levelname)-8s %(message)s", stream=sys.stdout, force=True) log = logging.getLogger("namuna") log.debug("bu ko'rinmaydi") log.info("bu ham ko'rinmaydi") log.warning("bu ko'rinadi") log.error("bu ham ko'rinadi") print("--- faqat WARNING va yuqorisi chiqdi ---")
WARNING bu ko'rinadi ERROR bu ham ko'rinadi --- faqat WARNING va yuqorisi chiqdi ---
Qaysi darajani qachon ishlatish:
| Daraja | Ma'nosi | Misol |
|---|---|---|
DEBUG |
Tuzatish uchun tafsilot | "5-qator tahlil qilindi" |
INFO |
Odatiy ish jarayoni | "hisobot yozildi: natija.json" |
WARNING |
G'alati, lekin davom etadi | "3 qator tashlab yuborildi" |
ERROR |
Amal bajarilmadi | "faylni yozib bo'lmadi" |
CRITICAL |
Dastur davom eta olmaydi | "bazaga ulanib bo'lmadi" |
Eng keng tarqalgan xato — hamma narsani INFO qilish. Shunda jurnal shovqinga aylanadi va hech kim
o'qimaydi.
Kutubxonada jurnal qanday sozlanadi
Bu eng muhim qoida va ko'pchilik uni buzadi:
Kutubxona jurnalni sozlamaydi. U faqat yozadi. Sozlashni dastur qiladi.
Sabab: logging.basicConfig global holatni o'zgartiradi. Kutubxonangiz uni chaqirsa, sizni
ishlatayotgan dasturning sozlamasini buzadi.
shu faylni yaratingsrc/hisobot/oqish.py
"""CSV o'qish va qatorlarni tekshirib modelga aylantirish.""" import csv import logging from pathlib import Path from .modellar import Natija, Talaba from .tekshiruv import bahoni_tekshir, sanani_tekshir from .xatolar import FaylXato log = logging.getLogger(__name__) # "hisobot.oqish" — modul nomi bilan KERAKLI_USTUNLAR = {"ism", "guruh", "baho", "sana"} def oqi(yol: str | Path) -> list[dict[str, str]]: """CSV ni o'qiydi. Har qanday muammoda FaylXato qaytaradi.""" p = Path(yol) log.debug("fayl o'qilmoqda: %s", p) try: matn = p.read_text(encoding="utf-8") except FileNotFoundError: raise FaylXato(str(p), "fayl topilmadi") from None except PermissionError: raise FaylXato(str(p), "o'qishga ruxsat yo'q") from None except UnicodeDecodeError as e: raise FaylXato(str(p), f"matn UTF-8 emas ({e.reason})") from None qatorlar = list(csv.DictReader(matn.splitlines())) if not qatorlar: raise FaylXato(str(p), "fayl bo'sh yoki faqat sarlavha bor") yetishmayotgan = KERAKLI_USTUNLAR - set(qatorlar[0]) if yetishmayotgan: raise FaylXato(str(p), f"ustunlar yetishmaydi: {sorted(yetishmayotgan)}") log.info("o'qildi: %d qator, fayl %s", len(qatorlar), p.name) return qatorlar def tozala(qatorlar: list[dict[str, str]]) -> Natija: """Xom lug'atlarni Talaba obyektlariga aylantiradi. Xatolar to'planadi, otilmaydi.""" natija = Natija() for i, q in enumerate(qatorlar, start=2): try: baho = bahoni_tekshir(q.get("baho")) sana = sanani_tekshir(q.get("sana")) except ValueError as e: log.debug("%d-qator rad etildi: %s", i, e) natija.xatolar.append((i, q.get("ism", "?"), str(e))) continue natija.talabalar.append(Talaba( ism=q.get("ism", "").strip(), guruh=q.get("guruh", "").strip(), baho=baho, sana=sana, )) if natija.xatolar: log.warning("%d qatordan %d tasi rad etildi", natija.jami, len(natija.xatolar)) return natija
logging.getLogger(__name__) — standart naql. __name__ moduli nomini beradi
(hisobot.oqish), shuning uchun jurnalda qaysi modul yozgani ko'rinadi va darajani modul
bo'yicha sozlash mumkin.
Sozlashni dastur qiladi
shu faylni yaratingsrc/hisobot/__main__.py
"""python3 -m hisobot bilan ishga tushadi.""" import argparse import logging import sys from .hisob import daraja_taqsimoti, guruh_ortachasi from .oqish import oqi, tozala from .xatolar import HisobotXato log = logging.getLogger(__name__) def jurnalni_sozla(batafsil: int) -> None: """Faqat DASTUR sozlaydi, kutubxona emas.""" daraja = {0: logging.WARNING, 1: logging.INFO}.get(batafsil, logging.DEBUG) logging.basicConfig( level=daraja, format="%(asctime)s %(levelname)-8s %(name)s: %(message)s", datefmt="%H:%M:%S", stream=sys.stderr, ) def main(argv: list[str] | None = None) -> int: p = argparse.ArgumentParser(prog="hisobot", description="CSV dan hisobot") p.add_argument("fayl", help="CSV fayl yo'li") p.add_argument("-v", "--batafsil", action="count", default=0, help="-v: INFO, -vv: DEBUG") args = p.parse_args(argv) jurnalni_sozla(args.batafsil) try: n = tozala(oqi(args.fayl)) except HisobotXato as e: log.error("%s", e) return 1 print(f"jami: {n.jami}, to'g'ri: {len(n.talabalar)}, xato: {len(n.xatolar)}") for g, o in guruh_ortachasi(n.talabalar).items(): print(f" {g}: {o}") print("darajalar:", daraja_taqsimoti(n.talabalar)) return 0 if not n.xatolar else 1 if __name__ == "__main__": sys.exit(main())
Endi bir xil dastur uch xil batafsillikda ishlaydi:
echo "=== sukut bo'yicha (faqat WARNING+) ===" .venv/bin/python3 -m hisobot sinov_malumot.csv echo "" echo "=== -v (INFO) ===" .venv/bin/python3 -m hisobot -v sinov_malumot.csv echo "" echo "=== -vv (DEBUG) ===" .venv/bin/python3 -m hisobot -vv sinov_malumot.csv
=== sukut bo'yicha (faqat WARNING+) ===
jami: 8, to'g'ri: 6, xato: 2
A: 90.67
B: 83.0
C: 61.0
darajalar: {'yaxshi': 3, "a'lo": 2, 'qoniqarli': 1}
=== -v (INFO) ===
jami: 8, to'g'ri: 6, xato: 2
A: 90.67
B: 83.0
C: 61.0
darajalar: {'yaxshi': 3, "a'lo": 2, 'qoniqarli': 1}
=== -vv (DEBUG) ===
jami: 8, to'g'ri: 6, xato: 2
A: 90.67
B: 83.0
C: 61.0
darajalar: {'yaxshi': 3, "a'lo": 2, 'qoniqarli': 1}
13:33:14 WARNING hisobot.oqish: 8 qatordan 2 tasi rad etildi
13:33:14 INFO hisobot.oqish: o'qildi: 8 qator, fayl sinov_malumot.csv
13:33:14 WARNING hisobot.oqish: 8 qatordan 2 tasi rad etildi
13:33:14 DEBUG hisobot.oqish: fayl o'qilmoqda: sinov_malumot.csv
13:33:14 INFO hisobot.oqish: o'qildi: 8 qator, fayl sinov_malumot.csv
13:33:14 DEBUG hisobot.oqish: 5-qator rad etildi: baho son emas: ''
13:33:14 DEBUG hisobot.oqish: 8-qator rad etildi: baho son emas: 'abc'
13:33:14 WARNING hisobot.oqish: 8 qatordan 2 tasi rad etildi
Kod o'zgarmadi — faqat daraja o'zgardi. print bilan bu imkonsiz edi.
%s va f-string: nega farqi bor
import logging, sys, time logging.basicConfig(level=logging.WARNING, format="%(message)s", stream=sys.stdout, force=True) log = logging.getLogger("tezlik") def qimmat(): time.sleep(0.0005) return "hisoblangan qiymat" t0 = time.perf_counter() for _ in range(200): log.debug(f"natija: {qimmat()}") # f-string HAR DOIM hisoblanadi fstring_ms = (time.perf_counter() - t0) * 1000 t0 = time.perf_counter() for _ in range(200): log.debug("natija: %s", qimmat()) # bu ham hisoblanadi (argument!) lazy_ms = (time.perf_counter() - t0) * 1000 t0 = time.perf_counter() for _ in range(200): if log.isEnabledFor(logging.DEBUG): log.debug("natija: %s", qimmat()) # bu hisoblanmaydi shart_ms = (time.perf_counter() - t0) * 1000 print(f"f-string : {fstring_ms:6.1f} ms") print(f"%s bilan : {lazy_ms:6.1f} ms") print(f"isEnabledFor bilan : {shart_ms:6.1f} ms") print("(DEBUG o'chiq, ya'ni bu xabarlarning birortasi ham chop etilmadi)")
f-string : 113.9 ms %s bilan : 113.5 ms isEnabledFor bilan : 0.0 ms (DEBUG o'chiq, ya'ni bu xabarlarning birortasi ham chop etilmadi)
Uchala holatda ham hech narsa chop etilmadi, lekin vaqt boshqa.
Asosiy fikr: %s formatlashni kechiktiradi, lekin argumentni emas. Agar argument qimmat
bo'lsa, isEnabledFor bilan o'rab qo'ying. Oddiy o'zgaruvchilar uchun %s yetarli va u odatiy
uslub.
Faylga yozish va aylantirish
Server dasturi jurnalni faylga yozadi. Fayl cheksiz o'smasligi uchun uni aylantirish kerak:
import logging, tempfile, os from logging.handlers import RotatingFileHandler papka = tempfile.mkdtemp() yol = os.path.join(papka, "hisobot.log") log = logging.getLogger("fayl_namuna") log.setLevel(logging.INFO) log.handlers.clear() h = RotatingFileHandler(yol, maxBytes=500, backupCount=3, encoding="utf-8") h.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s", datefmt="%H:%M:%S")) log.addHandler(h) log.propagate = False for i in range(60): log.info("qator %d qayta ishlandi, natija tayyor", i) h.close() fayllar = sorted(os.listdir(papka)) print("yaratilgan fayllar:", fayllar) for f in fayllar: print(f" {f}: {os.path.getsize(os.path.join(papka, f))} bayt") print("\noxirgi faylning birinchi qatori:") with open(os.path.join(papka, "hisobot.log"), encoding="utf-8") as f: print(" ", f.readline().strip()) import shutil; shutil.rmtree(papka)
yaratilgan fayllar: ['hisobot.log', 'hisobot.log.1', 'hisobot.log.2', 'hisobot.log.3'] hisobot.log: 318 bayt hisobot.log.1: 477 bayt hisobot.log.2: 477 bayt hisobot.log.3: 477 bayt oxirgi faylning birinchi qatori: 13:33:15 INFO qator 54 qayta ishlandi, natija tayyor
maxBytes ga yetganda fayl hisobot.log.1 ga ko'chiriladi va yangisi boshlanadi. backupCount=3
— eng ko'pi bilan 3 ta eski nusxa saqlanadi, qolgani o'chiriladi.
Istisnoni jurnalga yozish
import logging, sys logging.basicConfig(level=logging.ERROR, format="%(levelname)s %(message)s", stream=sys.stdout, force=True) log = logging.getLogger("istisno") def yiqiladi(): return 1 / 0 print("1) log.error — iz YO'Q:") try: yiqiladi() except ZeroDivisionError as e: log.error("hisob bajarilmadi: %s", e) print("\n2) log.exception — iz BOR (faqat except ichida ishlaydi):") try: yiqiladi() except ZeroDivisionError: log.exception("hisob bajarilmadi")
1) log.error — iz YO'Q: ERROR hisob bajarilmadi: division by zero 2) log.exception — iz BOR (faqat except ichida ishlaydi): ERROR hisob bajarilmadi Traceback (most recent call last): File "<kitob>", line 16, in <module> File "<kitob>", line 6, in yiqiladi ZeroDivisionError: division by zero
log.exception faqat except bloki ichida ishlatiladi va to'liq izni qo'shadi. Kutilmagan xatolar
uchun aynan shu kerak.
:::gotcha Jurnalga maxfiy ma'lumot yozmang
Parol, token, API kalit, karta raqami, shaxsiy ma'lumot — jurnalga tushmasligi kerak. Jurnal
fayllari ko'pincha boshqa tizimga yuboriladi, uzoq saqlanadi va ko'proq odamga ochiq bo'ladi.
Foydalanuvchi ma'lumotini yozish kerak bo'lsa, identifikator yozing (talaba_id=417), ism va
aloqa ma'lumotini emas.
:::
Testda jurnalni tekshirish
shu faylni yaratingtests/test_jurnal.py
import logging from hisobot.oqish import tozala def test_xato_bolsa_ogohlantiradi(caplog): qatorlar = [ {"ism": "A", "guruh": "X", "baho": "85", "sana": "2026-01-01"}, {"ism": "B", "guruh": "X", "baho": "abc", "sana": "2026-01-01"}, ] with caplog.at_level(logging.WARNING, logger="hisobot.oqish"): n = tozala(qatorlar) assert len(n.xatolar) == 1 assert "rad etildi" in caplog.text def test_hammasi_togri_bolsa_ogohlantirmaydi(caplog): qatorlar = [{"ism": "A", "guruh": "X", "baho": "85", "sana": "2026-01-01"}] with caplog.at_level(logging.WARNING, logger="hisobot.oqish"): tozala(qatorlar) assert caplog.text == ""
.venv/bin/python3 -m pytest tests/test_jurnal.py -v
============================= test session starts ============================== platform linux -- Python 3.10.12, pytest-9.1.1, pluggy-1.6.0 rootdir: /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish configfile: pyproject.toml plugins: cov-7.1.0 collected 2 items tests/test_jurnal.py .. [100%] ============================== 2 passed in 0.06s ===============================
caplog — pytest ning tayyor fixture i. U jurnal xabarlarini ushlaydi, shuning uchun
"ogohlantirish berildimi?" degan savolni sinash mumkin.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
print cheklovi |
O'chirib bo'lmaydi, manba va vaqt yo'q, faqat ekran |
getLogger(__name__) |
Har modulda; jurnalda manba ko'rinadi |
| Kutubxona sozlamaydi | basicConfig ni faqat dastur chaqiradi |
| Darajalar | Hamma narsani INFO qilmang — jurnal shovqinga aylanadi |
%s |
Formatlashni kechiktiradi; qimmat argumentga isEnabledFor |
RotatingFileHandler |
Fayl cheksiz o'smasin |
log.exception |
Faqat except ichida; to'liq izni qo'shadi |
| Maxfiylik | Parol, token, shaxsiy ma'lumot jurnalga tushmasin |
caplog |
Testda jurnalni tekshirish |
Talaba ishi
-vvvbersangiz nima bo'ladi?jurnalni_sozlabuni qanday qayta ishlaydi?hisobot.oqishuchunDEBUG, qolgan modullar uchunWARNINGqilib sozlang (logging.getLogger("hisobot.oqish").setLevel(...)). Chiqish qanday o'zgardi?- JSON formatida yozadigan
Formatteryozing (har qator — bitta JSON obyekt). Bu nima uchun foydali? caplogbilanlog.exceptionni sinang: izning matnda borligini tekshiring.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.