Cea mai folosită metodă de logging a evenimentelor pentru depanarea (debugging) codului în JavaScript este prin apelarea "console.log(mesaj)". Aceasta are ca efect afișarea mesajului în consola pentru dezvoltatori care folosesc browser-ul. Se mai pot folosi "console.warn(mesaj)" și "console.error(mesaj)" pentru înregistrarea avertismentelor, respectiv a erorilor.

Obiectul "console" este un obiect host (gazdă), ceea ce înseamnă că el este oferit de către mediul în care rulează script-ul JavaScript. Deoarece este un obiect gazdă, el nu este descris în nicio specificație. De aceea, implementările pot să difere sau în cazul Internet Explorer 7 să lipsească cu desăvârșire.

Dacă există această funcționalitate în majoritatea browser-elor, de ce ar mai fi nevoie de o bibliotecă pentru același scop? Pentru a realiza diverse funcții suplimentare cum ar fi căutarea, filtrarea și formatarea mesajelor.

Proiectul la care lucrez este o aplicație web cu partea de server scrisă în Java şi cea de client în JavaScript. Clientul pentru care am dezvoltat această soluție nu ne putea oferi log-urile de pe server într-un timp rezonabil. Pentru a putea rezolva defectele care apăreau cât mai repede, s-a implementat pe partea de server un mecanism prin care atunci când se arunca o excepție, se colectau informații despre tabelele implicate și împreună cu stack trace-ul Java erau arhivate și trimise către browser pentru a putea fi descărcate on the fly de către client. Acesta le putea trimite ulterior nouă pentru a investiga și remedia problema.

În ceaa ce privește partea de front-end, pentru investigarea unei probleme semnalată de către client,dar pe care noi n-o puteam reproduce, am apelat la conexiuni desktop la distanță, am instalat Firebug și am urmărit consola oferită de acesta.

Pentru a îmbunătăți procesul şi a face mai facilă raportarea defectelor ne-am gândit să replicăm și pe partea de client funcționalitățile mecanismului de pe server. Așa s-a născut "Logger", o bibliotecă JavaScript pentru înregistrarea evenimentelor. Ceea ce o diferențiază faţă de alte biblioteci asemănătoare este posibilitatea de salvare a evenimentelor atât în consola pentru dezvoltatori cât și în spațiul local de stocare al browser-ului (local storage) precum și posibilitatea exportului log-urilor într-un fișier text.

Câteva cuvinte despre cum se folosește librăria. Includerea librăriei este foarte simplă, realizându-se cu o singură linie:

""

Există două moduri prin care se pot înregistra evenimente:

1. Folosind Logger.log(nivel, mesaj, locație)

>Logger.log(Logger.level.Error, "mesaj de eroare", Logger.location.All);
[ERROR]> mesaj de eroare

Deoarece am folosit ca locație "All", mesajul va fi salvat și în spațiul local de stocare al browser-ului.

>localStorage;
Storage {1393948583410_ERROR: "mesaj de eroare", length: 1}

2. Folosind metode specifice pentru fiecare eveniment: Logger.error(mesaj, locație), Logger.warn(mesaj, locație), Logger.info(mesaj, locație)

>Logger.error("al doilea mesaj de eroare", Logger.location.All);
[ERROR]> al doilea mesaj de eroare 
Logger.warn("atentionare", Logger.location.All);
[WARN]> atentionare 
Logger.info("informare", Logger.location.All);
[INFO]> informare  

Fiecare eveniment este salvat în local storage sub forma de cheie și valoare. Cheia este formată din "timestamp_nivel", iar valorea conține mesajul.

>Logger.getEvents(Logger.level.All);
[ERROR]> mesaj de eroare
[ERROR]> al doilea mesaj de eroare
[WARN]> atentionare
[INFO]> informare

În continuare voi prezenta funcționalitatea care, după părerea noastră, va face programatorii mai productivi și anume exportarea tuturor mesajelor înregistrate în spațiul local de stocare al browser-ului într-un fișier text. Astfel programatorii pot avea acces mai rapid la mesajele și evenimentele petrecute în timpul rulării aplicației.

Exportul înregistrărilor în fișierul text se poate face apelând Logger.exportLog() sau setând o funcție de callback în cazul apariției unei erori în aplicație:

"Logger.onError(exportLog, suppressErrorAlerts, errorCallback)"

Dacă "exportLog" este setat pe true, atunci eroarea apărută precum și toate înregistrările prezente în spațiul de stocare al browser-ului vor fi salvate într-un fișier text. În cazul în care "suppressErrorAlerts" este setat pe true, eroarea apărută nu va mai fi afișată și în consola browser-ului. "errorCallback" reprezintă funcția ce va fi executată în cazul apariției unei erori.

Următoarea secvență de cod va afișa un mesaj de dialog în momentul în care apare o eroare de JavaScript.

Logger.onError(true, true, function(errorMsg, url, lineNumber) {

var errorMessage = errorMsg + " la linia " + 
lineNumber + " in " + url,	raspuns = "";

Logger.error(errorMessage, 
	      Logger.location.LocalStorage);

raspuns = confirm("A aparut o eroare. Doriti sa 
salvati inregistrarile intr-un fisier text?");

	if (raspuns === true) {
		Logger.exportLog();
	}
	return true;//suppress errors on console
});

Putem testa dacă acest cod funcționează prin adăugarea unui buton la al cărui eveniment onClick() vom arunca o eroare:

""

La apăsarea butonului vom primi următorul mesaj:

Apăsarea butonului "OK", în funcție de setările browser-ului va salva înregistrările într-un fișier în locația prestabilită sau va afișa un dialog de salvare cu numele fișierului format din data și ora curentă.

Dacă deschidem fișierul, vom vedea toate mesajele precum și eroarea de JavaScript.

[ERROR]> mesaj de eroare
[ERROR]> al doilea mesaj de eroare
[WARN]> atentionare
[INFO]> informare
[ERROR]> Uncaught Error: eroare. la linia 19 in http://localhost:8080/logger/main.html

Putem observa că eroarea a apărut în fișierul "main.html" la linia 19, locul în care am creat butonul la a cărui apăsare se aruncă eroarea JavaScript:

Biblioteca folosește şablonul de proiectare "Revealing Module" pentru a expune funcțiile ce pot fi apelate de către dezvoltatori.

Cea mai simplă soluție pentru a scrie datele din local storage într-un fișier era folosirea unei biblioteci JavaScript numită "FileSaver.js" ce folosește API-ul FileSystem sau furnizează o alternativă la acesta în browser-ele care nu-l suportă nativ. Am vrut să evit folosirea de biblioteci suplimentare și astfel am recurs la o soluție alternativă.

saveToDisk: function(content, filename) {
 var a = document.createElement("a"),

 blob = new Blob([content], {"type" : "
                 application/octet-stream"});
 
 a.href = window.URL.createObjectURL(blob);
 a.download = filename;
 a.click();
}

Am creat un element ancoră la a cărui atribut href am asociat un obiect URL creat dintr-un blob. Blob-ul reprezintă un obiect immutable (imuabil) ce conține date brute. Numele fișierului care este format din data și ora curentă sunt asociate atributului download,apoi se apelează funcția click() ce va avea ca efect afișarea dialogului de salvare al browser-ului.

Pentru a intercepta erorile care apar în JavaScript am creat inițial o variabilă initialWindowErrorHandler ce conține o referință la window.onerror. Inițial, window.onerror scrie toate mesajele de eroare în consolă. În funcția onError() dacă parametrul errorCallback nu este nedefinit atunci se va executa funcția primită ca parametru la fiecare eroare JavaScript.

saveToDisk: function(content, filename) {
 var a = document.createElement("a"),
 blob = new Blob([content], {"type" : 
		"application/octet-stream"});
 
 a.href = window.URL.createObjectURL(blob);
 a.download = filename;
 a.click();
}

Se poate reveni oricând la comportamentul default al lui window.onerror prin apelarea Logger. resetWindowErrorHandler().

Aceasta este versiunea inițială a Logger-ului, fără funcționalități avansate. În viitor ar putea fi îmbunătățit prin adăugarea unei limite maxime a numărului de înregistrări, posibilitatea de a șterge anumite înregistrări după tipul evenimentului, filtrarea înregistrărilor exportate după tipul acestora, realizarea unei interfețe grafice pentru vizualizarea înregistrărilor existente și posibilitatea generării de statistici pentru a determina cât de stabilă a fost aplicația pe o anumită perioadă de timp, posibilitatea de a seta numele fișierului la o valoare ce poate fi configurată.

Referințe:

Mozilla Developer Network

1. Create Object URL: https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL

2. Blob: https://developer.mozilla.org/en-US/docs/Web/API/Blob

3. File System API: https://developer.mozilla.org/en-US/docs/WebGuide/API/

4. Window.onerror: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers.onerror

Github

5. FileSaver.js: https://github.com/eligrey/FileSaver.js

JSFiddle

6. Save to disk example: http://jsfiddle.net/koldev/cW7W5/

7. Revealing Module Pattern: carldanley.com/js-revealing-module-pattern/