1. Cox println — hele loglama deyil
Tetbiq kicik olanda ele gelir ki, her yerde sadəcə println("men burdayam"), println("indi burdayam"), println("indi artiq burda deyiləm"). Isleyir axı! Amma bir anda klassika baslayir: qeribe bir bagi tutursunuz, proqrami isledirsiniz, “men burdayam” kimi on setr gorursunuz ve… hansinin vacib oldugunu ve prosesin hansi ardicilliqla getdiyini anlamirsiniz.
Burada konsola cixisin iki novunu ayirmag faydalidir. Birincisi — istifadeci ucun cixis: ipuclar, komandalarin neticeleri, “Ededi daxil edin”, “Elave olundu”, “Silindi” kimi mesajlar. Ikincisi — proqramin isinin texniki izi: ne etmeye qerar verdi, hansI logic budaqlarini secdi, hansi melumatlari emal etdi, hansi xeberdarliqlari sezdi. Məhz ikinci nov loglamadir.
Ve vacib fikir: loglar “gozel” ve ya “dostcasina” olmali deyil. Onlarin vezifesi — sabit, vahid ve gozle axtaris ucun rahat olmaqdir.
Loglarin idarə olunan olmasi ucun bizə iki sey lazimdir: ehemiyyet seviyyeleri ve vahid format.
2. Log seviyyeleri ve minimal seviye
Proqrami insan kimi tesevvur etsək, loglar onun gunluyu kimidir. Amma gunluk hissler haqqinda yox, “ne etdim ve niye” haqqindadir. Ve her gunlukde oldugu kimi, her qeyd eyni derecede vacib deyil.
Bu leksiyada biz dord klassik seviyye daxil edirik:
| Seviyye | Insani dille mena | Tipik hallar |
|---|---|---|
|
“Developer ucun detallar” | araliq deyerler, budaqlanmalar, emalin teferruati |
|
“Proqramin normal heyati” | start/stop, komanda emal olundu, emeliyyat ugurla icra olundu |
|
“Sübhəli bir sey var, amma davam edirik” | qeribe daxil etmə, melumatin atlanmasi, cox ideal olmayan amma dusus olmadan durum |
|
“Senari ugursuz oldu” | emeliyyat xetasi, exception, senarini davam etdirmek mumkun deyil |
Seviyyelerin esas praktiki menasi — bele demek imkani: “Mene yalniz xeberdarliqlari ve xetalari goster, detallar gizli qalsin”. Bu, loglamanin minimal seviyyasi adlanir (minLevel): log yalniz o zaman cap olunur ki, onun seviyyesi verilmis seviyeden asagi olmasin.
3. Minimal Logger: seviyye, format ve filtrasiya
LogLevel kimi enum class
Seviyyeleri setrlerle teyin edende ("WARN", "ERROR"), ozumuz-ozumuze tələ qururuq: "WARM", "ERORR", "warning" — compiler buraxacaq, siz isə sonra “niye filtrasiya bəzən isleyir” sualina cavab axtaracaqsiniz.
Ona gore de log seviyyeleri enum class ucun ideal namizeddir: bu, compiler-in nezaret etdiyi sabit variantlar toplusudur.
enum class LogLevel {
DEBUG, INFO, WARN, ERROR
}
Burada rahat olan odur ki, enum elementleri elan edildiyi ardicilliqda saxlayir ve her elementin ordinal-i var (0, 1, 2, 3…). Bu ardicilliqdan seviyeleri muqayise etmek ucun istifade etmek olar: seviyye nə qeder “saga” dogrudursa, o qeder ciddidir.
Dərhal gələcək ucun kicik xeberdarliq (panika yoxdur): sonradan enum class daxilinde seviyelerin yerini dəyişsəniz, ordinal ile filtrasiya menasi da dəyişəcək. Tədris mini-logger çərçivəsində bu normaldır, amma yadinda saxlamaq faydalidir.
Vahid log formati
Loglar “kim ne bacarirsa” stilində cap olunanda, onlari oxumaq cetin olur. Biri "[INFO] started" yazir, basqasi — "INFO: start", ucuncusu — "start ok". Neticede hatta gozle de harda ne oldugunu anlamaq cetinlesir.
Minimal faydali format (artiq “yasamaq” ucun kifayet eden) belədir:
timestamp [LEVEL] message
Niyə timestamp faydalidir? Cunki o, hadiselerin ardicilliğini verir ve fasilələri heç olmasa kobudca qiymətləndirməyə imkan yaradir. Niyə sadə System.currentTimeMillis() gotururuk? Cunki o, dərhal əlçatandir, tarixin “gozel” formatlaşdırilmasi olmadan; indi isə bizə gozəllikdən çox intizam lazimdir. Gozəlliyi sonra asanliqla əlavə etmək olar, amma loglari vahid usulla yazmaq vərdişi — bir az daha cetindir.
Log setrini formatlamaq ucun kicik funksiya yazaq. Onun ayrı funksiya olmasi vacib deyil, amma bele oxumaq daha asandir.
fun formatLogLine(ts: Long, level: LogLevel, message: String): String {
return "$ts [$level] $message"
}
Əgər formatLogLine(123, LogLevel.WARN, "Suspicious input") cagirsaniz, proqnozlasdirila bilən setr alinacaq:
123 [WARN] Suspicious input
Və məhz bu “proqnozlasdirila bilən” — açar sözdür.
Logger sinfi: filtrasiya ve cap etmə bir yerdə
Indi her seyi Logger sinfinde yigaq. Onun ideyasi cox sadədir: uc tapsirigi bir yerde hell etmek.
Birinci tapsiriq: cari minLevel ile bu seviyye mesajini cap etmeye icaze varmı.
class Logger(private val minLevel: LogLevel) {
private fun enabled(level: LogLevel): Boolean {
return level.ordinal >= minLevel.ordinal
}
}
Ikinci tapsiriq: icaze varsa — vahid formata gore setri yigmaq ve cap etmek.
class Logger(private val minLevel: LogLevel) {
private fun enabled(level: LogLevel): Boolean =
level.ordinal >= minLevel.ordinal
fun log(level: LogLevel, message: String) {
if (!enabled(level)) return
val ts = System.currentTimeMillis()
println("$ts [$level] $message")
}
}
Stilə diqqət edin: biz guard clause istifade edirik — “olmursa, dərhal cix”. Bu, kodu daha birbasa edir: her seyi if icine yigmaga ehtiyac qalmir.
Indi minLevel-in nece islediyini hiss etmek ucun kicik test elave edek:
fun main() {
val logger = Logger(minLevel = LogLevel.WARN)
logger.log(LogLevel.DEBUG, "Details") // cap olunmayacaq
logger.log(LogLevel.INFO, "Started") // cap olunmayacaq
logger.log(LogLevel.WARN, "Odd input") // cap olunacaq
logger.log(LogLevel.ERROR, "Failed") // cap olunacaq
}
Cixis təxminən bele olacaq (sizde timestamp, əlbəttə, fərqli olacaq):
1700000000000 [WARN] Odd input
1700000000001 [ERROR] Failed
Və bu, “kod boyu səpələnmiş yuzlərlə println” üzərində böyük bir qələbədir.
debug/info/warn/error metodlari
Her defe logger.log(LogLevel.INFO, "...") yazmaq tez bezdirir. Bundan elave, kod daha pis oxunur: goz daim tekrarlanan LogLevel. hissəsinə “ilişir”.
Buna gore logger-lerde demək olar ki, həmişə wrapper metodlar edirlər.
class Logger(private val minLevel: LogLevel) {
private fun enabled(level: LogLevel): Boolean =
level.ordinal >= minLevel.ordinal
fun log(level: LogLevel, message: String) {
if (!enabled(level)) return
val ts = System.currentTimeMillis()
println("$ts [$level] $message")
}
fun debug(message: String) = log(LogLevel.DEBUG, message)
fun info(message: String) = log(LogLevel.INFO, message)
fun warn(message: String) = log(LogLevel.WARN, message)
fun error(message: String) = log(LogLevel.ERROR, message)
}
Indi kod prosesin ne oldugunu danisan hekayeye benzemeye baslayir:
fun main() {
val logger = Logger(LogLevel.INFO)
logger.info("App started") // cap olunacaq
logger.debug("x=10, y=20") // cap olunmayacaq
}
Məhz bu filtr ucun her seyi baslamisdiq: debug — “qazmaq” ucun, info — normal heyat ucun.
Istifadeci cixisi ve loglar
“İstifadeciye mesajlar” ve “loglar”i tesadufen bir birinə qatmaq cox asandir. Konsol tetbiqlərində bu xüsusilə hiyləgərdir: həm biri, həm digəri — println.
Sadə bir qaydada razilasdiriq: istifadeci cixisi adi println olaraq qalir, texniki hadiseler ise Logger ile gedir.
Təsəvvur edək ki, bizdə “qeyd elave et” və “siyahini goster” kimi ssenariləri olan tədris CLI tetbiqi var (siz kursda buna bənzərini etmisiniz). Domain modelini murəkkəbləşdirməyəcəyik — bizə məhz mesaj axini vacibdir.
fun handleAdd(rawAmount: String, logger: Logger) {
logger.info("Handle add started")
val amount = rawAmount.toIntOrNull()
if (amount == null) {
println("Xəta: məbləğ rəqəm olmalıdır") // istifadeciye mesaj
logger.warn("Add failed: amount is not a number: '$rawAmount'")
return
}
println("Elave olundu: $amount") // istifadeciye mesaj
logger.info("Add finished: amount=$amount")
}
Fərqi tona görə görürsünüz. İstifadeci mesajina “handle started” bilmək lazım deyil. Ona qısa şəkildə vacib olan budur: nə səhvdir və ya nə uğurlu alındı. Loglara isə “quru” və texniki olmaq normaldır: onlar istifadeci üçün deyil, sizin (və gələcəkdə nə nəzərdə tutduğunuzu xatırlayacaq “gələcək siz” üçün) lazımdır.
Mini-senarini yoxlayaq:
fun main() {
val logger = Logger(LogLevel.INFO)
println("Məbləği daxil edin:")
val raw = readln()
handleAdd(raw, logger)
}
Əgər istifadeci abc daxil etsə, cixis təxminən belə görünəcək:
Məbləği daxil edin:
abc
1700000000000 [INFO] Handle add started
Xəta: məbləğ rəqəm olmalıdır
1700000000001 [WARN] Add failed: amount is not a number: 'abc'
Bəli, istifadeci həm logu, həm də mesaji gorəcək. Real tetbiqlərdə loglari çox vaxt ayrıca axina (meselen, stderr) və ya fayla göndərirlər — amma bu artıq başqa hekayədir. Indi isə bizə mənanı ayırmağı öyrənmək vacibdir.
ordinal əvəzinə prioritet
Biz artıq ordinal istifade edirik və bu, duzgun isleyir. Amma bir incəlik var: ordinal enum daxilinde elan ardicilligina baglidir. Əgər komandada kimsə “gəlin ERROR-u birinci edək, çünki ən vacibidir” desə — filtrasiya qirilacaq.
Davranisin daha sabit olmasi ucun aciq prioriteti rəqəmlə vermək olar. Hələ də sadədir, amma artıq bir az daha “boyuk” yanaşmadir.
enum class LogLevel(val priority: Int) {
DEBUG(10),
INFO(20),
WARN(30),
ERROR(40)
}
Ve onda yoxlama ardicilliqdan asili olmur:
class Logger(private val minLevel: LogLevel) {
private fun enabled(level: LogLevel): Boolean =
level.priority >= minLevel.priority
fun log(level: LogLevel, message: String) {
if (!enabled(level)) return
val ts = System.currentTimeMillis()
println("$ts [$level] $message")
}
fun debug(message: String) = log(LogLevel.DEBUG, message)
fun info(message: String) = log(LogLevel.INFO, message)
fun warn(message: String) = log(LogLevel.WARN, message)
fun error(message: String) = log(LogLevel.ERROR, message)
}
Siz ordinal-i saxlaya bilərsiniz (tədris layihəsi üçün bu normaldır), amma nə baş verdiyini anlamaq — faydalidir.
Is prinsipi sxemi
Bəzən bir dəfə gözlə görmək, mətni on dəfə oxumaqdan daha asandir. Loglamada bizim üçün vacib olan odur ki, cap edib-etməmək qerari bir yerde cəmlənir.
flowchart TD
A["Tetbiq kodu
logger.info(...)"] --> B["Logger.enabled(level)"]
B -->|false| C["Heç nə cap etmirik"]
B -->|true| D["Setri formatlasdiririq
ts + [LEVEL] + message"]
D --> E["println(...) bir yerde"]
Əgər bu intizama alışsanız, çox xoş bir “superguc” qazanacaqsiniz: log formatini dəyişmək və ya yeni detallar əlavə etmək istəyəndə, bunu bir yerde edəcəksiniz; layihə boyu println("DEBUG") axtarışı ilə qaçmayacaqsiniz.
4. Tipik sehvler
Səhv № 1: bütün mesajları INFO-ya çevirmək, çünki “belə daha asandir”.
Bu həqiqətən daha asandir… bir axşamlığa. Sonra isə sizdə milyonlarla INFO olacaq və onların arasında vacib bir xeberdarliq və ya xəta gizlənəcək. Seviyyələr gozəllik ucun yox, səs-küyü tənzimləmək ucun düşünülib: DEBUG — detallar, INFO — normal hadisələr, WARN — qəribəliklər, ERROR — əməliyyatın ugursuzlugu.
Səhv № 2: log mesajlarini fərqli yerlərdə fərqli formada formatlamaq.
Kodun bir hissəsi "ts=$ts level=$level msg=$message" yazanda, digər hissə isə "$level: $message" yazanda, loglari gozle “skan etmək” mümkün olmur. Düzgün yanaşma — setr formatı Logger daxilində yaşamalıdır, xarici kod isə yalnız mənalı mesaji verməlidir.
Səhv № 3: istifadeci cixisini ve loglamani elə qarışdırmaq ki, istifadeciyə “daxili detallar” gosterilsin.
Istifadeci mesajı “mən nə etməliyəm/nə baş verdi” sualina cavab verməlidir, log isə “proqram nə edirdi” sualina. Əgər insan üçün interfeysde HandleAdd started; parsing raw=... cap etsəniz, istifadeci ozunu istəmədən tester kimi hiss edəcək. UX üçün println-i, diaqnostika üçün isə logger.*-i saxlayın.
Səhv № 4: filtrasiya qaydasini qeyri-aydın etmək.
Əgər logger bəzən cap edir, bəzən etmir və qayda “bu gun çərşənbə axşamıdırsa və setr boş deyilsə — cap et” kimidirsə, siz loglara inanmağı dayandıracaqsiniz. Bazis səviyyədə qayda sadə və tək olmalıdır: cap edirik, əgər level >= minLevel.
Səhv № 5: seviyeler ucun enum class yerinə setrlərdən istifadə etmək.
Setrlər ilk yazı səhvinə qədər rahatdır. enum class sizə type-safety verir: compiler “sol” bir seviyye verməyə icazə verməyəcək. Bu elə haldır ki, sərt tiplər həyatı çətinləşdirmir — əksinə, asanlaşdırır.
Səhv № 6: ordinal-in enum-da dəyərlərin sırasından asılı olduğunu unutmaq.
Dörd seviyyeniz var ve onlar duzgun ardicilliqda elandirsa, hər şey yaxşıdır. Amma sıranı dəyişsəniz, filtrasiya da onunla birlikdə dəyişəcək. Bir az daha sabitlik istəsəniz, aciq priority verin və ona görə müqayisə edin.
Səhv № 7: gələcəkdə ola biləcək bütün loglama tapşırıqlarını indi həll etməyə çalışmaq.
Bu yerdə “gozel tarix”, fayla yazmaq, kontekst əlavə etmək, stack trace-lər, metrikalar və daha on sey etməyə başlamaq çox cazibədardır. Amma onda sadə və işlək logger yerinə “ağrı konstruktoru” alınır. Minimal logger ona görə yaxşıdır ki, onu tez tetbiq edib dərhal fayda ala bilərsiniz: seviyeler, filtrasiya, vahid format.
GO TO FULL VERSION