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.
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.