Script en BASH para comparar tráfico de los access_logs de Apache

Antes que nada debo decir que este script fue algo que hice en un rato tratando de solucionar un problema, y tiene varios errores y cosas mejorables. De más está decir que no cuenta con ninguna garantía, sino que lo comparto porque quizás pueda ayudar a alguien que enfrente un problema similar.

Mi problema es que necesitaba saber cuáles eran los dominios de un servidor que alojaba más de 300 que estaban teniendo mayor tráfico. Como no tenía ninguna herramienta en tiempo real que me pudiera reportar esta información para el Apache, lo que hice es un script que revisa los access_log de cada dominio y obtiene la cantidad de bytes traficados por cada uno.

En este caso, cada VirtualHost define un archivo de logs diferente para cada dominio. Los access_log usan el formato combined (que incluye User-Agent, etc.) y no especifican nada más. El script no hace ninguna validación al respecto, así que podría no funcionar con logs en otros formatos.

Debe tenerse en cuenta que si los archivos de logs rotan en base a su tamaño, es probable que se presente el siguiente caso: El dominio web1.com tiene gran cantidad de tráfico y el dominio web2.com todo lo contrario. El access_log correspondiente al primer dominio puede reportar el tráfico de 1 Gb en un día y el access_log del segundo puede reportar tráfico también de 1 Gb pero en 6 meses. Por lo tanto, es importante mantener referencia de las fechas extremas de los logs, para poder dar cuenta de estas circunstancias.

Hechas estas aclaraciones, pasamos a la explicación del script.

¿Cómo funciona?

Muy sencillo. El script recorre todos los archivos de logs (1 por dominio). Por cada uno obtiene la fecha inicial (la del primer request registrado) y la final (la del último). Luego suma la cantidad de bytes de todos los requests registrados, e imprime una fila con esa cifra en megabytes, kilobytes, bytes y el rango de fechas que abarca. El conjunto de todas esas líneas es enviada al sort de GNU para ordenar las líneas de menor a mayor cantidad de tráfico.

Quizás suene medio complejo, pero se entenderá mejor viendo el código.

#!/bin/bash

BASE_DIR=/tmp/domain_logs

dateToStamp ()
{
    if [ ${#1} -eq 19 ]; then
        date --utc --date "$1" +%s;
    else
        echo 0
    fi
}

formatDate ()
{
    echo $1 | awk 'BEGIN { \
                    dict["Jan"]=1; \
                    dict["Feb"]=2; \
                    dict["Mar"]=3; \
                    dict["Apr"]=4; \
                    dict["May"]=5; \
                    dict["Jun"]=6; \
                    dict["Jul"]=7; \
                    dict["Aug"]=8; \
                    dict["Sep"]=9; \
                    dict["Oct"]=10; \
                    dict["Nov"]=11; \
                    dict["Dec"]=12 \
                    } { \
                        split(substr($1, 2), a, /[\\\/:]+/); \
                        print a[3] "-" dict[a[2]] "-" a[1] " " a[4] ":" a[5] ":" a[6] \
                    }'
}

for file in `ls $BASE_DIR/*`;
do
    from=$(formatDate `head -1 $file | awk '{print($4)}'`)
    to=$(formatDate `tail -1 $file | awk '{print($4)}'`)
    stamp_from=$(dateToStamp "$from")
    stamp_to=$(dateToStamp "$to")
    total_time=$(($stamp_to-$stamp_from))
    bytes=`cat $file | awk '{a+=$10}END{print a}'`
    kbytes=$((bytes/1024))
    mbytes=$((kbytes/1024))
    echo  "$mbytes MB | \
           $kbytes KB | \
           $bytes Bytes | \
           From: $from ($stamp_from) | \
           To: $to ($stamp_to) | \
           $total_time secs | \
           ${file:${#BASE_DIR}+1}"
done \
| sort -nb

Como se puede ver, al principio se define la variable BASE_DIR, que es el directorio base donde el script va a buscar los access logs. Cuando ejecuto el script, para evitar problemas de cambios en tiempo real, creo un snapshot con todos los archivos de logs y los guardo en una carpeta (en este caso /tmp/domain_logs).

<pre># cp /var/log/httpd/domains/*.access.log /tmp/domain_logs/</pre>

Luego, trabajo directamente sobre el snapshot. Entonces, como se observa en el script, defino dos funciones que utilizo más abajo, dateToStamp y formatDate. Como sus respectivos nombres indican, la primera convierte una fecha en un unix timestamp y la segunda cambia el formato de la fecha que figura en el archivo de logs por el que a mi me sirve para pasarle a date. Esto seguramente es mucha complicación para algo que podría hacer usando mejor el comando date, pero fue lo que más rápido me resultó hacer.

Y luego está el for, que recorre los archivos y por cada uno obtiene las fechas límite ($from y $to), de allí obtiene los timestamps ($stamp_from, $stamp_to), el tiempo total en segundos y la cantidad de bytes (la cual sumo con awk).

Por último, verán que la salida del for tiene un pipe a sort, a quien le paso los parámetros “n” y “b”. Para que haga un ordenamiento numérico ignorando los espacios en blanco.

Ejemplo

# ./access_log_parser.sh
97 MB | 99901 KB | 102299281 Bytes | From: 2008-11-28 02:25:54 (1227839154) | To: 2008-11-28 15:34:12 (1227886452) | 47298 secs | domain1.com.ar.log
115 MB | 118260 KB | 121098817 Bytes | From: 2008-11-28 02:27:45 (1227839265) | To: 2008-11-28 15:34:26 (1227886466) | 47201 secs | domain2.com.ar.log
123 MB | 126652 KB | 129691792 Bytes | From: 2008-11-28 02:27:53 (1227839273) | To: 2008-11-28 15:33:01 (1227886381) | 47108 secs | domain3.com.ar.log
145 MB | 149279 KB | 152862681 Bytes | From: 2008-11-28 02:30:29 (1227839429) | To: 2008-11-28 15:34:31 (1227886471) | 47042 secs | domain4.com.ar.log
189 MB | 194517 KB | 199185477 Bytes | From: 2008-11-28 04:35:20 (1227846920) | To: 2008-11-28 15:32:58 (1227886378) | 39458 secs | domain5.com.log
197 MB | 202034 KB | 206883560 Bytes | From: 2008-11-28 02:35:55 (1227839755) | To: 2008-11-28 15:34:29 (1227886469) | 46714 secs | domain6.com.ar.log
499 MB | 511816 KB | 524099937 Bytes | From: 2008-11-28 02:25:44 (1227839144) | To: 2008-11-28 15:34:29 (1227886469) | 47325 secs | domain7.com.ar.log
#

NOTA: si conocen alguna forma de monitorear esto en tiempo real, también me sería de gran utilidad.

Contar peticiones/request por IP de un access_log de Apache en bash

Aqui os dejo una pequeña linea en bash para contar la cantidad de requests por IP en un access_log de Apache.

cat /var/log/httpd/access_log |awk '{print($1)}'|sort |uniq -c |sort

Aqui la explicacion de como funciona:
Lo primero que hacemos es abrir el archivo con cat, el contenido se pasa a awk con el cual obtenemos la primera columna (lo mismo lo podríamos hacer con cut, pero estoy más acostumbrado a awk). Eso nos devuelve la lista de las IPs. Eso lo pasamos a sort que ordena las IPs antes de poder pasarselo a uniq. Éste último comando, con el parámetro -c se ocupa de contar las líneas repetidas en un listado ordenado (por eso el sort anterior). Y por último le volvemos a pasar todo a sort para que lo ordene en forma ascendente de acuerdo a la cantidad de ocurrencias de cada IP.

URLs estáticas y agradables para cualquier aplicación web

La idea de este artículo es mostrar un sencillo ejemplo de cómo hacer que los links de nuestras aplicaciones web sean “bonitos”, algo así como los que podemos utilizar con WordPress. De tal manera no tendríamos links como estos:

http://www.sitio.com/index.php?accion=consultar&objetivo=personas

Sino algo estéticamente más agradable como:

http://www.sitio.com/personas/

¿Qué necesito?

Es necesario tener en cuenta los prerrequisitos para poder hacer esto. Para este ejemplo voy a suponer que la aplicación la estás haciendo sobre el servidor Apache, y que estás programando en PHP+MySQL. Necesitas:

  • Manejar la mayor parte del trabajo con el archivo index.php. Esto más que un requisito es un consejo. Cuando estés desarrollando aplicaciones en PHP es recomendable que la mayor parte del sistema tenga que ser procesado inicialmente por este archivo, el cual se encargará de manejar todas las peticiones y utilizar los módulos que se necesiten. Esto, por supuesto, NO quiere decir que TODO el código vaya dentro del archivo index.php; una buena práctica es separar el código en módulos y llamarlos con funciones como include o require.
  • Es necesario tener instalado el mod_rewrite, el cual se utilizará desde un archivo .htaccess. El ModRewrite es un módulo para Apache que por lo general se configura (definir reglas) en el archivo httpd.conf, pero es posible poner una configuración ModRewrite en cualquier directorio de nuestro servidor web dentro del archivo .htaccess.
  • Puesto que de acuerdo al string que pasemos en la URL se debe determinar qué hacer, es necesario hacer algo de esto:
    • Hacer que dentro de la base de datos la llave primaria de la tabla a consultar sea una cadena de texto, ó
    • Hacer otro campo en la tabla de MySQL aparte de la llave primaria

Ejemplo

Para nuestro ejemplo necesitamos un servidor Apache con ModRewrite, PHP y MySQL. Lo primero es crear la base de datos, así:

Continuar leyendo «URLs estáticas y agradables para cualquier aplicación web»