Ya tienes tres tareas ejecutándose solas: el informe a las 06:30, el estado cada 10 minutos y el respaldo a las 03:15. Cada una escribe en un fichero que crece sin parar, y ninguna responde la pregunta que de verdad importa: ¿quién se entera si una deja de funcionar? Registro y monitorización son las dos caras del mismo problema. El registro es que el sistema te cuente lo que hace, para reconstruir después qué pasó; la monitorización es que te avise mientras pasa. Uno sirve para investigar, el otro para reaccionar, y ninguno funciona improvisado: un log sin formato no se puede consultar, y una alerta que suena cada cinco minutos deja de leerse a la semana.
Contenido
- Por qué
echono basta y cómo es una línea de log - La función
veloz_logcon niveles y umbral logger, syslog yjournalctl- Rotación con
logrotate copytruncatefrente acreate- Comprobar frente a observar tendencia: el patrón 0/1/2
- Umbrales, ruido, histéresis y estado
- Comprobaciones típicas de Veloz Envíos
- Canales de aviso y bucle frente a ejecución periódica
- Aplicación: nace
vigilante.sh
- Por qué
echo no basta y cómo es una línea de log
echo no basta y cómo es una línea de logUn echo "procesando" responde a la persona que está mirando la pantalla ahora mismo. Dentro de un fichero de tres meses, esa línea no dice cuándo ocurrió, quién la escribió ni si era importante —y esas tres respuestas son todo lo que necesitas cuando el informe del martes falla y lo investigas el jueves—. Además, echo escribe en la salida estándar, y eso mezcla dos cosas que deben ir separadas (02-04): los resultados, que otro programa podría consumir por una tubería, y los diagnósticos. La regla de siempre: resultados a stdout, log a stderr.
Un formato de log tiene cuatro campos, cada uno con su razón de ser: la marca de tiempo (2026-08-03T06:30:01+02:00) para correlacionar con otros sistemas, el nivel (INFO, WARN, ERROR) para filtrar por gravedad, el componente (informe, respaldo, vigilante) para saber quién habla en un log compartido, y el mensaje con lo que ha pasado. La marca de tiempo va primero y en ISO-8601 por dos motivos prácticos: ordena alfabéticamente igual que cronológicamente —así sort funciona directamente sobre el log— y no es ambigua entre zonas horarias. La genera date -Is. Sobre el mensaje, una recomendación que se agradece cuando el log crece: usa pares clave=valor en lugar de prosa, porque procesados=1284 ciudad=Madrid se filtra con grep y se agrega con awk (06-01), y «se han procesado 1284 envíos, de los cuales…» no. Y nunca metas datos personales ni credenciales en el log: los logs se copian, se comparten y se conservan meses (08-03).
- La función
veloz_log con niveles y umbral
veloz_log con niveles y umbralLos cuatro niveles clásicos son DEBUG (detalle interno, invisible en producción), INFO (hitos normales: inicio, fin, resultados), WARN (algo raro pero recuperable, hay que revisarlo) y ERROR (fallo que impide el trabajo, hay que actuar). La función completa para lib/comun.sh:
declare -A VELOZ_NIVELES=([DEBUG]=10 [INFO]=20 [WARN]=30 [ERROR]=40)
: "${VELOZ_LOG_NIVEL:=INFO}" # umbral configurable por entorno o .conf
: "${VELOZ_LOG_FICHERO:=}" # vacio = solo stderr
: "${VELOZ_COMPONENTE:=${0##*/}}" # nombre del script sin ruta
veloz_log() { # veloz_log NIVEL mensaje...
local nivel="$1" linea; shift
(( ${VELOZ_NIVELES[$nivel]:-20} >= ${VELOZ_NIVELES[$VELOZ_LOG_NIVEL]:-20} )) || return 0
printf -v linea '%s [%s] %s: %s' "$(date -Is)" "$nivel" "$VELOZ_COMPONENTE" "$*"
printf '%s\n' "$linea" >&2
[[ -n $VELOZ_LOG_FICHERO ]] && printf '%s\n' "$linea" >> "$VELOZ_LOG_FICHERO"
return 0
}
veloz_log_info() { veloz_log INFO "$@"; } # y sus hermanas _warn, _error, _debugProduce líneas como 2026-08-03T06:30:02+02:00 [INFO] informe-diario.sh: procesados=1284 incidencias=37, y cuatro decisiones merecen explicación. El array asociativo (04-03) convierte etiquetas en números para poder compararlas; sin él, «¿WARN es más grave que INFO?» no tiene respuesta en Bash. El : "${VAR:=valor}" asigna un valor por defecto solo si la variable no venía definida, así que el .conf o el entorno pueden cambiar el umbral sin tocar código (05-06). Escribe siempre a stderr y además al fichero si está configurado, para ver los mensajes al ejecutar a mano y tenerlos registrados bajo cron. Y el return 0 final evita que un log filtrado devuelva código distinto de cero y aborte el script bajo set -e (05-03). Con el umbral por defecto los DEBUG no aparecen, así que puedes dejarlos puestos y activarlos con VELOZ_LOG_NIVEL=DEBUG solo cuando investigas.
logger, syslog y journalctl
logger, syslog y journalctlTodo Linux tiene ya un servicio de registro centralizado —syslog en los clásicos, journald en los que usan systemd— y logger es la orden que escribe en él: logger -t veloz-respaldo -p user.err "el respaldo de 2026-08-03 fallo con codigo 74". -t pone la etiqueta, para filtrar luego por servicio, y -p la prioridad en formato facilidad.nivel (user.info, user.warning, user.err). También acepta la entrada estándar: comando 2>&1 | logger -t veloz-informe.
¿Fichero propio o syslog? El fichero te da control total del formato y volumen ilimitado, pero la rotación la montas tú y consultarlo es grep. El journal ya resuelve la rotación, ofrece los filtros de journalctl y se reenvía a un servidor central de forma estándar, a cambio de una cabecera y de poder descartar mensajes bajo carga. No es excluyente, y la práctica que sigue el toolkit es: el detalle al fichero propio, y los eventos importantes —fallo de una tarea, alerta activada o resuelta— también a logger, para que aparezcan donde el equipo ya mira.
| Orden | Qué muestra |
|---|---|
journalctl -t veloz-respaldo |
Solo lo etiquetado con logger -t |
journalctl -u cron |
Solo de esa unidad (07-05) |
journalctl --since "today" |
También "1 hour ago", "2026-08-01 03:00" |
journalctl -p err / -f |
Prioridad err o superior / seguir en vivo |
journalctl -u veloz-informe -n 50 --no-pager |
50 últimas líneas, sin paginador (para scripts) |
Se combinan: journalctl -t veloz-vigilante -p warning --since "-24h" da los avisos y errores del vigilante en el último día. Para el fichero propio basta lo del Módulo 2: tail -f, grep -F '[ERROR]' y, gracias a que el nivel está en un campo fijo, agregaciones como awk '$2 == "[ERROR]" { print $3 }' vigilante.log | sort | uniq -c | sort -rn. Con un log de prosa libre, eso no sería posible.
- Rotación con
logrotate
logrotateLos logs crecen para siempre, y vigilante.sh escribirá cada 5 minutos. Un disco lleno tumba el servidor entero —incluidas las tareas que iban a avisarte—, así que la rotación no es un extra: es parte de montar una tarea automática. logrotate se ejecuta a diario desde cron.daily y lee las configuraciones de /etc/logrotate.d/:
# /etc/logrotate.d/veloz-ops
/home/veloz/veloz-ops/logs/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 veloz veloz
su veloz veloz
}Directiva a directiva: daily rota una vez al día, al ritmo de las tareas (también existen weekly y size 100M); rotate 14 conserva dos semanas de historial y borra el resto; compress reduce un log de texto a un 10 % de su tamaño; delaycompress comprime a partir del segundo ciclo, dejando el .1 sin comprimir por si aún se escribe en él; missingok no falla si el fichero todavía no existe; notifempty evita acumular catorce rotados vacíos; create 0640 veloz veloz crea el nuevo con el dueño correcto, porque el script no corre como root; y su veloz veloz rota con esa identidad, necesario en directorios de usuario. Eso produce exactamente los acceso.log.1 y app.log.2.gz que llevas viendo desde el Módulo 2: ahora sabes de dónde salen. Y pruébalo antes de confiar en él: logrotate -d /etc/logrotate.d/veloz-ops simula sin tocar nada —es el --dry-run de 07-02 aplicado aquí— y logrotate -f fuerza un ciclo ahora para comprobar permisos y dueño sin esperar a mañana.
copytruncate frente a create
copytruncate frente a createEste apartado explica un fallo que desconcierta a mucha gente: rotas el log y el proceso sigue escribiendo en el fichero antiguo. La causa está en cómo funciona el sistema: un proceso que abrió un fichero escribe en el inodo, no en el nombre. Cuando logrotate renombra vigilante.log a .1 y crea uno nuevo, el proceso que lo tenía abierto sigue apuntando al inodo antiguo, ahora llamado .1, y el fichero nuevo se queda vacío para siempre. Hay tres estrategias. create renombra el antiguo y crea uno nuevo, con el riesgo que acabas de ver. create más postrotate … reload hace lo mismo pero avisa al proceso de que reabra, y es la mejor si el proceso sabe hacerlo. Y copytruncate copia el contenido y luego vacía el original, con el riesgo de perder las líneas escritas entre copiar y vaciar. A los scripts del toolkit no les afecta: cada ejecución de cron abre el log con >>, escribe y termina, y el siguiente >> abre el fichero nuevo por su nombre. Por eso create es lo correcto aquí. Sí afecta a un proceso de larga duración que mantiene el fichero abierto, y ahí necesitas postrotate systemctl reload veloz-api; endscript o bien copytruncate, que es la solución universal cuando el proceso no sabe reabrir, a cambio de esa pequeña ventana de pérdida. Regla práctica: create para procesos que abren y cierran; copytruncate o recarga para los que mantienen el fichero abierto.
- Comprobar frente a observar tendencia: el patrón 0/1/2
Antes de escribir el vigilante, una distinción que evita construir la herramienta equivocada. Una comprobación (check) responde «¿está bien ahora?» con sí/no/dudoso y sirve para avisar; una métrica responde «¿cómo evoluciona?» con un número en el tiempo y sirve para diagnosticar y prever. Bash es excelente para comprobaciones y para recoger métricas, pero no es el sitio donde guardar series temporales ni dibujar gráficas —eso son Prometheus, Grafana y compañía—. Los sistemas de monitorización clásicos (Nagios y sus descendientes) establecieron un convenio muy útil que puedes adoptar aunque no uses ninguno: 0 = OK, 1 = WARNING (rozando el umbral, mirar), 2 = CRITICAL (roto, actuar ya) y 3 = UNKNOWN (no se ha podido comprobar). Cada comprobación es entonces una función independiente y homogénea: recibe sus umbrales, imprime una línea de resumen y devuelve uno de esos cuatro códigos.
check_disco() { # check_disco <ruta> <aviso%> <critico%>
local ruta="$1" aviso="$2" critico="$3" uso
uso=$(df -P "$ruta" | awk 'NR == 2 { gsub(/%/, "", $5); print $5 }') || return 3
printf 'disco %s al %s%% (aviso %s%%, critico %s%%)\n' "$ruta" "$uso" "$aviso" "$critico"
(( uso >= critico )) && return 2
(( uso >= aviso )) && return 1
return 0
}El gsub(/%/, "", $5) quita el símbolo de porcentaje para poder comparar como número (06-01), y el || return 3 distingue «no he podido medirlo» de «está mal»: son cosas muy distintas, y confundirlas produce alertas falsas cada vez que df tarda o la ruta no existe. Con esta forma, añadir una comprobación nueva es escribir una función más y añadirla a una lista.
- Umbrales, ruido, histéresis y estado
Aquí se decide si tu monitorización se usa o se ignora. Una comprobación que se ejecuta cada 5 minutos y avisa cada vez genera 288 mensajes al día por un solo problema; a la segunda vez que eso ocurre, el equipo crea un filtro de correo y deja de leer las alertas, incluidas las buenas. Dos mecanismos lo evitan.
Histéresis: umbrales distintos para activar y para desactivar. Si avisas al 85 % de disco y das por resuelto por debajo del 85 %, un disco oscilando entre 84,9 % y 85,1 % generará una alerta cada cinco minutos; con activación al 85 % y resolución al 80 %, hace falta una mejora real para que se apague. Estado: recordar en un fichero qué alertas ya están enviadas, y avisar solo en las transiciones.
stateDiagram-v2
[*] --> Normal
Normal --> Alertando: check falla (>= umbral critico)
note right of Alertando: se envia el aviso UNA vez
Alertando --> Alertando: sigue fallando (silencio)
Alertando --> Normal: check ok (< umbral de resolucion)
note left of Normal: se envia "RESUELTO" una vez
notifica_transicion() { # <nombre> <codigo> <mensaje>
local nombre="$1" codigo="$2" mensaje="$3" marca="$ESTADO_DIR/$nombre"
mkdir -p "$ESTADO_DIR"
if (( codigo == 0 )); then
[[ -e $marca ]] && { rm -f "$marca"; avisa RESUELTO "$nombre: $mensaje"; }
veloz_log_info "$nombre: OK ($mensaje)"
elif [[ ! -e $marca ]]; then # transicion de normal a alerta: se avisa
printf '%s %s\n' "$(date -Is)" "$codigo" > "$marca"; avisa ALERTA "$nombre: $mensaje"
else
veloz_log_warn "$nombre: sigue mal ($mensaje); aviso ya enviado"
fi
}Fíjate en que también se avisa al resolverse: una alerta que nunca se cierra deja al equipo sin saber si el problema sigue vivo, y es tan inútil como no avisar. La marca guarda además la hora de inicio, lo que permite responder después «¿cuánto duró la incidencia?».
- Comprobaciones típicas de Veloz Envíos
Con el patrón del apartado 6, la batería completa cabe en pocas líneas porque toda la maquinaria ya está construida en módulos anteriores:
check_proceso() { printf 'proceso veloz-api\n'; pgrep -x veloz-api > /dev/null; } # 05-02
check_puerto() { printf 'puerto 8080\n'; veloz_puerto_abierto localhost 8080; } # 06-04
check_carga() { # carga media de 1 minuto por nucleo
local umbral="$1" carga nucleos
read -r carga _ < /proc/loadavg; nucleos=$(nproc) # 06-03
printf 'carga %s con %s nucleos\n' "$carga" "$nucleos"
awk -v c="$carga" -v n="$nucleos" -v u="$umbral" 'BEGIN { exit !(c/n >= u) }' && return 2 || return 0
}
check_api() { # la API responde y se declara sana
local cuerpo
cuerpo=$(timeout 15s veloz_api_get /salud) || { printf 'API sin respuesta\n'; return 2; }
printf 'API dice %s\n' "$(jq -r '.estado // "?"' <<< "$cuerpo")" # 06-05
jq -e '.estado == "ok"' <<< "$cuerpo" > /dev/null && return 0 || return 1
}
check_incidencias() { # envios en estado incidencia hoy
local umbral="$1" n
n=$(awk -F, -v h="$(date +%F)" '$2 ~ h && $5 == "incidencia" { c++ } END { print c + 0 }' \
/srv/veloz/datos/envios.csv) || return 3 # 06-01
printf 'incidencias hoy: %s (umbral %s)\n' "$n" "$umbral"
(( n >= umbral * 2 )) && return 2; (( n >= umbral )) && return 1; return 0
}
check_respaldo() { # el respaldo de anoche existe y es reciente
local ultimo
ultimo=$(find /respaldos/diarios -mindepth 1 -maxdepth 1 -type d -mtime -1 | head -1)
printf 'ultimo respaldo: %s\n' "${ultimo:-ninguno en 24h}"
[[ -n $ultimo ]]
}Falta una más, check_errores_log, que cuenta los [ERROR] recientes de /var/log/veloz/app.log comparando la marca de tiempo con $(date -d "-15 minutes" '+%Y-%m-%d %H:%M:%S') dentro de awk (04-06): 1 o más es WARNING, 10 o más es CRITICAL. Y check_respaldo es especialmente valioso y suele faltar: detecta la tarea que no se ejecutó, un fallo que ningún log revela porque no hay nada que escribir cuando nada ocurre. Es la aplicación práctica del fichero de estado JSON de 07-02.
- Canales de aviso y bucle frente a ejecución periódica
Tres formas de que la alerta llegue a una persona, y la buena costumbre de usar varias:
avisa() { # avisa <ALERTA|RESUELTO> <texto>
local tipo="$1" texto="$2"
logger -t veloz-vigilante -p user.err "$tipo $texto"
[[ -n ${VELOZ_ALERTA_CORREO:-} ]] && printf '%s\n' "$texto" | mail -s "[Veloz $tipo]" "$VELOZ_ALERTA_CORREO"
[[ -n ${VELOZ_ALERTA_WEBHOOK:-} ]] && { jq -n --arg t "[$tipo] $texto" '{text: $t}' |
curl -sS -m 10 -H 'Content-Type: application/json' -d @- "$VELOZ_ALERTA_WEBHOOK" > /dev/null ||
veloz_log_warn "no se pudo entregar el aviso por webhook"; }
return 0
}El webhook aplica lo de 06-05: el cuerpo JSON se construye con jq -n --arg en vez de interpolar el texto en una plantilla —un mensaje con comillas o un salto de línea rompería el JSON—, y -d @- se lo pasa a curl por la entrada estándar. El -m 10 es obligatorio: un chat que no responda no puede colgar el vigilante (07-02). Y fíjate en que un fallo al entregar el aviso no aborta el script, porque un destinatario caído no debe impedir que las demás comprobaciones se ejecuten. El destino de las alertas vive en etc/veloz-ops.conf con permisos 600, nunca en el código. Última decisión de diseño: la tentación de escribir while true; do comprobar; sleep 300; done es casi siempre peor idea que programar la ejecución cada 5 minutos. Si el proceso muere, el bucle deja de vigilar y nadie lo nota, mientras que la siguiente ejecución periódica llega igual; una fuga de memoria se acumula durante semanas frente a un proceso corto que empieza limpio; al reiniciar el servidor hay que relanzarlo a mano; y al cambiar el código hay que reiniciarlo. El bucle solo se justifica con granularidad por debajo del minuto o con estado en memoria caro de reconstruir —y en ese caso conviértelo en un servicio de systemd con Restart=on-failure, que es el tema de 07-05—. Para todo lo demás: ejecución periódica, estado en un fichero, proceso corto.
- Aplicación: nace
vigilante.sh
vigilante.sh#!/usr/bin/env bash
# vigilante.sh — Bateria de comprobaciones de Veloz Envios.
# CUANDO: cada 5 minutos | LOG: ~/veloz-ops/logs/vigilante.log
# Codigos: 0 todo OK | 1 algun WARNING | 2 algun CRITICAL
set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export LC_ALL=C
readonly BASE="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
. "$BASE/lib/comun.sh"
[[ -r $BASE/etc/veloz-ops.conf ]] && . "$BASE/etc/veloz-ops.conf"
: "${VELOZ_LOG_FICHERO:=$BASE/logs/vigilante.log}"
VELOZ_COMPONENTE=vigilante
readonly ESTADO_DIR="$BASE/logs/estado"
readonly -a COMPROBACIONES=(
"disco:check_disco /srv 85 95" "carga:check_carga 2" "proceso:check_proceso"
"puerto:check_puerto" "api:check_api" "errores:check_errores_log 15"
"incidencias:check_incidencias ${UMBRAL_INCIDENCIAS:-20}" "respaldo:check_respaldo"
)
main() {
exec 9>/var/lock/veloz-vigilante.lock
flock -n 9 || { veloz_log_info "otra vigilancia en curso; salgo"; exit 0; }
local peor=0 entrada nombre orden salida codigo
for entrada in "${COMPROBACIONES[@]}"; do
nombre="${entrada%%:*}"; orden="${entrada#*:}" # 04-04
salida=$(timeout 30s bash -c "$orden" 2>&1) && codigo=0 || codigo=$?
(( codigo == 124 )) && { salida="agotado el tiempo de espera"; codigo=3; }
notifica_transicion "$nombre" "$codigo" "$salida"
(( codigo > peor && codigo != 3 )) && peor=$codigo
done
veloz_log_info "ciclo completado, peor estado: $peor"
return "$peor"
}
main "$@"Las decisiones de diseño, una por una. Las comprobaciones viven en un array de cadenas nombre:orden para poder añadir una sin tocar main, y el nombre se separa con las expansiones de 04-04. Cada una va envuelta en timeout 30s, porque una sola colgada no puede impedir que se ejecuten las demás. El idioma … && codigo=0 || codigo=$? captura el código sin que set -e aborte el bucle. El código 3 (UNKNOWN) no empeora el resultado global, para que un fallo de medición no dispare una alarma crítica. Y notifica_transicion garantiza que cada incidencia se avise una sola vez, y su resolución también. En el crontab va cada 5 minutos con flock -n y redirección al log. Esta es la versión funcional; el proyecto 09-04 profundiza en la parte de red, con vigilancia de varios destinos, medición de latencia e informe histórico.
Errores Comunes y Consejos
echosin marca de tiempo ni nivel, o log a stdout. Lo primero no responde ninguna pregunta útil dentro de un mes; lo segundo contamina la salida que otro proceso podría consumir.- No rotar. Un disco lleno por logs tumba el servidor y, de paso, la tarea que iba a avisarte.
copytruncatepor defecto. Innecesario para scripts que abren y cierran, y pierde líneas. Usacreatesalvo con procesos de larga duración.- Avisar en cada ciclo, o no avisar de la resolución. 288 mensajes al día por un solo problema hacen que el equipo filtre tus alertas; y una alerta que nunca se cierra deja a todos sin saber si sigue viva.
- Confundir «no he podido medir» con «está mal». Devuelve 3 (
UNKNOWN) y no lo cuentes como crítico. - Un umbral único para activar y resolver, o comprobaciones sin
timeout. Lo primero produce parpadeo (usa histéresis); lo segundo deja que una comprobación colgada impida las demás. - Datos personales o credenciales en el log. Se copian, se comparten y se conservan meses (08-03).
- Consejo: por cada alerta que vayas a crear, pregúntate «si suena a las 3 de la madrugada, ¿hay algo que hacer?». Si la respuesta es no, no es una alerta: es una métrica, y su sitio es un panel, no tu teléfono.
Ejercicios
Ejercicio 1. Escribe la configuración de logrotate para /var/log/veloz/app.log, que mantiene abierto el proceso veloz-api: rotación diaria, 30 copias, comprimidas salvo la más reciente, sin fallar si el fichero no existe, y con la estrategia correcta para un proceso que no sabe reabrir. Justifica la elección.
Ejercicio 2. Escribe check_memoria siguiendo el patrón 0/1/2/3: avisa (1) si la memoria disponible baja del 20 % del total y es crítico (2) por debajo del 10 %, imprimiendo una línea de resumen.
Soluciones
Solución 1.
# /etc/logrotate.d/veloz-api
/var/log/veloz/app.log /var/log/veloz/acceso.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
}La directiva clave es la última. copytruncate es la elección correcta porque veloz-api mantiene el fichero abierto y no sabe reabrirlo: con create, el proceso seguiría escribiendo en el inodo renombrado a .1 y el fichero nuevo quedaría vacío para siempre. El precio es una ventana mínima entre copiar y truncar en la que se pueden perder líneas, y se acepta porque la alternativa es perderlas todas. Si veloz-api admitiera una recarga, la opción superior sería create más postrotate systemctl reload veloz-api; endscript. delaycompress deja el .1 sin comprimir, lo que evita comprimir un fichero que todavía podría recibir escrituras rezagadas.
Solución 2.
check_memoria() { # check_memoria [aviso%] [critico%]
local aviso="${1:-20}" critico="${2:-10}" total disponible pct
read -r total disponible < <(awk '/^MemTotal:/ { t = $2 }
/^MemAvailable:/ { d = $2 } END { print t, d }' /proc/meminfo) || return 3
(( total > 0 )) || return 3
pct=$(( disponible * 100 / total ))
printf 'memoria disponible %s%% (%s de %s kB)\n' "$pct" "$disponible" "$total"
(( pct < critico )) && return 2
(( pct < aviso )) && return 1
return 0
}Se lee MemAvailable y no MemFree (06-03): MemFree excluye la memoria de caché, que el núcleo cede al instante si hace falta, así que un servidor sano puede tener casi cero «libre» y estar perfectamente; MemAvailable es la estimación real de lo que un proceso nuevo podría usar. Los dos valores se extraen en una sola pasada de awk, y (( total > 0 )) protege de una división por cero que devuelve 3 (UNKNOWN) en lugar de reventar.
Conclusión
Del registro: echo no basta porque no dice cuándo, quién ni con qué gravedad; diseña una línea con marca date -Is, nivel, componente y mensaje en pares clave=valor, e impleméntala en una veloz_log con niveles DEBUG/INFO/WARN/ERROR, umbral configurable y salida a stderr y opcionalmente a fichero. Añade logger -t -p para que los eventos importantes lleguen al syslog o al journal, donde journalctl -u, --since, -p err y -f los recuperan con filtros que un fichero plano no ofrece. Y rota, con un fichero en /etc/logrotate.d/ (daily, rotate, compress, delaycompress, missingok, notifempty), probándolo con logrotate -d: create para procesos que abren y cierran como tus scripts, copytruncate o una recarga para los que mantienen el fichero abierto, porque un proceso escribe en el inodo y no en el nombre.
De la monitorización: distingue comprobar de medir tendencia; escribe cada comprobación como una función homogénea que imprime una línea y devuelve 0/1/2/3 al estilo de los plugins clásicos, con 3 reservado para «no he podido medirlo»; define umbrales con histéresis y guarda estado en ficheros para avisar solo en las transiciones —y avisar también cuando se resuelve—; entrega por varios canales (logger, correo, webhook con jq -n y curl -m) sin que un canal caído aborte el ciclo; y prefiere la ejecución periódica al bucle infinito. vigilante.sh ya cubre disco, carga, proceso, puerto, API, incidencias, errores recientes y —lo que casi nadie vigila— que el respaldo de anoche exista.
El toolkit está completo y programado, pero cron empieza a quedarse corto: no sabe esperar a que la red esté lista, no recupera la ejecución perdida mientras el servidor estaba apagado, no aísla ni limita los recursos de una tarea, y su registro está disperso en cuatro ficheros. En 07-05 conocemos a systemd: unidades .service y .timer, OnCalendar validado con systemd-analyze calendar, Persistent=true para las ejecuciones perdidas, registro centralizado en el journal y una tabla honesta de cuándo merece la pena migrar y cuándo cron sigue siendo la respuesta correcta.
Curso de Programación en Bash
Módulo 1: Introducción a Bash
- ¿Qué es Bash?
- Configurando tu Entorno
- Navegación Básica en la Línea de Comandos
- Entendiendo el Shell
- Encontrar Ayuda: man, help y --help
Módulo 2: Comandos Básicos de Bash
- Operaciones con Archivos y Directorios
- Comandos de Procesamiento de Texto
- Permisos y Propiedad de Archivos
- Redirección y Tuberías
- Comodines y Expansión de Rutas
- Historial y Atajos de Teclado
Módulo 3: Fundamentos de Scripting
- Creando y Ejecutando un Script
- Variables y Constantes
- Operadores Básicos
- Sentencias Condicionales
- Argumentos y Entrada del Usuario
- Comillas, Expansión y Sustitución
Módulo 4: Scripting Intermedio
- Bucles en Bash
- Funciones en Bash
- Arrays y Arrays Asociativos
- Manipulación de Cadenas
- La Sentencia case y los Menús Interactivos
- Aritmética y Cálculos Numéricos
Módulo 5: Técnicas Avanzadas de Scripting
- Operaciones Avanzadas con Archivos
- Gestión de Procesos
- Manejo de Errores y Depuración
- Expresiones Regulares
- Entrada/Salida Avanzada: Descriptores y Here-Documents
- Scripts Modulares y Librerías Reutilizables
Módulo 6: Trabajando con Herramientas Externas
Módulo 7: Automatización y Programación
- Trabajos Cron
- Automatizando Tareas
- Scripts de Respaldo y Restauración
- Monitoreo y Registro
- Servicios y Temporizadores con systemd
- Automatización Remota con SSH
Módulo 8: Mejores Prácticas y Optimización
- Escribiendo Código Legible
- Optimizando Scripts en Bash
- Consideraciones de Seguridad
- Control de Versiones con Git
- Análisis Estático con ShellCheck y shfmt
- Pruebas Automatizadas con Bats
- Portabilidad: POSIX sh frente a Bashismos
