Análisis de registros de Apache con GoAccess
En esta publicación, muestro una herramienta que permite extraer información interesante de archivos generados automáticamente durante el funcionamiento del servidor.
Daniel Gustaw
• 21 min read
Estructura del código
PHP 32.9% HTML 22.6% JavaScript 20.5% Shell 18.5% CSS 5.5%
Instalación de GoAccess
GoAccess está adaptado para funcionar en muchos sistemas con varios tipos de registros. Asumo que tenemos arch linux
y un servidor Apache2. En este caso, utilizaremos los siguientes comandos para instalar GoAccess:
yay -S goaccess
La configuración implica eliminar comentarios del archivo de configuración /etc/goaccess.conf
en las líneas que contienen entradas:
time-format %H:%M:%S
date-format %d/%b/%Y
log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u"
Ahora necesitas descargar el repositorio desde GitHub.
git clone https://github.com/gustawdaniel/Apache-Log-Analysis-Admin-Panel.git
Estamos creando nuestra propia configuración para este proyecto. Como de costumbre, utilizaremos el archivo yml
.
config/parameters.yml
config:
apache: /var/log/apache2/*access.log
report: report
security:
user: user
pass: pass
authorization: api
La propiedad apache
es una colección de todos los archivos de registro de acceso para dominios individuales que mantenemos en el servidor. El sufijo access.log
está relacionado con la convención que adopté, según la cual redirijo todos los registros de acceso a los archivos domain_access.log
en la configuración del dominio. Por otro lado, report
es la ubicación donde guardaremos los resultados del análisis.
Finalmente, ejecutamos el script de instalación.
bash install.sh
El proyecto debería ser accesible en el navegador en http://localhost:8000
.
Análisis de registros
Nuestro objetivo ahora es usar la herramienta GoAccess
para procesar todos los registros en archivos html.
Para leer el archivo de configuración en bash, utilizaremos una función escrita por Piotr Kuczyński.
lib/parse_yml.sh
#!/usr/bin/env bash
parse_yaml() {
local prefix=$2
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 |
awk -F$fs '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
}
}'
}
Esta función toma dos parámetros, el primero es el nombre del archivo a analizar, el segundo es el prefijo para los nombres asignados dentro de nuestro script a los parámetros extraídos del archivo yml
. Su uso se ilustra a continuación.
#!/usr/bin/env bash
# include parse_yaml function
. lib/parse_yaml.sh
# read yaml file
eval $(parse_yaml config/parameters.yml "parameters_")
mkdir -p $parameters_config_report $parameters_config_report/html $parameters_config_report/json
arr=();
# loop over apache logs
for file in $parameters_config_apache
do
out=$(basename "$file" .log)
out=${out%_access}
if [ ! -s $file ];
then
continue;
fi
echo "Processed: "$out;
goaccess -f $file -a -o $parameters_config_report/html/$out.html;
goaccess -f $file -a -o $parameters_config_report/json/$out.json;
arr+=($out);
done
jq -n --arg inarr "${arr[*]}" '{ list: $inarr | split(" ") }' > $parameters_config_report/list.json
En este script, secuencialmente: incluimos la función anterior, cargamos la configuración en variables. Luego creamos directorios donde deben ubicarse los resultados del análisis de logs, inicializamos un array y recorremos todos los archivos de logs. En este bucle, extraemos el nombre base del archivo. Si contiene _access
en su nombre, lo recortamos, omitimos archivos vacíos y ejecutamos el programa goaccess en los logs que crea archivos html
listos para mostrar en el directorio especificado en la configuración. Finalmente, agregamos el nombre del archivo procesado al array.
Después de completar el bucle, convertimos la lista de nombres procesados a formato json
y lo guardamos junto con los informes. Con esta lista, no necesitaremos recorrer el directorio en php
. Antes de ejecutar este script, es posible que necesites instalar jq. Es muy simple:
apt-get install jq
Backend
Tenemos los registros listos, ahora crearemos una API que los hará disponibles. No queremos mantenerlos en una ubicación accesible desde el nivel del navegador. El navegador solo tendrá acceso al directorio web
, y por eso colocaremos el archivo api.php
allí. Dado que necesitaremos acceso a la configuración, también instalaremos un analizador yml
.
composer require symfony/yaml
El archivo de la API es principalmente de enrutamiento. Sin embargo, comienza con la conexión de paquetes, la configuración de variables y encabezados:
web/api.php
<?php
require_once __DIR__."/../vendor/autoload.php";
use Symfony\Component\Yaml\Yaml;
$config = Yaml::parse(file_get_contents(__DIR__.'/../config/parameters.yml'));
session_start();
$uri = explode('/', strtolower(substr($_SERVER['REQUEST_URI'], 1)));
$route = isset($uri[1]) ? $uri[1] : "";
$parameter = isset($uri[2]) ? $uri[2] : "";
$data = array();
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type");
header("Access-Control-Allow-Methods: GET, POST");
header('Content-Type: application/json');
Adjuntando la configuración de esta manera ya ha sido discutido. La novedad es establecer la sesión. Esta es una función tan inteligente que crea una cookie con un número de sesión aleatorio para el usuario y simultáneamente guarda este número en el lado del servidor, para que se pueda referir a este número específico en la variable $_SESSION
sin tener que verificar la cookie manualmente o preocuparse por el hecho de que.
La novedad es dividir la dirección uri
en un array utilizando los caracteres /
. Su primer elemento tendrá el valor api.php
, por lo que capturamos los siguientes dos si existen. Establecemos un array vacío data
para nosotros y finalmente agregamos encabezados que permiten eludir problemas de CORS y establecer el tipo de dato de retorno predeterminado.
En Symfony, hay clases especiales Response
y JsonResponse
que facilitan devolver respuestas; sin embargo, aquí utilizaremos un método más primitivo debido a su simplicidad. Definiremos una función para devolver errores.
function returnError($code,$type,$message){
$data["error"] = ["code" => $code, "type" => $type, "message" => $message];
echo json_encode($data);
die();
}
Vale la pena notar que devuelve códigos de error, pero siempre tiene un código igual a 200. Las excepciones serán errores del lado del servidor que no capturaré. Solo en tal caso quiero devolver un código de error. Es hora de comenzar a discutir el enrutamiento. Comenzaremos con la ruta para validar el inicio de sesión. En Symfony
, no es login
, sino login_check
.
switch ($route) {
case "login": {
if(!isset($_POST["user"]) || !isset($_POST["pass"])) {
returnError(400,"Bad Request","Invalid form");
} elseif($_POST["user"]!=$config["security"]["user"] || $_POST["pass"]!=$config["security"]["pass"]) {
returnError(403,"Forbidden","Incorrect Login or Password");
}
$_SESSION['user'] = $config["security"]["user"];
$data = ["state" => "loggedIn"];
}
Nuestro interruptor acepta una ruta ingresada después de api.php/
pero antes del siguiente carácter /
. En esta parte del código, abordamos el caso cuando la dirección de la solicitud contenía login
. Dado que usamos el método $_POST
para iniciar sesión, el controlador en esta ruta verifica si se han enviado las variables user
y pass
y si coinciden con las establecidas en la configuración. Si la validación de datos es exitosa, se creará una variable $_SESSION['user']
, y se agregará el estado que confirma el inicio de sesión a la lista de datos devueltos en la respuesta.
Tenga en cuenta que no añadí la declaración break;
al final. Hice esto intencionadamente. Inmediatamente después de iniciar sesión, sin enviar otra solicitud, siempre quiero recibir una lista de dominios para los cuales Apache crea registros. Por lo tanto, coloqué un bloque report
debajo del bloque login
, que está destinado a ejecutarse tanto cuando se selecciona la ruta report
como después de que el usuario haya iniciado sesión correctamente. Sin embargo, dado que quiero acceder a esta ruta a través de la API
omitiendo el inicio de sesión por formulario, verificaré los derechos de acceso con la siguiente condición antes de recuperar los datos necesarios:
case "report": {
if(
(!isset($_SESSION['user'])
||!$_SESSION['user'])
&&(!isset(getallheaders()["Authorization"])
||getallheaders()["Authorization"]!=$config["security"]["authorization"]
)
){
returnError(403,"Forbidden","Incorrect Login or Password");
}
Además de verificar si la sesión está establecida, también verificamos el encabezado Authorization
aquí como un método de inicio de sesión alternativo. Si al menos uno de los métodos de inicio de sesión (sesión o encabezado) se considera válido, se ejecutará el siguiente código:
$data["report"] = [];
$list = json_decode(file_get_contents(__DIR__ . "/../" . $config["config"]["report"] . "/list.json"));
foreach ($list->list as $key => $value) {
$data["report"][] = ["name" => $value, "key" => $key, "link" => "api.php/report/" . $value];
};
Creamos la clave report
para el array de respuesta. Leímos y decodificamos la lista de nombres de archivos con los registros de Apache procesados generados por el script build.sh
. Luego, en un bucle, ampliamos la estructura de cada elemento en esta lista con los atributos key
y link
, y asignamos el nombre en sí a la clave name
. Esta transformación está destinada a facilitar un procesamiento más sencillo de estos datos por parte del front-end que hemos desarrollado.
Sin embargo, la funcionalidad principal no es mostrar los nombres de los archivos en sí, sino su contenido. Este es un buen momento para familiarizarse con el mecanismo de negociación de contenido
. Es una manera para que una API RESTful asigne una representación a la misma dirección url
utilizando diferentes tipos de datos. En nuestro ejemplo, estos serán html
y json
. El tipo de dato que queremos recibir se establece en el encabezado Accept
al preparar la solicitud. El siguiente código es responsable de la correcta interpretación de este encabezado.
if ($parameter != "") {
if (preg_match('/text\/html/', $_SERVER["HTTP_ACCEPT"])) {
header('Content-Type: text/html');
echo file_get_contents(__DIR__ . "/../" . $config["config"]["report"] . "/html/" . $parameter . ".html");
} elseif (preg_match('/application\/json|\*\/\*/', $_SERVER["HTTP_ACCEPT"])) {
header('Content-Type: application/json');
echo file_get_contents(__DIR__ . "/../" . $config["config"]["report"] . "/json/" . $parameter . ".json");
} else {
returnError(415,"Unsupported Media Type","Incorrect Content Type");
}
die();
}
break;
}
Se ejecutará solo si la url
contiene algo después de api.php/report/
. Esta última parte se guardó en la variable $parameter
al principio del script al dividir la uri
en partes. Indica qué archivo necesitamos extraer, y se establece mediante la clave link
del array $data["report"]
. La función preg_match
verifica si la expresión regular dada en el primer argumento aparece en la cadena del segundo argumento. Dependiendo de si se coincide con text/html
, application/json
o */*
, se devuelve html
o json
.
La última ruta manejada por la api
es logout
.
case "logout": {
session_unset();
session_destroy();
$data = ["state" => "loggedOut"];
break;
}
Es responsable de eliminar la sesión y asignar el estado loggedOut
. Finalmente, manejamos la excepción relacionada con la ruta incorrecta, en particular, este también es nuestro punto de partida api.php/
default: {
returnError(404,"Not Found","Use route /report with Authorization header");
break;
}
}
echo json_encode($data);
Después de ejecutar la instrucción switch
, enviamos los datos que se recopilaron en el arreglo $data
durante el procesamiento de la solicitud.
Acceso a través de API
Para acceder a través de API
, simplemente envía la siguiente solicitud:
http -v --pretty=all GET localhost:8000/api.php/report Authorization:api
Recibimos una lista de archivos disponibles. Si queremos un archivo específico, ingresamos:
http -v --pretty=all GET localhost:8000/api.php/report/api_brainjinn Authorization:api
Frontend
La separación del frontend del backend ha sido de gran interés para mí durante algún tiempo. Me gustaron las formas de ver la organización del código en frameworks como aurelia
y angular
. Sin embargo, no nos pasemos de la raya. No vamos a sacar un cañón para matar una mosca y no usaremos ninguno de ellos aquí.
Solo establecí una condición para mi frontend: debe ser una aplicación de una sola página que maneje correctamente el inicio y cierre de sesión. También renuncié al uso de gulp, ya que complicaría innecesariamente este pequeño proyecto.
A pesar de esto, apliqué mecanismos de plantillas aquí y un enrutamiento muy primitivo basado en respuestas de api
baratas o eventos en lugar de basarse en los fragmentos de URL.
Comenzaremos con la instalación de bibliotecas externas para el frontend. Estas serán bootstrap 4
y mustache 2.3
. Mientras que el primer paquete es generalmente bien conocido, encontré mustache
por primera vez. Es el equivalente a twig
pero ejecutado del lado del cliente en lugar de del servidor. Antes de comenzar la instalación, crearemos un archivo de configuración de bower:
.bowerrc
{
"directory": "web/bower_components"
}
Indica instalar directamente en el directorio web
. Esto está relacionado con el hecho de que al renunciar a gulp
, desean tener paquetes listos para usar expuestos externamente. Les recuerdo que el navegador solo tiene acceso al directorio web
en nuestra estructura de proyecto. Para instalar los paquetes, ejecutamos:
bower init
bower install --save bootstrap#v4.0.0-alpha.5
bower install --save mustache
Ahora pasaremos al punto de entrada de nuestra aplicación: el archivo index.html
.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Apache Log Analysis</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="bower_components/tether/dist/css/tether.min.css">
<link href="https://fonts.googleapis.com/css?family=Lato:300&subset=latin-ext" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="content"></div>
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="bower_components/tether/dist/js/tether.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="bower_components/mustache.js/mustache.min.js"></script>
<script src="js/site.js"></script>
<script>
var url = "/api.php/";
</script>
<script src="js/routing.js"></script>
</body>
Su estructura se asemeja un poco a la estructura de index.html
de Aurelia o Angular. Es prácticamente un html
vacío con estilos adjuntos, un div que sirve como punto de anclaje, y luego los scripts en sí. En términos de estilos, tenemos bootstrap
, tether
como su dependencia, la fuente Lato
y nuestros estilos. Más adelante, hay espacio para el mencionado punto de anclaje. En el div con id="content"
, nuestra aplicación se construirá dinámicamente. En cuanto a los scripts, adjuntamos bootstrap
y mustache
junto con las dependencias de bootstrap
. El archivo site.js
es nuestra biblioteca que contiene funciones comúnmente utilizadas. La variable global url
ha sido expuesta en index.html
porque no valía la pena crear un entorno de producción y desarrollo separado para esta única variable. Finalmente, se adjunta routing.js
, que verifica si el usuario ha iniciado sesión y nos redirige a la página de inicio de sesión o muestra la lista de archivos de registro.
Sin embargo, más sobre eso más adelante, ahora repasaremos los adjuntos de index.html
de arriba hacia abajo. Comenzaremos con estilos:
web/css/style.css
body {
font-family: 'Lato', sans-serif;
}
.login-container {
padding-top: 25vh;
}
.report-container {
padding-top: 15vh;
}
.btn-login {
background-color: #59B2E0;
outline: none;
color: #fff;
font-size: 14px;
height: auto;
font-weight: normal;
padding: 14px 0;
text-transform: uppercase;
border-color: #59B2E6;
}
.btn-login:hover,
.btn-login:focus {
color: #fff;
background-color: #53A3CD;
border-color: #53A3CD;
}
.padding-b-10{
padding-bottom: 10px;
}
Estilos como estilos. Nada especial. Agregamos una fuente, padding
al contenedor
principal, un botón de inicio de sesión personalizado y padding
para mostrar la lista de archivos de registro. Los scripts son más interesantes, aquí está nuestra biblioteca con funciones útiles:
web/js/site.js
function getFormData($form){
var unindexed_array = $form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i){
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
El primero de estos se usa para convertir un formulario a un formato json
que es más intuitivo que lo que ofrece serializeArray
, a la vez que es mucho más elegante que lo que hace serialize
.
function deleteAllCookies() {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
var eqPos = cookie.indexOf("=");
var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
}
}
La segunda función es responsable de borrar cookies. Curiosamente, no se pueden simplemente eliminar, pero su fecha de caducidad se puede establecer en varias décadas atrás.
function loadComponent(name,data){
$.get("component/"+name+"/"+name+".html").done(function(template){
var html = Mustache.render(template, data);
$("#content").html(html);
$.getScript("component/"+name+"/"+name+".js")
});
}
La última función se adapta mejor a nuestra estructura de directorios y contenido, así que antes de discutirla, mencionaré la estructura de directorios. En web
, además de los obvios js
, css
, bower_components
, también tenemos el directorio component
. El nombre tomado de Angular indica que dentro encontraremos scripts y plantillas correspondientes a una funcionalidad específica. Esta es una intuición correcta, así que en component
tenemos el directorio login
con los archivos login.html
y login.js
, y el directorio report
con los archivos report.html
y report.js
. Esta función es responsable de obtener el archivo html
del componente utilizando el método GET
, renderizándolo con la biblioteca mustache
, que inyecta los datos contenidos en la variable data
. Luego, este archivo se une a nuestro punto de anclaje en index.html
, y cuando eso sucede, se le proporcionan scripts. Un mecanismo hermoso en su simplicidad, y es el corazón de todo el front-end. Es gracias a este método que el front-end vive y cambia de vistas sin recargar la página.
Sin embargo, esta función no se llamará por sí sola. Mencioné el enrutamiento primitivo. Administra lo que veremos cuando se carga la página:
web/js/routing.js
$.get(url+"report").done(function(data){
//console.log(data);
if(data.hasOwnProperty('report')){
loadComponent("report",data);
} else {
loadComponent("login",{});
}
});
Componentes
Su funcionamiento consiste en intentar recuperar contenido reservado para usuarios registrados. No quería devolver un código de error 403
aquí, porque realmente no es un error. Es bastante normal que a veces no estemos registrados. Gracias a esto, incluso si el usuario no tiene acceso a estos recursos, utilizo el método done
. Por supuesto, si no estamos registrados, la respuesta no contendrá la clave report
sino error
. En este caso, el login
se cargará con un array de datos vacío. Sin embargo, si la sesión se crea y el usuario está correctamente registrado, cargamos el componente report
y le pasamos los datos recibidos del servidor.
Solo nos quedan 4 archivos por discutir de los componentes. Comenzaremos con la plantilla de inicio de sesión:
web/component/login/login.html
<div class="container login-container">
<div class="row">
<div class="offset-lg-3 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card card-block text-xs-center">
<h4 class="card-title">Apache Log Analysis</h4>
<div class="card-block">
<form id="login-form">
<div class="form-group">
<input type="text" name="user" tabindex="1" class="form-control" placeholder="Username" value="">
</div>
<div class="form-group">
<input type="password" name="pass" tabindex="2" class="form-control" placeholder="Password">
</div>
<div class="form-group">
<input type="submit" name="login-submit" id="login-submit" tabindex="4" class="form-control btn btn-login" value="Log In">
</div>
</form>
<div id="login-error"></div>
</div>
</div>
</div>
</div>
</div>
Formulario simple con dos campos y un div para posibles errores. Se ve así:
El script que lo soporta es un ejemplo de libro de texto de manejo de formularios en js.
var form = document.getElementById("login-form");
var error = document.getElementById("login-error");
form.addEventListener("submit", function (e) {
e.preventDefault();
//console.log(JSON.stringify(getFormData($(this))));
$.post(url + 'login', getFormData($(this))).done(function (data) {
//console.log("s",data);
if (data.hasOwnProperty('error')) {
//console.log("error_detected");
error.innerHTML = '<div class="alert alert-danger">' + data.error.message + '</div>';
} else if (data.hasOwnProperty('state')) {
if (data.state == "loggedIn") {
loadComponent("report", data);
}
}
}).fail(function (data) {
error.innerHTML = '<div class="alert alert-danger">' + 'There are unidentified problems with service.' + '</div>';
//console.log(data);
});
return false;
});
Elementos de seguimiento. Agregando un oyente. Al intentar enviar, enviamos un POST
con el contenido del formulario. El manejo de errores para 4xx
está en done
y no en fail
. En caso de éxito, cargamos el reporte
. Finalmente, manejamos errores 5xx
con fail
.
La vista del informe es más interesante, ya que mustache
crea un bucle aquí.
web/component/report/report.html
<div class="container report-container">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="card card-block text-xs-center">
<h4 class="card-title">Apache Log Analysis</h4>
<div class="card-block">
<ul class="list-group row">
{{ "{{ #report " }}}}
<div class="col-sm-6 col-md-4 col-lg-3 padding-b-10">
<a target="_blank" href="{{link}}" class="list-group-item ">{{ "{{ #name " }}}}</a>
</div>
{{ "{{ /report " }}}}
</ul>
</div>
<div class="card-block">
<button id="logout" class="btn btn-danger btn-block">Logout</button>
</div>
</div>
</div>
</div>
</div>
El bucle sobre el array report
muestra todos los elementos de la lista, adjuntando nombres y enlaces a ellos. Para mis registros, se ve así:
El script solo cierra sesión aquí, y por eso es bastante corto:
web/component/report/report.js
var logout = document.getElementById("logout");
logout.addEventListener("click", function () {
deleteAllCookies();
$.get(url + "logout");
loadComponent("login", {});
});
Al final, también proporcionaré una captura de pantalla de un análisis de registro de muestra. Esta es la imagen que veremos después de seleccionar uno de los archivos desde la vista de informe
. En este caso, estos son los registros de este blog.
Despliegue
Una técnica que me gusta mucho, pero que aún no he descrito, para desplegar un proyecto en producción es usar git. Git nos permite enviar solo los archivos esenciales, y podemos instalar bibliotecas externas directamente desde el entorno de producción. Para que esto funcione, necesitamos agregar la ubicación del repositorio en nuestro servidor a la colección local de repositorios remotos.
Asumimos que iniciamos sesión como un usuario llamado root
y que la ip
de nuestro servidor está en la variable $ip_gs
. El repositorio del proyecto en el servidor se mantendrá en el directorio /var/repo/log_analysis
.
git remote add live ssh://root@$ip_gs/var/repo/log_analysis
En el servidor, ejecutamos comandos:
mkdir -p /var/repo/log_analysis && cd /var/repo/log_analysis
git init --bare
A continuación, creamos el archivo post-receive
en el directorio hooks
y guardamos el siguiente contenido en él:
/var/repo/log_analysis/hooks/post-receive
#!/bin/sh
WORK_TREE=/var/www/log_analysis
GIT_DIR=/var/repo/log_analysis
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout -f
exit
Finalmente, establecemos permisos chmod a+x post-receive
y creamos un directorio donde se ubicarán los archivos del proyecto.
mkdir -p /var/www/log_analysis
Regresamos a la máquina local y empujamos el repositorio al servidor.
git push live master
Regresamos al servidor y configuramos la configuración de producción en el archivo /var/www/log_analysis/config/parameters.yml
. El objetivo aquí es no dejar al usuario user
con la contraseña pass
en producción. La forma más sencilla será copiar el archivo /var/www/log_analysis/config/parameters.yml.dist
y cambiar los valores bajo la clave security
.
La instalación consiste en ejecutar cuatro comandos:
apt-get install jq
composer install
bower install
bash build.sh
Ahora nuestra tarea es conectar la web a uno de los dominios o puertos. Para nosotros, será el puerto 8001. Añadiremos un listener en este puerto a Apache añadiendo la línea apropiada a la configuración:
/etc/apache2/ports.conf
# log analysis
Listen 8001
Agregamos un archivo al directorio sites-available
:
/etc/apache2/sites-available/log_analysis.conf
<VirtualHost *:8001>
DocumentRoot /var/www/log_analysis/web
ErrorLog /var/log/apache2/log_analysis_error.log
CustomLog /var/log/apache2/log_analysis_access.log combined
</VirtualHost>
Lo vinculamos simbólicamente con sites-enabled
utilizando el comando:
a2ensite log_analysis.conf
Recargando Apache
service apache2 reload
El servicio debería funcionar, pero querían automatizar el proceso de actualización de las vistas de los registros procesados.
Cron
Hay diferentes enfoques posibles. El primero es construir vistas al inicio de cada sesión. El segundo es construir solo la vista que estamos consultando actualmente. El tercero es construir todas las vistas diariamente y no sobrecargar al usuario con la espera.
Dado que la puntualidad de los registros con precisión horaria no es un valor más grande para mí que unos pocos segundos de espera para que se cargue la vista, decidí crear todas las vistas cíclicamente cada día.
Para lograr esto, basta con crear un archivo:
/etc/cron.daily/log_analysis
#!/bin/bash
cd /var/www/log_analysis/
bash build.sh
y otorgarle permisos de ejecución:
chmod a+x /etc/cron.daily/log_analysis
Los registros de Apache son una fuente valiosa de información. Aunque no tienen las capacidades de medición de los scripts instalados en el sitio (mapas de calor, tiempo de actividad, seguimiento de eventos), el hecho de que se recojan automáticamente significa que se pueden utilizar sin ninguna carga adicional en el sitio.
Háganme saber en los comentarios si este proyecto les ha sido útil, o si tienen alguna idea de cómo podría mejorarse.
Other articles
You can find interesting also.
Publicando una actualización del paquete en el repositorio AUR
Aprende a publicar actualizaciones de paquetes en el repositorio de usuarios de Arch Linux.
Daniel Gustaw
• 3 min read
Registro de datos en MySql, Ajax y Behat
Escribiremos una aplicación web simple - una calculadora. Usándola como ejemplo, mostraremos cómo configurar selenium con behat y realizar pruebas automatizadas en ella.
Daniel Gustaw
• 14 min read
tRPC - ciclo de desarrollo súper rápido para aplicaciones fullstack en TypeScript
Estamos construyendo un cliente y servidor tRPC con consultas, mutaciones, autenticación y suscripciones. La autenticación para websocket puede ser complicada y en este caso lo es, por lo que se presentan tres enfoques para resolver este problema.
Daniel Gustaw
• 16 min read