Asosiy qismga o'tish

Jurnal (logging): print dan chiqish

9-bob 10 kod bloki 2483 so'z

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

python10 qator
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")
Chiqish
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:

python9 qator
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 ---")
Chiqish
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

python57 qator
"""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

python46 qator
"""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:

python8 qator
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
Chiqish (qaytish kodi 1, 785 ms)
=== 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

python28 qator
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)")
Chiqish
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:

python26 qator
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)
Chiqish
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

python18 qator
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")
Chiqish
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

python21 qator
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 == ""
matn1 qator
.venv/bin/python3 -m pytest tests/test_jurnal.py -v
Chiqish (qaytish kodi 0, 1835 ms)
============================= 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

  1. -vvv bersangiz nima bo'ladi? jurnalni_sozla buni qanday qayta ishlaydi?
  2. hisobot.oqish uchun DEBUG, qolgan modullar uchun WARNING qilib sozlang (logging.getLogger("hisobot.oqish").setLevel(...)). Chiqish qanday o'zgardi?
  3. JSON formatida yozadigan Formatter yozing (har qator — bitta JSON obyekt). Bu nima uchun foydali?
  4. caplog bilan log.exception ni sinang: izning matnda borligini tekshiring.

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar