1. Estructura de un mensaje de log
Imaginemos que los logs no son solo «flujo de conciencia» de tu programa, sino un valioso cuaderno de bitácora en el que, dentro de un mes o un año, tú o tu colega podréis encontrar la respuesta a la pregunta: «¿Qué demonios pasó aquí?». Para que eso sea posible, cada mensaje de log debe estar estructurado. Por lo general (y este es el estándar en la mayoría de bibliotecas) cada mensaje contiene:
- Hora del evento — cuándo sucedió.
- Nivel — cuán importante es (INFO, ERROR, etc.).
- Nombre del logger — normalmente es el nombre de la clase o del componente.
- Texto del mensaje — qué ocurrió exactamente.
- Stack trace (si hay error) — para entender dónde y por qué.
He aquí un ejemplo de una línea de log bien formateada (con Log4j/SLF4J):
2024-06-16 18:42:07,123 INFO com.example.MainApp - El usuario inició sesión: username=user
Y si ocurrió un error:
2024-06-16 18:42:10,456 ERROR com.example.LoginService - Error de autorización del usuario: user
java.lang.IllegalArgumentException: Contraseña no válida
at com.example.LoginService.checkPassword(LoginService.java:42)
...
¿Por qué es importante?
Cuando una aplicación lleva ejecutándose mucho tiempo, los logs pueden ocupar gigabytes. Si los mensajes no están estructurados, encontrar un problema se convierte en algo así como «adivinar la canción por el ruido del ventilador».
2. Formateo de mensajes
Por qué no conviene hacerlo así:
logger.info("Usuario " + username + " ha iniciado sesión en el sistema");
Parece sencillo, pero hay trampa: incluso si el nivel de logging está en ERROR, la cadena dentro de los paréntesis se construirá igualmente (la concatenación se ejecuta), y eso es un gasto innecesario de recursos. En sistemas grandes, donde los logs generan miles de líneas por segundo, esto puede traducirse en latencias reales.
Cómo hacerlo bien: plantillas y parámetros
Las bibliotecas modernas (por ejemplo, SLF4J y Log4j 2) admiten plantillas con parámetros:
logger.info("El usuario {} ha iniciado sesión en el sistema", username);
Aquí la cadena solo se construirá si el nivel de logging permite emitir este mensaje. Si ahora está, por ejemplo, en WARN, ni siquiera se evaluará la cadena: ahorro de recursos y de nervios.
Bonus: si se pasan varios parámetros, se insertan en orden:
logger.info("El usuario {} realizó la acción {} sobre el objeto {}", username, action, objectId);
Registro de excepciones (stack trace)
Si capturas una excepción, no añadas manualmente el stack trace al mensaje:
// NO LO HAGAS:
logger.error("Error: " + ex.getMessage() + "\n" + Arrays.toString(ex.getStackTrace()));
Correcto:
logger.error("Error al procesar la solicitud", ex);
SLF4J y Log4j añadirán el stack trace al log de forma adecuada.
Ejemplo: comparación de enfoques
// Malo (la concatenación siempre se ejecuta)
logger.debug("Objeto: " + expensiveToString(obj));
// Bien (evaluación diferida)
logger.debug("Objeto: {}", obj);
3. Elección de niveles de logging
Si en tus logs todo está en nivel ERROR, eso ya no son logs, es una «luz roja». Si todo está en DEBUG, te ahogarás en detalles. Veamos cuándo usar cada nivel.
| Nivel | Para qué se utiliza | Ejemplo de mensaje |
|---|---|---|
|
Fallos críticos que hacen que el sistema funcione mal o deje de funcionar | «Error de conexión a la base de datos» |
|
Advertencias importantes que no son críticas pero requieren atención | «No se pudo encontrar al usuario; usando guest» |
|
Eventos normales que reflejan el funcionamiento habitual de la aplicación | «Usuario registrado: user» |
|
Información detallada para depuración; no necesaria en producción | «Se invocó el método checkPassword con parámetros ...» |
|
Información más detallada, normalmente para diagnóstico profundo | «Inicio del bucle de procesamiento: i=0» |
Ejemplos de mensajes típicos
- ERROR — no se pudo escribir el archivo, se capturó una excepción no controlada, el servicio no está disponible.
- WARN — API obsoleto, comportamiento sospechoso del usuario, se superó el límite de intentos.
- INFO — el usuario entró/salió, se completó el procesamiento de un pedido, arranque de la aplicación.
- DEBUG — parámetros de la petición, valores de variables, resultados intermedios de cálculos.
- TRACE — entrada/salida de métodos, bucles internos, detalles del funcionamiento de algoritmos.
Consejo:
En producción normalmente se activan solo INFO y superiores, a veces WARN y ERROR. DEBUG y TRACE — solo para buscar bugs complejos.
4. Best practices (mejores prácticas de logging)
No registres datos sensibles
Contraseñas, tokens, números de tarjetas de crédito — nada de esto debe estar en los logs. Incluso si piensas que «el archivo de log es solo para mí», recuerda el GDPR y al colega que, por accidente, enviará el log al chat general.
// Malo:
logger.info("El usuario {} inició sesión con contraseña {}", username, password);
// Bien:
logger.info("El usuario {} ha iniciado sesión en el sistema", username);
No abuses del nivel ERROR
Si lo escribes todo a través de logger.error, cuando de verdad ocurra una catástrofe nadie se dará cuenta: todos estarán acostumbrados a la «luz roja». Usa ERROR solo para situaciones en las que la aplicación realmente no puede continuar o se ha violado la lógica de negocio.
Registra las excepciones con el stack completo
No escribas solo ex.getMessage(), de lo contrario nunca sabrás dónde ocurrió exactamente el error. Pasa la excepción como segundo parámetro al logger.
logger.error("Error al procesar la solicitud", ex);
Usa identificadores únicos (correlación de eventos)
En sistemas grandes es útil asignar a cada petición, usuario u operación un identificador único. Esto ayuda a «coser» eventos de distintas partes del sistema.
logger.info("Se ha iniciado el procesamiento del pedido: orderId={}", orderId);
logger.info("Pedido procesado correctamente: orderId={}", orderId);
No lo registres todo
Si hay demasiados logs, se vuelven inútiles. No registres cada línea de código; de lo contrario, será imposible encontrar la información necesaria.
Formatea los mensajes de forma clara
Escribe mensajes para que no solo los entienda el autor del código, sino también la persona que leerá los logs dentro de seis meses. Evita abreviaturas, recortes no evidentes y «bromas internas».
5. Práctica: configurar el formato del log y los niveles
Ejemplo de configuración de formato en Log4j2 (log4j2.xml)
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
¿Qué significa esto?
- %d{...} — hora del evento.
- %-5level — nivel (ERROR, INFO, etc.).
- %logger{36} — nombre del logger (normalmente la clase).
- %msg — el propio mensaje.
Ejemplo de código con distintos niveles de logging (SLF4J)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogDemo {
private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);
public static void main(String[] args) {
logger.info("La aplicación se ha iniciado");
logger.debug("Valor de la variable x: {}", 42);
try {
throw new IllegalArgumentException("¡Ay, ay, ay!");
// ...
} catch (Exception ex) {
logger.error("Se produjo un error al iniciar", ex);
}
}
}
Demostración de la diferencia entre niveles
Si en la configuración del logger el nivel está en INFO, los mensajes con nivel DEBUG y por debajo no se mostrarán. Prueba a cambiar el nivel a debug en la configuración: verás más detalles.
6. Errores típicos
Error n.º 1: Concatenación de cadenas en los logs. Muy a menudo los principiantes escriben así:
logger.debug("Usuario: " + user.getName() + ", rol: " + user.getRole());
Como resultado, incluso con el nivel DEBUG desactivado, estas cadenas se construirán, lo que lleva a carga innecesaria. ¡Usa parámetros!
Error n.º 2: Registrar sin el stack de la excepción. Solo escriben el mensaje:
logger.error("Error: " + ex.getMessage());
Al final, en los logs no hay información sobre dónde ocurrió exactamente el error. ¡Pasa la excepción como segundo parámetro!
Error n.º 3: Registrar todo al nivel ERROR. Si todo está en rojo, nada está en rojo. Usa los niveles según su propósito; de lo contrario, los errores importantes se perderán entre las «menudencias».
Error n.º 4: Registrar datos sensibles. Nunca escribas en los logs contraseñas, tokens ni números de tarjetas. Aunque parezca que nadie lo verá, la vida da sorpresas.
Error n.º 5: Mensajes poco claros. Si el mensaje en el log se ve como «ERR42: fail», dentro de un mes no recordarás qué significa. Escribe de forma clara y detallada.
Error n.º 6: Ausencia de identificadores únicos. En sistemas complejos, sin orderId, userId y otros identificadores no podrás «coser» los eventos ni entender qué pasó con un usuario o pedido concreto.
GO TO FULL VERSION