Asosiy qismga o'tish

Jurnal (logging): print dan chiqish

9-bob 10 kod bloki 2481 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.

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