Análisis de logs de Apache (uso de awk)

<body>.</body>

El objetivo principal del presente ejercicio es profundizar en el uso de las utilidades de shell para el procesado de logs del sistema, haciendo especial énfasis en la herramienta awk.

DESARROLLO

Todo consiste en generar un único script que realice varias funciones. Para todas ellas, el script procesará una serie de ficheros de log del servidor Apache, <poner ejemplos de logs para descargar>.

El script considerará una variable que determine dónde se encuentran todos los ficheros de logs. Dichos ficheros se denominan access.log y access.log.2.gz (siendo este último resultado de una rotación de logs).

Es de gran ayuda para este tipo de scripts el generar ficheros temporales, pero se deben borrar adecuadamente a la salida. Tampoco podremos alterar en forma alguna los ficheros originales de logs.

Para cada funcionalidad solicitada se creará una función independiente. El código principal del programa comprueba cuál es el primer argumento que recibe el script, y en función de dicho argumento se ejecuta una función u otra. Es útil y profesional imprimir un breve mensaje de ayuda si no se recibe argumento.

El uso que se dará a la herramienta awk será el de obtener y separar los campos, y realizar los oportunos recuentos estadísticos (haciendo uso de expresiones regulares) principalmente.

Para el desarrollo del programa, pudiera resultar interesante conocer el carácter ‘^’, que en la notación de expresiones regulares indica “el primer carácter de la línea”, y el carácter ‘$’ que indica “el último carácter de la línea”. Estos caracteres, junto con las expresiones regulares en general, pueden ser usados en los programas como grep, egrep, sed, y awk.

Las funciones que realizará el programa son:

  • Argumento “ips”: se imprimirá por la salida estándar una lista de las 10 direcciones IP desde las cuales más visitas se hayan hecho al servidor, precedidas del número de visitas desde dicha IP, utilizando un espacio como separador de campo. No no se imprimirán más mensajes o información: sólo el número de visitas, un espacio, y la IP. Podr´an resultar de utilidad los programas sort (que permite ordenar resultados), y uniq (que dada una entrada ordenada elimina filas repetidas a la salida estándar).

  • Argumento “tamanno”: el script calculará el tamaño medio de los ficheros servidos por el servidor web. Para ello, nos fijaremosúnicamente en el código HTTP (estándar RFC2616, http://www.faqs.org/rfcs/rfc2616.html), 200, que indica “OK”. Para aquellas peticiones que hayan sido respondidas con un 200, el siguiente campo indica el tamaño en bytes del objeto servido. Obviamente, las peticiones no servidas no deben afectar al cálculo del tamaño medio. Dicho valor se escribirá, sin más mensajes ni información, y en bytes, por la salida estándar.

  • Argumento “horas”: se imprimirá por la salida estándar un mensaje indicando en qué rango horario (de una hora de amplitud) se ha producido el mayor número de visitas, y cual es ese número de visitas. Así, si el mayor número de visitas se ha producido entre las 19 y las 20 horas, donde ha habido 912 visitas, se imprimirá por la salida estándar “19-20h (912)”. Se respeta estrictamente este formato de salida para que la lectura posterior del análisis sea más clara.

  • Argumento “navegadores”: el script generará una estadística de los navegadores más usados para acceder al servidor web. Por cuestiones de diseño y modularidad, se desarrollan a su vez, al menos, dos funciones:

    • La primera cuenta el número de veces con los que se ha visitado con cada navegador el servidor. El resultado, será una única línea (que no se imprime, sino que se pasa a la segunda función) con el siguiente formato:

    totales mozilla firefox ie opera konqueror wget otros

    donde cada variable indica el número de veces que se ha identificado cada navegador. El navegador usado se identifica en cada línea de logs como el tercer valor entrecomillado que aparece. Se puede considerar que:

     

      • firefox es el navegador que contiene la cadena “Firefox”.
      • ie es el navegador que contiene la cadena “MSIE”.
      • opera es el navegador que contiene la cadena “Opera”.
      • konqueror es el navegador que contiene la cadena “Konqueror”.
      • wget es el navegador que contiene la cadena “Wget”.
      • mozilla es el navegador que contiene la cadena “Mozilla” y no es ni firefox ni ie ni opera ni konqueror.

     

    • La segunda recibe como primer argumento el número total de visitas, como segundo el número de visitas desde mozilla, como tercero el número de visitas desde firefox, como cuarto el número de visitas desde ie, etc. En otras palabras, recibe como argumentos la línea de salida de la función anterior. Esta función calcula para cada navegador, y manteniendo el orden, el porcentaje sobre el total que representa, con dos decimales. Dicho resultado se imprime por la salida estándar, en una única línea según el formato especificado (exceptuando el total, que no debe imprimirse). Para la realización de esta función es de utilidad el comando shift (de bash), que “mueve” los argumentos del script (o una función) un lugar hacia la izquierda; así, $2 pasa a ser $1, $3 pasa a ser $2 y así sucesivamente (y $1 se pierde).

     

    Para la realización de los cálculos, lo más óptimo es usar el programa bc. Este programa permite desde linea de comandos realizar cualquier operación matemática, con precisión arbitraria, que le sea especificada a la entrada estándar. Las operaciones o sentencias pueden separase por “;” (punto y coma) y pueden declararse algunas variables del propio programa (como por ejemplo scale que fija el número de decimales; las variables se asignan con “=”).


SOLUCIÓN

#!/bin/bash

DIR_LOGS=../enunciado/
tmp=`tempfile`

zcat $DIR_LOGS/access.log.2.gz > $tmp
cat $DIR_LOGS/access.log >> $tmp


# Funciones

function ips () {
	tmpips=`tempfile`
	for ip in `cat $tmp |awk '{print $1}' |sort |uniq`
	do
		n=`grep $ip $tmp |wc -l`
		echo "$n $ip" >> $tmpips
	done

	sort -nr $tmpips |head -10
	rm $tmpips
}

function ips2 () {
	awk '{ips[$1]++} END {for (ip in ips) print ips[ip], ip}' $tmp \
		|sort -nr |head -10
}

function tamanno () {
	tmptamanno=`tempfile`
	awk -F\" '{print $3}' $tmp |awk '{print $1, $2}' |grep "^200" \
		|awk '{print $2}' > $tmptamanno

	tamanno=0
	for i in `cat $tmptamanno`
	do
		tamanno=`expr $tamanno + $i`
	done

	n_lineas=`cat $tmptamanno |wc -l`
	echo `expr $tamanno / $n_lineas`

	rm $tmptamanno
}

function tamanno2 () {
	awk -F\" '{print $3}' $tmp \
		|awk '$1 ~ /200/ {tam += $2; n += 1} END { printf ("%d\n",  tam / n) }'
}

function horas () {
	tmphoras=`tempfile`
	awk -F"[" '{print $2}' $tmp  |awk -F: '{print $2}' > $tmphoras
	hora=-1; max=-1
	for h in `seq 0 23`
	do
		c=`grep "^$h$" $tmphoras |wc -l`
		if [ $c -gt $max ]
		then
			hora=$h; max=$c
		fi
	done
	
	echo "$hora-$(( $hora + 1 % 24 ))h ($max)"

	rm $tmphoras
}

function horas2 () {
	awk -F"[" '{print $2}' $tmp \
		|awk -F: '{horas[$2]++} END { \
			max=-1; \
			for (hora in horas)\
				if (horas[hora] > horas[max])\
					max=hora;\
			printf ("%d-%dh (%d)\n", max, (max + 1) % 24, horas[max])\
		}'
}

function cuentanavegador () {
	cat $1 |grep $2 |wc -l
}

function navegadores () {
	tmpnavegadores=`tempfile`
	awk -F"\"" '{print $6}' $tmp > $tmpnavegadores

	firefox=`cuentanavegador $tmpnavegadores "Firefox"`
	ie=`cuentanavegador $tmpnavegadores "MSIE"`
	opera=`cuentanavegador $tmpnavegadores "Opera"`
	mozilla=`cuentanavegador $tmpnavegadores "Mozilla"`
	konqueror=`cuentanavegador $tmpnavegadores "Konqueror"`
	wget=`cuentanavegador $tmpnavegadores "Wget"`
	totales=`cat $tmpnavegadores |wc -l`
	otros=`expr $totales - $mozilla - $wget`
	mozilla=`expr $mozilla - $firefox - $ie - $opera - $konqueror`

	porcentage_navegadores $totales $mozilla $firefox $ie $opera $konqueror $wget $otros

	rm $tmpnavegadores
}

function navegadores2 () {
	porcentage_navegadores $( \
	awk -F"\"" ' \
	BEGIN { firefox=0; ie=0; opera=0; mozilla=0; konqueror=0; wget=0} \
		$6 ~ /Firefox/ {firefox++} \
		$6 ~ /MSIE/ {ie++} \
		$6 ~ /Opera/ {opera++} \
		$6 ~ /Mozilla/ {mozilla++} \
		$6 ~ /Konqueror/ {konqueror++} \
		$6 ~ /Wget/ {wget++} \
	END {print NR, mozilla - ie - firefox - konqueror - opera, \
	firefox, ie, opera, konqueror, wget, NR - mozilla - wget}' $tmp \
	)
}

function porcentage_navegadores () {
	total=$1
	shift
	grafica_navegadores ` \
		for i in $*
		do
			echo "scale=2; $i * 100 / $total" |bc
		done \
	` |xargs
}

function grafica_navegadores () {
	echo $*
}

function ayuda () {
	echo -ne "\n\tUso: $0"
	echo -e " [ips|ips2|tamanno|tamanno2|horas|horas2|navegadores|navegadores2]\n\n"
}

# Programa principal

case $1 in
	ips)
		ips
		;;
	ips2)
		ips2
		;;
	tamanno)
		tamanno
		;;
	tamanno2)
		tamanno2
		;;
	horas)
		horas
		;;
	horas2)
		horas2
		;;
	navegadores)
		navegadores
		;;
	navegadores2)
		navegadores2
		;;
	*)
		ayuda
		;;
esac

rm $tmp
		



vidalmb_admin – Sáb, 14/07/2007 – 16:51