El Blog de Trespams

Blog personal sobre tecnologia, gestió de projectes i coses que se me passen pel cap

Visor de logs amb Django

L'altra dia em vaig trobar amb la necessitat de mostrar els logs de l'aplicació a la plana web que estava desenvolupant.

Per anar bé, els logs han de ser dinàmics, és a dir, la plana ha de refrescar-se segons apareguin noves línies de log (i això vol dir Ajax). La idea és tenir quelcom com el tail -n 100 f ... del Linux. Per la naturalesa de les peticions Http, no podem arribar a tenir la funcionalitat del tail, però podem atracar-nos-hi un poc. Aquest article explica com ho podem fer utilitzant Django, Python i jQuery.

El javascript

La part del navegador ha de refrescar-se automàticament i sols el tros que correspongui a la visualització dels logs. A cada refresc haurà de cridar al servidor, obtenir la informació del log i presentar-la.

Per aconseguir això utilitzarem Jquery i un afegitó anomenat Timers. Aquest plugin ens permet definir de manera una funció que s'executarà cada x milisegons fins que l'aturem o tanquem el navegador.

El temps de refresc dependrà de la nostra aplicació, i el truc està en donar-li un temps un poc menor que la velocitat a la que la nostra aplicació genera les línies de log. No fa falta mirar-s'hi gaire, sols és per donar una major sensació d'actualització en temps real i que es mostri la informació línia a línia. Si li donam un temps major farem menys peticions i la presentació del log potser es faci per blocs de línies.

També necessitarem fer una cridada Ajax al servidor per a que ens doni les línies de log. Això ho podem fer directament amb jQuery, amb la funció jQuery.ajax per exemple. Veurem que a més del text del log necessitam la darrera posició llegida, així que farem servir el getJSon, que ens permetrà obtenir la informació de manera més estructurada.

El servidor

Per simular el tail farem un poc de trampa. La primera vegada que es faci la petició ens situarem al final del fitxer de log i la segona ja enviarem la informació. És a dir, mostram al informació a partir de la segona vegada que es crida la funció. Com que el temps es suposa que és petit no té massa importància i simplifica molt la programació. Hem de tenir en compte que l'obtenció de log ha de ser ràpida.

Per a posicionar-nos farem servir la funció seek i tell ens donarà la posició resultant una vegada llegit el fitxer. Aquesta posició és la que anirem passant a cada petició, de manera que quan tornem a llegir el log, ho farem a partir de la darrera posició llegida. A l'exemple a més he fet servir la funció reverse per tal d'ordenar la llista de línies llegides de més nova a més antiga i simular l'scroll.

from django.http import HttpResponse
from django.utils import simplejson


def _tail(f, actual=0):
""" Returns a tuple with the actual position on the file and the last read lines in html format. """
text = ""
if actual == 0:
f.seek(0, 2)
else:
f.seek(actual)
log = f.readlines()
if log:
log.reverse()
text = "
".join(log)
return f.tell(), text


def tail(request):
"Tail simulation"
f = open('sample.log', 'rU')
pos = int(request.GET['pos'])
pos, msg = _tail(f, actual=pos)
f.close()
data = {'msg': msg, 'pos': pos}
return HttpResponse(simplejson.dumps(data))

Fixem-nos que el que tornam és una estructura json, creada a partir d'un diccionar Python mitjançant el simplejson.

A més consumim la posició del fitxer des de la que hem de començar a llegir, que vindrà donada pel paràmetre get. No he posat validacions ni tractament d'excepcions, ho deix com a exercici per al lector (això sempre queda bé dir-ho quan un no té massa ganes de fer feina :-P )

Amb el que ara veim del codi de servidor, el codi javascript ja és entenidor:

<
script type = "text/javascript" >
$(function() {
var pos = 0;
var demos = $("div.wrapper div.demos");
var active = false;
$('#log').ajaxStart(function() {
$('#loading').show()
});
$('#log').ajaxStop(function() {
$('#loading').hide()
});
$('.controlled-interval', demos).find('.start').css("cursor", "pointer").click(function() {
if (!active) {
active = !active;
$('#log').everyTime(1000, 'controlled', function() {
$.getJSON("/log/tail/?pos=" + pos, function(data) {
if (data.msg != '') {
$("#log").prepend(data.msg + '
');
}
pos = data.pos;
})
});
}
}).end().find('.stop').css("cursor", "pointer").click(function() {
if (active) {
active = !active;
$(this).parents("div").find('#log').stopTime('controlled');
}
});
}); <
/script>

Qui fa la feina és $('#log').everyTime(1000, 'controlled', function() qui es que manté el timer. Cada vegada que passa un segon (1000 ms) es crida al la funció del servidor amb la posició que acabam de llegir. El primer cop no tenim aquesta posició i la inicialitzam a zero, d'aquí que necessitem dues cridades a la funció per obtenir el primer log.

Notem com és de simple manipular el json a Javascritp. data té el text que hem d'escriure i la darrera posició de l'arxiu. Si el text està en blanc simplement ens limitam a actualitzar la posició; si hi ha contingut el posarem dins del div amb identificador log que tenim definit dins la plana.

S'hi pot fer molta més: posar-hi més disseny, podríem fer que es retornàs ja informació la primera vegada que es crida, tenir un reset per a que cuan continua el log sols venguin les darreres línies i no tot el bloc, però la idea és la mateixa:

  • fer servir un timer
  • fer servir ajax enviant la darrera posició on hem arribat
  • fer servir seek i tell per posicionar-nos dins l'arxiu

Com és habitual l'exemple ho podeu davallar d'appfusedjango, o directament un svn co http://code.google.com/p/appfusedjango/source/browse/#svn/trunk/tail

Hi trobareu també una petita utilitat anomenada generate_log.py executant-la en una consola escriu línies de text que poden ser llegides per l'exemple.

blog comments powered by Disqus