ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 74
Abonament PDF

De ce ai scrie propria abstracție de logging?

Cristian Botiza
Product Architect @ Itiviti



PROGRAMARE


Dacă nu ai scris aplicații de la zero (ceea ce nu avem des ocazia), foarte probabil că ai observat un șablon: aproape fiecare proiect și companie are propria abstracție de logging (logare): un adaptor (wrapper) peste third-party-ul care face logarea sau chiar o implementare alternativă. De asemenea, dacă scrii o aplicație de la zero, te vei confrunta cu această decizie: să folosești direct interfața (API) furnizată de frameworkul de logare preferat sau să îl îmbraci în ceva ce ți s-ar potrivi mai bine în opinia ta?

Cam în toate firmele în care am lucrat, am observat că aveau deja un adaptor propriu de logare. Am început să mă întreb de ce această tendință. Ce avantaje avem dacă procedăm aşa? Merită efortul (costul)? Întrebare delicată, în condițiile în care majoritatea ne pricepem foarte bine să folosim un log4j, slf4j/logback sau commons-logging. Tendința de a le folosi direct este extrem de naturală. Probabil că mulți dintre noi am configurat în detaliu logback sau log4j, poate chiar am scris propriile log destinations (appenders), și am vrea să punem abilitățile dobândite în folosul proiectului (și al nostru, nu?). Dacă începi proiectul de la zero, întrebarea e cu atât mai importantă, pentru că o decizie nepotrivită te poate costa ulterior. Sigur, orice se poate repara, dar nu cred că îți dorești să primești plângeri de la clienți și să nu găsești nimic în logurile furnizate (pentru care uneori trebuie să duci lupte serioase) pe motiv că nu ai configurat bine logback, de exemplu. În plus, recent au apărut și variante care promovează renunțarea la loguri, de exemplu https://www.overops.com/. Un motiv în plus pentru a te decide pe ce rută vrei să mergi: loghezi folosind API-ul frameworkului preferat (sau impus deja de proiect); îți scrii propria abstracție de logare sau pur și simplu nu loghezi, folosind (și) alte mijloace de diagnosticare a problemelor?

Presupunând că nu dorești să renunți la loguri, voi încerca să răspund la întrebarea enunțată inițial: de ce ai scrie propria abstracție de logare. Sub nici o formă nu voi încerca să vă 'vând' un framework de logare în defavoarea altuia, deși personal eu sunt un mare fan logback. Aceasta poate fi o discuție separată, altădată.

Problema si opțiunile

Logarea este una dintre principalele metode de diagnosticare a problemelor ce pot apărea când sistemul rulează în producție. Ca atare e important să fie făcută bine, adică să îndeplinească minimum următoarele:

Există și alte calități dezirabile la un framework de logare , dar cele enunțate mai sus ar reprezenta în opinia mea un minim necesar (nu neapărat și suficient).

Cât privește opțiunile, poți lua în considerare programarea orientată pe aspecte (AOP). Cam în orice tutorial, exemplul tipic de aspect este chiar logarea! Aceasta ar avea avantajul că nu trebuie să scrii nici o linie de cod dedicată logării, lăsând totul în seama frameworkului care îți va injecta (weave) aspectul în codul aplicației. Deși sună promițător, în practică nu este aşa de simplu. Trebuie în primul rând să știi ce metode vor reprezenta pointcuturile de interes pentru logare, lucru nu tocmai obișnuit în aplicații multi-layered sau care rulează în servere de aplicație. Apoi, ce ai putea să incluzi în liniile de log care să se potrivească pe cazul general? Dacă semnăturile nu respectă un pattern bine stabilit ești limitat la a loga valorile parametrilor. Sau, dacă ești ambițios, poți adăuga suport pentru logarea excepțiilor care apar (cu un joinpoint de tip after-throwing). Sigur, dacă faci aceasta doar la nivel de subsistem și toate clasele respectă un anumit șablon, poți obține o oarecare decuplare și codul în acel subsistem rămâne nepoluat cu instrucțiuni specifice logării. Dar trebuie să te ocupi de procesul de weave-ing, cu cele două opțiuni: compile sau runtime weaving. Dacă din diverse motive ești nevoit să recurgi la runtime weaving, apar alte complicații: de exemplu: interferența aspectelor în mediile de tip application server. Astfel că, deși sună promițător, a folosi AOP pentru logare nu este chiar banal; mai mult, eu nu știu nici o aplicație care să fi mers în direcția aceasta (deși este perfect posibil și poate fi un exercițiu foarte bun când înveți AOP).

Presupunând că nu folosești AOP, de ce să apelezi la o abstracție de logare proprie? Ce probleme ai rezolva mai bine decât frameworkul de logare preferat?

Inițializarea controlată

Aplicațiile (Java) moderne nu rulează standalone ci în diverse medii controlate, de tip container / application server. Ceea ce înseamnă, printre altele, că îți va fi furnizat un class loader de către container. Să ne amintim că majoritatea frameworkurilor de logare se bazează pe un bloc de inițializare static, adică pe o rutină de inițializare ce se execută când respectiva clasă core (Logger, LogManager, …) este încărcată. Când se încarcă acea clasă? De obicei, la execuția unui bloc de cod care face referință la ea. De exemplu: când este întâlnită prima declarație de obiect Logger în aplicație (în alt bloc static, de data aceasta în clasele tale). Apoi, în mediile de tip application server class loaderele sunt ierarhice și e foarte posibil ca în ierarhie să existe mai mult de o versiune a claselor ce compun frameworkul de logare. În funcție de ce class loader ajunge să definească respectiva clasă (the defining class loader, în termeni specifici) poți avea surpriza apariției unei configurații neașteptate. Adică este important de subliniat că procesul de inițializare nu este 100% sub controlul tău, ca application developer. Abordarea bazată pe blocuri de cod statice are un mic avantaj: mașina virtuală (JVM) îți garantează execuția blocurilor de inițializare statice înainte de a te lăsa să instanțiezi clasa respectivă; prin urmare, în momentul când chiar ajungi să loghezi poți fi sigur că blocurile de inițializare ale claselor cheie din framework s-au executat cu succes. (Dacă ar cauza erori, aplicația nu ar funcționa, întrucât nu ai avea clasele necesare definite.) Dar ceea ce nu ți se poate garanta este ce class loader va încărca respectivele clase și de asemenea ce resurse (fișiere de configurare) vor fi disponibile în ierarhia acelui class loader.

Dacă ai scrie un adaptor pentru logare, ai putea să implementezi tu inițializarea frameworkului de logare folosit într-o manieră mai predictibilă. Ai putea să faci:

E foarte important ca inițializările necesare să se întâmple la momentul potrivit, pentru a nu avea surprize. Un caz particular ar fi mediile OSGI sau în general, containerele. Poți să furnizezi, pentru compatibilitate, și o componentă (în termeni OSGI) sau conector (JCA) care să inițializeze framework-ul de logare înainte de a fi utilizat.

Adaptabilitate

Frameworkurile de logare moderne (log4j 2, logback, ...) sunt rezultatul unui continuu proces de rafinare și perfecționare, astfel încât e puțin probabil să vrei să scrii un framework alternativ. Este destul de improbabil să vrei să renunți la un framework cu care te-ai obișnuit și să îl înlocuiești pur și simplu. Dar cum aceste frameworkuri evoluează și își îmbunătățesc performanțele de la release la release, poți ajunge în punctul în care o schimbare să fie necesară în beneficiul proiectului. Dacă este să alegi dintre opțiuni, ce preferi: să modifici integrarea cu frameworkul în abstracția ta de logare, probabil izolată într-o bibliotecă in-house și în proiect doar să incluzi noua versiune a bibliotecii (ideal fără nici o modificare în cod!) sau să modifici linii de logging calls și inițializarea frameworkului spre care migrezi? Sigur că și varianta a doua poate fi parțial automatizată la nevoie, dar eu unul aș alege prima variantă.

Monitorizare și configurare unificată

Dacă ai scris propria abstracție de logare, poți expune abilități de monitorizare (JMX de exemplu) care să nu fie specifice unui framework de logare sau altul, ci adaptate peste nevoile tale (ale companiei). În esență, exact unul dintre beneficiile unui adaptor (sau fațade). Implementarea acestor operații (JMX calls, web services, ...) va fi localizată în biblioteca pe care o scrii, independent de codul aplicației.

De asemenea, poți să implementezi abilități de reconfigurare dinamică, independente, la nivel de API, de specificitățile frameworkului de logare ales. În implementare vei avea grijă să faci conversiile necesare între adaptorul tău și frameworkul target. Orice modificare în frameworkul respectiv (inclusiv upgrades) sau migrarea la alt framework va fi îngrădită doar la biblioteca pe care o scrii, fără să afecteze codul aplicațiilor care o folosesc. Este important să ai grijă să expui doar API-ul tău de monitorizare, preferabil dezactivându-le pe cele oferite în plus de către framework ( de exemplu, logback are suport pentru JMX). Expunând în paralel și operațiile logback și pe acelea scrise de tine, poți cauza confuzie și chiar probleme.

Configurații standardizate

În enterprise-uri de dimensiuni medii și mari (și nu numai) logurile sunt centralizate și păstrate pe servere specializate, fiind șterse după un timp, de pe mașinile unde sunt generate (prin procesul de logshipping). Pentru a ușura procesul de centralizare și investigațiile bazate pe loguri e preferabil să ai configurații unitare, cel puțin în privința următoarelor aspecte:

Acestea fiind zise, ajută mult dacă împachetezi în abstracția ta de logare o configurație default (sau mai multe) pe care să le pasezi automat către framework când se inițializează. Vei face utilizatorii foarte fericiți, pentru că au asigurată corectitudinea formatului și a configurațiilor predefinite în raport cu standardele companiei și cerințele toolurilor utilizate pentru procesarea logurilor. Un use case: folosești stiva ELK (ElasticSearch, LogStash, Kibana) pentru a analiza logurile. Dacă toate liniile de log respectă același format, va fi ușor și să îl ai preconfigurat în soluția ELK aleasă. În plus, această configurare va fi centralizată și nu vei lăsa utilizatorilor luxul de a greși și de a nu respecta formatul.

Un caz special: redirectarea standard output streams. Oricâtă grijă ai avea, nu ai control 100% peste ce fac bibliotecile third party utilizate. Așadar e posibil ca să apară, de te miri unde, să ai linii care ajung pe consolă (fie calluri directe System.out fie ascunse bine într-un log4j / logback ConsoleAppender, să zicem). Dacă ai făcut deploy pe Amazon sau pe o mașină unde nu ai consola standard, aceste mesaje se vor pierde, cel mai probabil. Așadar, ar fi preferabil ca și ele să fie standardizate, adică supuse acelorași reguli (rotație, restricții de dimensiuni, format unificat pe cât posibil). Ai putea în abstracția de logare pe care o scrii să implementezi redirectarea output streams către fișiere al căror nume și locație să fie configurabile, la fel ca pentru restul logurilor.

Ajutor în testarea automată

Când scrii unit teste vei exercita (vrei nu vrei) și cod care loghează. Cum majoritatea testelor nu au ca scop testarea logurilor, ai vrea să ai opțiunea să dezactivezi logarea în teste (sau alte configurații). Sigur, poți utiliza mocking, dar va trebui să fii atent la metodele statice sau finale - poate să devină complicat. Nu ar fi mai simplu dacă în adaptorul tău, ai oferi posibilitatea de a substitui să zicem un Log Factory care dezactivează complet logurile, sau le redirectează pur și simplu către consolă (care e salvată de Jenkins sau alte tooluri de continuous integration)? Şi apoi în Setup-ul suitei de teste să scrii maxim o linie care instruiește adaptorul de logare să folosească acest stub? Atunci nu mai trebuie sa te ocupi de convențiile fiecărui framework de logare specific ( de exemplu, în logback poți crea un fișier logback-test.xml în src/test/resources și el va avea prioritate, fiind citit prima dată de către logback).

Considerații finale

Subiectele atinse aici merită probabil tratate separat în câte un articol. Din păcate, aici nu avem spațiu să abordăm aspectele tehnice aferente fiecărui subiect (class loading, execuție privilegiată, redirectare output streams, logshipping, correlation ids, repackaging, …).

Am vrut în esență doar să arăt că merita să te gândești la acest aspect (adaptor de logare propriu) când începi un proiect, chiar dacă apoi alegi din diverse motive varianta mai simplă - folosirea directă a unui framework de logging. Aș vrea să reiau avantajele enunțate mai sus: controlul mai bun asupra inițializării, configurații standardizate, monitorizare unificată, adaptabilitate la schimbări ulterioare. Ce e important (și din păcate, am observat că asta se încalcă cel mai adesea) e să alegi o abordare sau alta și să o folosești consecvent: degeaba ai un wrapper sofisticat, dacă apoi folosești în paralel și logback sau log4j (sau uneori mai multe frameworkuri de logare în paralel). Revii practic la problema inițială, lipsa de control asupra inițializării, numai că acum ai de administrat un utilitar în plus. Prin urmare, una sau alta: wrapperul tău sau un third-party direct. Nu ambele.

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects