Hyödyllisten tietojen poimiminen lokitiedostoista on ratkaisevan tärkeää Java-sovelluksen onnistumisen kannalta. Lokitiedoista saat arvokasta tietoa sovelluksesi suorituskyvystä, vakaudesta ja käytettävyydestä.
Lokitietojen analysointi saattaa tuntua tylsältä, mutta sen ei tarvitse olla. Lokitietojen lukemiseen, jäsentämiseen ja yhdistämiseen on olemassa erilaisia työkaluja. Peruskomentorivityökalut, kuten grep, uniq ja sort, voivat yhdistää ja poimia lokitiedostoista hyödyllistä tietoa. Kehittyneemmät lokien jäsentäjät, kuten Logstash tai Fluentd, voivat poimia lokitiedostojen keskeiset tiedot helposti haettaviksi tunnisteiksi. Pilvipohjaiset lokipalvelut, kuten SolarWinds® Loggly®, tallentavat lokitietosi puolestasi ja tarjoavat kehittyneitä analyysiominaisuuksia, jolloin sinun ei tarvitse itse ylläpitää lokitietoja.
Tässä osiossa tutustutaan joihinkin menetelmiin ja työkaluihin, joiden avulla voit analysoida lokitietoja sovellustesi parantamiseksi.
- Yleisimpien poikkeusten löytäminen
- Poikkeusten löytäminen tyypin mukaan Grep:n avulla
- Poikkeusten löytäminen luokan mukaan Grep:n avulla
- Lokinhallintaratkaisun käyttäminen
- Tuotannon ongelmien vianmääritys
- Tietojen kerääminen ongelmasta
- Ongelman syyn tunnistaminen
- Resolve the Problem and Prevent It From Recurring
- Lisää tuotannon virheenkorjaustyökaluja
- jdb
- OverOps
- BTrace
- Chronon
- jhsdb
- Transaktioiden jäljittäminen
- Yksilöivien tunnisteiden jäljittäminen
- Metodikutsujen jäljittäminen
- Tracing-metodit Log4j:ssä
- Tracing Methods in SLF4J
- Muistin käytön hallinta
- Roskankeräys
- Muistivuodot
- Lisälähteet
- Katso se. Analysoi se. Tarkasta se. Solve it
Yleisimpien poikkeusten löytäminen
Yleisimpien poikkeusten löytäminen voi auttaa sinua paikallistamaan huonon suorituskyvyn alat Java-sovelluksessasi. Useimmat lokikehykset tallentavat poikkeuksen tyypin, poikkeusviestin ja metodin, jossa poikkeus tapahtui. Log4j:n avulla poikkeusten loki näyttää samankaltaiselta kuin jokin seuraavista.
09:54:44.565 ERROR Log4jTest.MathClass - java.lang.ArithmeticException: / by zero10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: myFile (No such file or directory)10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: newFile (No such file or directory)
Jos tulosteesi sisältää pinojälkiä, katso tämän oppaan Monirivisten pinojälkien jäsentäminen -osio.
Aloitetaan yksinkertaisella esimerkillä, jossa käytetään suosittua GNU-työkalua grep. Sitten näytetään, miten lokien hallintatyökalu voi tehdä siitä vieläkin helpompaa.
Poikkeusten löytäminen tyypin mukaan Grep:n avulla
Seuraavalla Unix-komennolla etsitään poikkeuksia, poimitaan poikkeustyyppi ja lasketaan esiintymien määrä. grep on suosittu komentorivityökalu, joka suorittaa hahmontäsmäytyksen, kun taas uniq ryhmittelee ja sort lajittelee tulokset vastaavasti:
$ grep -o "\w*Exception" myLog.log | sort -r | uniq -c
Tämässä esimerkeissämme säännöllinen lauseke \w*Exception
täsmää mihin tahansa sanaan, joka loppuu sanaan ”Exception”. Lipuke -o
käskee grep:tä tulostamaan vain ne osat tulosteesta, jotka vastaavat hakusanaa. sort -r
lajittelee tuloksen käänteisessä järjestyksessä, kun taas uniq -c
ryhmittelee tulokset ja laskee esiintymien määrän. Tuloksena saamme laskennan poikkeuksista tyypeittäin.
2 FileNotFoundException1 ArithmeticException
Voidaan myös käyttää grep:iä etsimään lokista kutakin tiettyä tapausta näistä poikkeuksista.
$ grep ArithmeticException myLog.log09:54:44.565 ERROR Log4jTest.Log4jTest - java.lang.ArithmeticException: / by zero
Tämä palauttaa jokaisen rivin, joka sisältää hakujonon ArithmeticException
instanssin, ja itse hakujono on korostettu tulosteessa.
Poikkeusten löytäminen luokan mukaan Grep:n avulla
Voidaan etsiä poikkeuksia myös sen luokan mukaan, jossa ne esiintyivät. Yksirivisten lokitapahtumien osalta seuraava komento ryhmittelee poikkeusten määrän luokan ja tyypin mukaan. grep poimii luokan nimen etsimällä sanaa, jota seuraa tietty merkki (tässä esimerkissä käytetään väliviivaa, vaikka mitä tahansa merkkiä voidaan käyttää, kunhan se on merkinnälle yksilöllinen). Vaikka tämä merkki ei ole välttämätön, se auttaa meitä löytämään luokan nimen lokitapahtumasta. OR-operaattorilla (merkintä |) grep poimii myös poikkeuksen nimen etsimällä sanaa, joka sisältää merkkijonon ”Exception”.
sed on toinen komentorivin apuohjelma, jota voidaan käyttää grepin tulosteiden muotoiluun. Tässä esimerkissä sed poistaa tulosteestamme erikoismerkin sekä kaikki rivinvaihtomerkit. Tämän jälkeen tuloste ohjataan putkella uniq- ja sort-komennoille tulosten ryhmittelyä ja lajittelua varten.
$ grep -o "w* -|\w*Exception" myLog.log | sed 'N; s/ -n/ /' | sort -r | uniq -c
Tulos tuo meidät lähemmäs keskeisiä ongelma-alueita:
2 Log4jTest FileNotFoundException1 MathClass ArithmeticException
Näillä tuloksilla voimme käyttää grep:iä etsiessämme tarkempia yksityiskohtia tietyissä luokissa esiintyvistä ongelmista. Esimerkiksi seuraava komento hakee luokassa MathClass
esiintyneet poikkeukset.
$ grep -e "MathClass.*Exception" myLog.log09:54:44.565 ERROR Log4jtest.MathClass - java.lang.ArithmeticException: / by zero
Lokinhallintaratkaisun käyttäminen
Useimmat lokinhallintaratkaisut tarjoavat tapoja ryhmitellä ja hakea lokimerkintöjä lokityypin, viestin sisällön, luokan, metodin ja säikeen perusteella. Jos lokit jo jäsennetään ja tallennetaan, monet ratkaisut pystyvät kuvaamaan ja lajittelemaan poikkeuksia esiintymien lukumäärän mukaan. Logglyssä tämä on osoita ja napsauta -toiminto, joten sinun ei tarvitse opetella ulkoa monimutkaisia grep-komentoja. Loggly myös indeksoi lokitietueet, mikä tekee etsinnöistä ja laskennoista paljon nopeampia kuin grep. Voimme esimerkiksi käyttää kenttäselainta nähdäksemme luettelon poikkeuksista ja niiden esiintymistiheydestä ja sitten klikata poikkeusta nähdäksemme kaikki asiaankuuluvat lokitiedot.
Logglyn kenttäselaimen käyttäminen lokitietojen nopeaan etsimiseen poikkeustyypin mukaan.
Loggly tarjoaa myös visualisointityökaluja, jotka voivat olla informatiivisempia kuin grepin tekstipohjainen tuloste. Tämänkaltaiset graafit voivat auttaa priorisoimaan resursseja kehityssprinttejä tai uuden julkaisun jälkeisiä korjauksia varten.
Poikkeusten kartoittaminen taajuuden mukaan Logglyssä.
Tuotannon ongelmien vianmääritys
Tuotannon ongelmien yhteydessä aika on ratkaisevaa. Kriittinen virhe sovelluksessasi ei jätä käyttäjiäsi vain tyytymättömiksi, vaan se myös laskee myyntiä ja vähentää luottamusta sovellukseen tai palveluun.
Useimmissa tapauksissa ongelman ratkaiseminen voidaan jakaa kolmeen keskeiseen vaiheeseen:
- Tietojen kerääminen ongelmasta
- Ongelman syyn tunnistaminen
- Ratkaisun löytäminen ja ongelman toistumisen estäminen
Tietojen kerääminen ongelmasta
Ensimmäisessä vaiheessa kerätään tietoja ongelmasta. Kerää mahdollisimman paljon tietoa – kuvakaappauksia, kaatumisraportteja, lokitietoja, linkkejä (verkkopalvelujen osalta) jne. – auttaaksesi rajaamaan mahdolliset syyt. Haluat, että ongelman kokenut käyttäjä antaa yksityiskohtaisia tietoja tapahtumasta, mukaan lukien: milloin ja missä kohtaa ohjelmaa ongelma ilmeni, hänen toimensa, jotka johtivat ongelmaan, käyttöympäristö ja ohjelman outo tai epätavallinen käyttäytyminen ennen ja jälkeen ongelman ilmenemisen.
Sen jälkeen voit aloittaa lokitietojen keräämisen. Jos sovelluksesi on isännöity palvelu, aloita lokien hakeminen verkko- ja sovelluspalvelimilta. Jos sovelluksesi on hajautettu ohjelmistopaketti, pyydä käyttäjää sisällyttämään lokitiedot vikailmoitukseensa. Vaihtoehtoisesti, jos sovelluksesi lähettää lokitapahtumia keskitetylle lokipalvelimelle, lokitiedot ovat heti saatavilla.
Kun sinulla on kohtuullinen määrä tietoa ongelmasta, voit aloittaa sen jäljittämisen koodissa.
Ongelman syyn tunnistaminen
Kerättyään tietoa ongelmasta seuraava vaihe on sen syyn tunnistaminen. Virheen toistaminen kehitysympäristössä on yksi helpoimmista tavoista vahvistaa sen olemassaolo, mutta se voi olla aikaa vievää eikä välttämättä toimi kaikissa tapauksissa. Kun sinulla on perusteelliset lokitiedot, pääset suoraan ongelman alkulähteelle ja säästät aikaa ja turhautumista.
Vikaraportti antaa sinulle yleiskäsityksen siitä, mikä ongelma on ja missä se ilmeni. Käyttämällä valitsemaasi lokienhallintatyökalua voit rajata haun pienempään joukkoon lokimerkintöjä etsimällä yksilöllistä tietomerkkiä, kuten käyttäjänimeä, istunnon tunnusta tai viestin tekstiä.
Käydäänpä läpi esimerkkiskenaario havainnollistaaksemme, miten järjestelmän vianmääritys tehdään. Kuvitellaan, että meillä on verkkopohjainen käyttöliittymä, jonka avulla kirjaudutaan etänä verkkosivustolle. Verkkosivulla on kirjautumisnäyttö, joka suorittaa perustodennuksen käyttäjätunnuksen ja salasanan avulla. Käyttäjät ovat ilmoittaneet, etteivät he voi kirjautua verkkosivustolle. Verkkosivu hyväksyy heidän syöttönsä, mutta epäonnistuu sen jälkeen yleisellä virheilmoituksella.
Esimerkki verkkosivustosta, joka raportoi virheestä sisäänkirjautumisyrityksen jälkeen.
Tämä viesti ei anna paljon muuta tietoa kuin yleisluonteisen merkinnän lokin vakavuudesta. Jos lokihallinnassa etsitään merkintöjä, joiden tasossa tai viestissä on ”Severe”, tuloksena voi olla satoja tuloksia, eikä ole mitään takeita siitä, että yksikään niistä liittyisi käsillä olevaan ongelmaan. Onneksi Logger
kirjasimme myös virheen kokeneen käyttäjän käyttäjätunnuksen, joten voimme suodattaa ”admin”-käyttäjätunnuksen perusteella.
Javapoikkeuksen tarkasteleminen Loggly-ohjelmassa.
Jos porautumme tarkemmin alaspäin, näemme, että virheen syynä on puuttuva tai virheellinen taulukko. Tämä on vakava ongelma, sillä se voi viitata tahattomaan poistoon tai tietokannan korruptoitumiseen.
ERROR: Exception for user admincom.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test_schema.users' doesn't exist...com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1651) at TestApplication.Test.doPost(Test.java:33) at javax.servlet.http.HttpServlet.service(HttpServlet.java:646) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at...
Katsellessamme pinojälkeä näemme, että palvelu epäonnistui Test.java-tiedoston rivillä 33. Tämä rivi koostuu SQL-lauseesta, joka hakee tietoja käyttäjästä test_schema.users-taulusta.
rs = stmt.executeQuery("SELECT * FROM test_schema.users WHERE username = " + username + " AND Password = " + password);
Kun yritämme suorittaa tämän kyselyn SQL-etuliitteessä, huomaamme, että ”test_schema.users”-taulua ei ole olemassa. Tietokannassa on kuitenkin taulukko ”test_schema.user”. Jossain vaiheessa kehittäjä on saattanut vahingossa kirjoittaa väärän taulukon nimen ja siirtää tarkistetun lomakkeen tuotantoon. Nyt tiedämme, mikä ongelma on ja missä se esiintyy koodissamme.
Käyttämällä SolarWinds Logglyä voit debugata ja löytää ongelmien perimmäisen syyn jäljittämällä virheet sovelluspinon läpi, useiden lokitapahtumien kautta ja jopa paikallistamalla asiaankuuluvan koodirivin GitHubissa.
Resolve the Problem and Prevent It From Recurring
Nyt kun olemme tunnistaneet ongelman ja sen syyn, viimeinen askel on korjata se. Kirjautumisesimerkkimme oli liioiteltu tapaus, johon oli suhteellisen helppo ratkaisu, mutta saatat törmätä monimutkaisempiin vikoihin, joiden juuret ovat sovelluksesi eri osa-alueilla. Ennen kuin hyppäät nopeaan ja likaiseen korjaukseen, harkitse huolellisesti, miten muutoksesi vaikuttaa sovellukseen. Onko tämä todella paras ratkaisu ongelmaan? Onko mahdollista, että muutoksesi vaikuttaa johonkin toiseen komponenttiin? Vaikeuttaako tämä korjaus uusien korjausten tai ominaisuuksien käyttöönottoa myöhemmin? Estääkö tämä korjaus myös samankaltaisten ongelmien esiintymisen myöhemmin?
Entä jos esimerkiksi kaksi eri käyttäjää onnistuu luomaan kaksi erillistä tiliä samalla käyttäjätunnuksella ja salasanalla? Voisit ottaa käyttöön yksilöllisen käyttäjätunnuksen kaikille käyttäjille, mutta miten se vaikuttaisi tietokantarakenteeseen, todennuskoodiin ja olemassa olevaan käyttäjäkantaan? Voisit lisätä uuden yksilöllisen kentän, kuten sähköpostiosoitteen, ja tehdä siitä pakollisen, mutta entä jos jotkut nykyisistä käyttäjistäsi eivät ole antaneet sähköpostiosoitetta? Entä jos jotkut sähköpostiosoitteet on jaettu useille tileille? Miten nämä uudet säännöt vaikuttavat rekisteröinti-, haku- ja hallintasivuihin?
Tulee myös ottaa huomioon viive korjauksen luomisen ja käyttöönoton välillä. Yritysympäristössä koodisi on tarkistettava muiden kehittäjien toimesta, integroitava koodikantaan, rakennettava, testattava laadunvarmistuksessa, porrastettava ja ehkä käytävä läpi useita muita vaiheita, ennen kuin se päätyy tuotantoon. Yrityksessäsi saattaa olla erityisiä protokollia kriittisiä tai ajallisesti arkaluonteisia virheitä varten, joita sinun on noudatettava. Kun olet löytänyt ratkaisun, sisällytä ratkaisusi dokumentaatioosi sekä mahdolliset järkevät kiertotiet ongelman ratkaisemiseksi. Näin asiakkaasi voivat edelleen käyttää tuotetta, ja tukitiimisi voi vähentää päällekkäisten vikailmoitusten määrää, kun korjaus etenee tuotantoon.
Korjauksen käyttöönoton jälkeen jatka tuotantosovelluksen seurantaa varmistaaksesi, että ongelma on ratkaistu. Esimerkiksi yllä olevan esimerkin tietokantaongelman korjauksen käyttöönoton jälkeen ei pitäisi enää näkyä tapahtumia, joissa lukee ”Table ’test_schema.users’ doesn’t exist”. Jos virheet jatkuvat tai jos alamme nähdä uusia virheitä, tiedämme, että korjaus ei toiminut. Ihannetapauksessa näemme seuraavan kuvakaappauksen kaltaisen kuvion, jossa virheiden määrä putoaa nollaan heti korjauksen käyttöönoton jälkeen.
Kaavio virheitä sisältävien lokien määrästä Logglyssä.
Lisää tuotannon virheenkorjaustyökaluja
Vaikka lokien kirjaaminen on hyväksi havaittu menetelmä sovellusten vianmääritykseen ja virheenkorjaukseen, nämä työkalut voivat auttaa saamaan enemmän tietoa sovelluksen toiminnasta.
jdb
jdb, Java Debugger, on komentorivin apuohjelma Java-luokkien virheenkorjaukseen. jdb on helppokäyttöinen, ja se toimitetaan Java Development Kitin mukana. Käyttämällä jdb:tä luodaan uusi Java-virtuaalikone (JVM), jolloin voit debugata luokan vaikuttamatta käynnissä oleviin ohjelmiin. Voit käyttää jdb:tä myös käynnissä olevien sovellusten debuggaamiseen lisäämällä java-komentoon seuraavat parametrit:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n
Kun JVM käynnistyy, se määrittää porttinumeron saapuvia jdb-yhteyksiä varten. Tämän jälkeen voit liittyä käynnissä olevaan JVM-instanssiin komennolla jdb -attach:
$ jdb -attach
Voit käyttää tätä esimerkiksi debuggerin liittämiseen käynnissä olevaan tuotantoinstanssiin. Ole varovainen taukopisteiden käytön kanssa tässä skenaariossa, koska se voi pysäyttää aktiivisen säikeen. Tällä voi olla seurauksia, jos esimerkiksi asiakas käyttää sovellusta sillä aikaa, kun debuggaat sitä. Lisätietoja on Java-dokumentaatiossa osoitteessa jdb.
OverOps
OverOps on joukko työkaluja sovellusten valvontaan, koodin analysointiin ja ongelmien havaitsemiseen. Toisin kuin lokiohjelmistot, jotka luottavat käynnissä olevan sovelluksen tuottamiin tulosteisiin, OverOps kytkeytyy suoraan Java-virtuaalikoneeseen kartoittaakseen sovelluksen koodikannan, lukeakseen muuttujia ja tallentaakseen virheitä. Näin se pystyy havaitsemaan enemmän virheitä ja tallentamaan enemmän tietoja kuin jopa sovelluksen kirjauskehys. OverOps käyttää SaaS-mallia (Software as a Service), jossa metriikat kerätään ja tallennetaan OverOpsin pilvipalvelimille. Voit kuitenkin ottaa sen käyttöön myös toimitiloissa. Kummassakin tapauksessa voit tarkastella tietojasi verkkopohjaisen käyttöliittymän avulla.
BTrace
BTrace on jäljitystyökalu, jonka avulla voit seurata sovelluksesi kaikkia osa-alueita luokkien nimistä virheisiin. BTrace käyttää aspektipainotteista ohjelmointitapaa, johon kuuluu annotaatioiden käyttö, jotka määrittelevät, missä ja miten BTrace valvoo sovellustasi. Esimerkiksi seuraava BTrace-skripti valvoo ja kirjaa jokaisen javax.swing
-paketin kutsun.
import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*; @BTrace public class AllMethods { @OnMethod( clazz="/javax\.swing\..*/", method="/.*/" ) public static void m(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { print(Strings.strcat("entered ", probeClass)); println(Strings.strcat(".", probeMethod)); }}
Lisätietoja BTrace:stä on BTrace GitHub-tietovarastossa ja Wikissä.
Chronon
Chronon avulla voit kelata ja toistaa sovelluksen koko suoritusvirran. Se tallentaa jokaisen yksittäisen muutoksen, joka on tehty sovelluksen elinkaaren aikana, jolloin voit toistaa sovelluksen tilan milloin tahansa. Tallenteet tallennetaan tiedostoon, joten suoritushistoria on helppo siirtää tuotantokoneelta kehityskoneelle testausta varten.
Chronon koostuu Chronon-tallennuspalvelimesta (Chronon Recording Server), jonka avulla voit tallentaa Java-sovelluksia etänä, sulautetusta Chrononista (Embedded Chronon), joka sulauttaa tallentimen sovelluksen sisälle, ja Time Travelling Debuggerista (Time Travelling Debugger -aikainen virheenkorjausohjelma), jonka avulla voit toistaa tallenteita.
jhsdb
jhsdb (Java HotSpot Debugger) on sekä OpenJDK:n että Oraclen JDK:n mukanaan tuoman JVM:n oletusarvoista virheenkorjausta, analysointia ja profilointia varten tarkoitettu työkalupaketti. jhsdb:n avulla voit liittyä käynnissä oleviin Java-prosesseihin, ottaa tilannekuvia pinojäljistä ja jopa analysoida kaatuneita JVM:iä. Sen avulla voit käyttää kasaa, koodin välimuistia, roskienkeruutilastoja ja paljon muuta. Lisätietoja saat jhsdb:n dokumentointisivulta.
Transaktioiden jäljittäminen
Kun ongelma ilmenee, on tärkeää tietää, mistä ongelma alkoi ja miten se vaikutti muuhun sovellukseen. Tämä on tarpeeksi vaikeaa monoliittisessa sovelluksessa, mutta vielä vaikeampaa se on hajautetussa palvelukeskeisessä arkkitehtuurissa, jossa yksi pyyntö voi osua kymmeniin palveluihin. Aina ei ole selvää, mikä palvelu sisältää virheen perimmäisen syyn tai miten se vaikutti muihin palveluihin. Jäljitys tarjoaa tiedot, joita tarvitaan sovelluksen suorituspolun seuraamiseen ja ongelman tarkan syyn selvittämiseen.
Oppaan Tuotanto-ongelmien vianmääritys -osiossa kävimme läpi esimerkin, jossa käyttäjällä oli vaikeuksia kirjautua verkkosovellukseen virheellisen tietokantataulukon vuoksi. Tässä osassa näytämme, miten tapahtumien jäljittäminen oli keskeisessä asemassa ongelman ratkaisemisessa.
Yksilöivien tunnisteiden jäljittäminen
Jotta voit jäljittää lokitapahtumien sarjan, tarvitset jonkin tavan tunnistaa toisiinsa liittyvät lokitapahtumat yksiselitteisesti. Monen käyttäjän ympäristössä voi syntyä satoja identtisiä lokeja, jolloin haku aikaleiman tai Logger
perusteella on vaikeaa. Helpompi ratkaisu on sisällyttää toisiinsa liittyvien lokimerkintöjen yhteyteen yksilöllinen tunniste. Tämä tunniste voi olla käyttäjänimi, istuntotunniste, API-avain tai UUID (Universally Unique Identifier). Onneksi ThreadContext soveltuu täydellisesti tähän tehtävään.
Tuotannon ongelmien vianmääritys -esimerkissä meillä oli web-pohjainen käyttöliittymä, jota isännöitiin Tomcat-servletissä, joka oli yhteydessä MySQL-tietokantaan. Käyttäjät syöttivät tunnistetietonsa verkkosivulle, ja kun he painoivat lähetä, servletti suoritti kyselyn, jossa heidän tunnistetietojaan verrattiin tietokantaan tallennettuihin tietoihin. Jos käyttäjän tunnistautuminen onnistui, hänet ohjattiin pääsivulle. Jos tapahtui virhe, virheen yksityiskohdat kirjattiin lokiin ja käyttäjille esitettiin yleinen viesti.
Pystyimme korjaamaan tämän ongelman sisällyttämällä käyttäjien käyttäjätunnukset lokiviestiin. Näin pystyimme nopeasti etsimään admin-käyttäjään liittyviä lokitapahtumia. Tämän saman esimerkin pohjalta voimme käyttää ThreadContext.put()
:ää käyttäjätunnuksen liittämiseen Logger
:ään. Kun käyttäjät lähettävät tunnuksensa, servletti siirtyy doPost()
-metodiin, joka lisää käyttäjien käyttäjätunnukset ThreadContextiin:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {ThreadContext.put("username", request.getParameter("username"));logger.info("Entering doPost().");...}
Käyttämällä Log4j:tä mallilla %p : %m%n
saadaan seuraava merkintä:
INFO : Entering doPost().
Voidaan lisätä samanlaisia tapahtumia MySQL-tietokannan käyttämiseen, doPost-metodin jättämiseen ja muiden toimintojen suorittamiseen. Tällä tavoin, jos käyttäjä laukaisee poikkeuksen, tiedämme tarkalleen, mitä käyttäjä teki, kun poikkeus tapahtui.
Metodikutsujen jäljittäminen
Monet lokikehykset tarjoavat natiiveja menetelmiä sovelluksen suorituspolun jäljittämiseen. Nämä menetelmät vaihtelevat hieman eri kehysten välillä, mutta noudattavat samaa yleistä muotoa.
-
traceEntry()
merkitsee metodin alkua. -
traceExit()
merkitsee metodin loppua. Metodeissa, jotka palauttavat objektin, voit samanaikaisesti palauttaa objektin ja kirjata tapahtumanreturn logger.exit(object)
:llä. -
throwing()
merkitsee poikkeuksen, jota ei todennäköisesti käsitellä, kutenRuntimeException
. -
catching()
merkitsee poikkeuksen, jota ei heitetä uudelleen.
Kehyskohtaista tietoa näistä metodeista löydät Log4j:n, Logbackin ja java.util.logging
:n Loggerin dokumentoinnista.
Tracing-metodit Log4j:ssä
Esimerkkinä Log4j:n käytöstä korvaamme servletissämme olevat Logger.info()-metodit tracing-metodeilla.
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { logger.entry(); ... logger.exit();}
Muutamme myös Appender's PatternLayout
:n näyttämään luokan nimen, metodin ja rivinumeron (muunnosmallit %class
, %M
ja %line
).
<PatternLayout pattern="%p %class %M %line: %m%n" />
Tracing-metodit kirjaavat tapahtumat TRACE
-tasolla, mikä tarkoittaa, että meidän on muutettava Logger's
-taso DEBUG
:stä TRACE
:ksi. Muuten lokiviestit tukahdutetaan.
<Loggers> <Root level="trace"> <AppenderRef ref="Console"/> </Root></Loggers> TRACE DatabaseApplication.Login doPost 26: entry...TRACE DatabaseApplication.Login doPost 59: exit
Tracing-metodit tarjoavat myös oman Markers
. Esimerkiksi metodit Logger.entry()
ja Logger.exit()
näyttävät vastaavasti ENTER
ja EXIT
.
ENTER 14:47:41.074 TRACE DatabaseApplication.Login - EnterEXIT 14:47:41.251 TRACE DatabaseApplication.Login - Exit
Tracing Methods in SLF4J
SLF4J-käyttäjät voivat hyödyntää MDC:tä lokitapahtumien jäljittämisessä. Log4j:n tapaan MDC-arvoja voidaan käyttää Appenderin kanssa käyttäen %X-muunnosmallia.
SLF4J tarjoaa myös entry()
-, exit()
-, throwing()
– ja catching()
-metodit XLogger
-luokan (Extended Logger) kautta. Voit luoda XLogger
-instanssin käyttämällä XLoggerFactory.getXLogger()
.
package DatabaseApplication;import org.slf4j.Logger;import org.slf4j.LoggerFactory; import org.slf4j.ext.XLogger;import org.slf4j.ext.XLoggerFactory; public class Login extends HTTPServlet { final static XLogger xlogger = XLoggerFactory.getXLogger(Login.class.getName()); final static Logger logger = LoggerFactory.getLogger(Login.class.getName()); @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { xlogger.entry(); .. xlogger.exit(); }}
Lisää logback.xml-tiedostoon %class- ja %line-muunnosmallit.
<configuration> <appender name="Console" class="ch.qos.Logback.core.ConsoleAppender"> <encoder> <pattern>%-5level %class{36} %M %L: %msg%xEx%n</pattern> </encoder> </appender> <root level="trace"> ... </root></configuration>
Tuloksena syntyvät lokimerkinnät ovat identtisiä Log4j:n luomien lokimerkintöjen kanssa.
Muistin käytön hallinta
Muistinhallinta jätetään usein huomiotta ylemmän tason kielissä, kuten Javassa. Vaikka muistin keskimääräinen määrä nykyaikaisissa laitteissa kasvaa, suurella muistin käytöllä voi olla suuri vaikutus sovelluksen vakauteen ja suorituskykyyn. Kun Java-virtuaalikone ei enää pysty varaamaan muistia käyttöjärjestelmästä, ohjelmasi voi keskeytyä ja kaatua. Kun osaat hallita muistia, vältät ongelmat sovelluksesi koon kasvaessa.
Kuvitellaan esimerkiksi, että haluaisimme tallentaa joukon numeroita, jotka alkavat ykkösestä. Käyttäjä antaa ylärajan ja ohjelma tallentaa jokaisen kokonaisluvun ”1:stä” tähän rajaan. Käytämme while-silmukkaa, jossa on laskuri, joka lisää jokaisen luvun matriisiin.
import java.util.Scanner;...System.out.print("Please enter the maximum size of the array: ");Scanner scanner = new Scanner(System.in);int limit = scanner.nextInt(); ArrayList intArray = new ArrayList(); int count = 1;while (count <= limit) { intArray.add(count);}
Olet ehkä huomannut, että count
ei kasva silmukassa. Tämä on suuri ongelma; jos käyttäjä syöttää minkä tahansa numeron, joka on suurempi kuin nolla, matriisi jatkaa kasvamistaan, kunnes JVM käyttää kaiken käytettävissä olevan muistinsa ja ohjelma kaatuu.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:2245)at java.util.Arrays.copyOf(Arrays.java:2219)at java.util.ArrayList.grow(ArrayList.java:242)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)at java.util.ArrayList.add(ArrayList.java:440)at MemoryTest.main(MemoryTest.java:10)
Roskankeräys
Muistinkäytön vähentämiseksi Java-virtuaalikone suorittaa ajoittaisen siivousprosessin, joka tunnetaan nimellä garbage collection. Roskienkeräys etsii muistilohkoja, joita ei enää käytetä, ja asettaa ne uudelleen käytettäväksi. Seuraavissa lähteissä kerrotaan tarkemmin Javan roskienkeruuprosessista.
- Java Garbage Collection Basics (Oracle)
- HotSpot Virtual Machine Garbage Collection Tuning Guide (Oracle)
- Understanding G1 GC Logs (Poonam Bajaj)
- Garbage Collection Tuning Guide (Atlassian)
Garbage Collector tuottaa diagnostista tietoa, joka voi olla hyödyksi sovellusten suorituskykyprofiilien laadinnassa. Voit kirjata nämä tiedot lokiin välittämällä -Xlog:gc JVM:lle sovellusta käynnistettäessä. Jokaisen ajon jälkeen roskienkerääjä tulostaa tilastoja roskienkeruuprosessista Unified JVM Logging -muodossa. Tässä on esimerkki.
Using G1 Periodic GC disabled GC(0) Pause Young (Normal) (G1 Evacuation Pause) 7M->1M(64M) 8.450ms
Using G1
kertoo, mitä roskienkeräysmenetelmää käytetään. Garbage-First (G1) -keräysmenetelmä on usein oletusarvoisesti käytössä moniprosessoritietokoneissa, joissa on paljon RAM-muistia. Periodic GC disabled
kertoo, että roskienkeräysprosessi ei toistu. Kolmas rivi kertoo, että kyseessä on evakuointitauko, jossa objekteja kopioidaan muistialueiden välillä sen perusteella, ovatko ne vielä käytössä. Pause Young
kertoo, että prosessi puhdisti nuoren sukupolven, jossa uudet objektit allokoidaan. Tämän seurauksena nuoren sukupolven objektien kokonaismuistikäyttö laski 7M:stä 1M:ään 64M:stä allokoidusta 64M:stä, ja prosessi vei 8.450ms
.
Kun roskienkerääjä tulostaa diagnostiikkatietoja konsoliin, näemme, miten allokoidun muistin määrä (kasan koko) jatkaa kasvuaan ajan myötä. Poistamme CPU-ajat yksinkertaisuuden vuoksi.
$ java -Xlog:gc MemoryLeakDemo Using G1 Periodic GC disabled GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 25M->23M(64M) 4.372ms GC(1) Concurrent Cycle GC(1) Pause Remark 50M->34M(64M) 0.250ms GC(1) Pause Cleanup 94M->94M(124M) 0.057ms GC(1) Concurrent Cycle 169.695ms GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 94M->94M(124M) 3.738ms GC(3) Concurrent Cycle...
Kaavio, josta näkyy JVM:n kasan käytön asteittainen kasvu ajan mittaan.
Voit lisätä jokaiseen merkintään aikaleimat -XX:+PrintGCDateStamps
ja -XX:+PrintGCTimeStamps
. Voit myös kirjata roskienkerääjän tulosteet tiedostoon välittämällä-Xlog:gc:file
: JVM:lle sovellusta käynnistäessäsi.
Jos seuraat lokitiedostojasi rsyslogin avulla, voit välittää lokit keskitettyyn lokijärjestelmään, jossa ne jäsennetään ja ovat valmiita lähes reaaliaikaiseen analysointiin. Tältä näyttää aikajanan kuvaaja Logglyssä. Se näyttää kasan kokonaiskoon sekä kasan koon ennen ja jälkeen roskienkerääjän suorituksen.
Muistivuodot
Muistivuoto on tilanne, jossa ohjelma varaa muistia nopeammin kuin se voi vapauttaa sitä. Helpoin tapa havaita muistivuoto on, kun ohjelma ei reagoi, muuttuu epävakaaksi tai aiheuttaa OutOfMemoryErrors
. Javassa löydät useampia Full GC
-kokoelmien tapauksia, kun muistin käyttö lisääntyy.
Lisätietoa roskienkerääjän tuloksista ja muistivuotojen paikallistamisesta saat Java-dokumentaation luvussa Muistivuotojen vianmääritys.
Lisälähteet
Leikkaa virheenkorjausaikaa kirjoittamatta ainoatakaan riviä koodia (Loggly)-opas Takipin käyttämiseen
Päällimmäinen opas: 5 Methods for Debugging Production Servers at Scale (High Scalability)-Työkaluja ja tekniikoita tuotanto-ongelmien vianmääritykseen
Muistinhallinta
Garbage Collection Tuning Guide (Oracle)-Ohje Javan roskienkeräyksen ymmärtämiseen ja hienosäätöön
Javan roskienkeräyksen ymmärtäminen (CUBRID blogi)-Ohje garbin käyttämiseen
Katso se. Analysoi se. Tarkasta se. Solve it
See what matters.
START FREE TRIAL