Blog de Programación sobre Java y Javascript
 
Logging Avanzado con Log4j2 y SLF4J usando Spring

Logging Avanzado con Log4j2 y SLF4J usando Spring

Introducción

El logging avanzado con Log4j2 y SLF4J en Spring Boot representa una competencia esencial para desarrolladores que construyen aplicaciones empresariales modernas. En el ecosistema actual de microservicios y arquitecturas distribuidas, contar con un sistema de logging robusto no es opcional, es fundamental para el éxito operacional.

Spring Boot, con su filosofía de configuración automática, simplifica significativamente la implementación de logging avanzado, mientras que la combinación de Log4j2 y SLF4J proporciona un rendimiento excepcional y flexibilidad sin precedentes. Las anotaciones de Spring Boot añaden una capa adicional de simplicidad, permitiendo configurar comportamientos complejos de logging con minimal código.

En esta guía completa, exploraremos desde la configuración básica hasta técnicas avanzadas de logging con anotaciones en Spring Boot, incluyendo ejemplos prácticos, configuraciones optimizadas y patrones empresariales que transformarán la observabilidad de tus aplicaciones Java.

Log4j2 y SLF4J

¿Qué es el Logging Avanzado con Log4j2 y SLF4J?

El logging avanzado en Spring Boot combina la potencia del framework Spring Boot con las capacidades avanzadas de Log4j2 y la abstracción elegante de SLF4J. Spring Boot proporciona autoconfiguración inteligente, perfiles específicos para diferentes entornos y integración seamless con el ecosistema Spring.

Componentes Principales de Log4j2 y SLF4J

Auto-configuration: Configuración automática basada en dependencias

Spring Boot Actuator: Endpoints para monitoreo y gestión de logs

@Slf4j: Anotación de Lombok para inyección automática de loggers

Configuración por Perfiles: Diferentes configuraciones para dev, test, prod

Configuración Inicial con Spring Boot

<!--
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
-->

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class LoggingAvanzadoApplication {
    
    public static void main(String[] args) {
        log.info("Iniciando aplicación con logging avanzado");
        SpringApplication.run(LoggingAvanzadoApplication.class, args);
        log.info("Aplicación iniciada exitosamente");
    }
}

Importancia y Beneficios del Logging Avanzado en Spring Boot

Ventajas de la Integración Spring Boot + Log4j2

Autoconfiguración Inteligente: Spring Boot detecta automáticamente Log4j2 en el classpath y configura el logging apropiadamente, eliminando configuración manual compleja.

Perfiles de Configuración: Diferentes configuraciones de logging para desarrollo, testing y producción, gestionadas transparentemente por Spring Boot.

Integración con Actuator: Endpoints REST para cambiar niveles de logging en tiempo de ejecución sin reiniciar la aplicación.

@Slf4j
@RestController
@RequestMapping("/api/pedidos")
public class PedidoController {
    
    @Autowired
    private PedidoService pedidoService;
    
    @PostMapping
    public ResponseEntity<PedidoResponse> crearPedido(@RequestBody PedidoRequest request) {
        log.info("Recibida solicitud de creación de pedido: {}", request.getId());
        
        try {
            PedidoResponse response = pedidoService.procesarPedido(request);
            log.info("Pedido {} creado exitosamente", response.getId());
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("Error procesando pedido {}: {}", request.getId(), e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

Beneficios de las Anotaciones de Logging

@Slf4j de Lombok: Inyección automática del logger, eliminando código boilerplate repetitivo.

@Timed de Micrometer: Medición automática de tiempo de ejecución con logging integrado.

@EventListener: Logging automático de eventos del contexto de Spring.

Configuración Avanzada de Log4j2 y SLF4J con Perfiles de Spring Boot

application.yml por Entornos
# application.yml - Configuración base
spring:
  application:
    name: logging-avanzado-app
  profiles:
    active: dev

logging:
  config: classpath:log4j2-${spring.profiles.active}.xml
  
# Configuración de Actuator para gestión de logs
management:
  endpoints:
    web:
      exposure:
        include: loggers, health, info, metrics
  endpoint:
    loggers:
      enabled: true

---
# application-dev.yml
spring:
  config:
    activate:
      on-profile: dev

logging:
  level:
    com.empresa: DEBUG
    org.springframework: INFO
    org.hibernate: DEBUG

---
# application-prod.yml  
spring:
  config:
    activate:
      on-profile: prod

logging:
  level:
    com.empresa: INFO
    org.springframework: WARN
    org.hibernate: WARN

Configuración Log4j2 Específica para Spring Boot

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Properties>
        <Property name="APP_NAME">${spring:spring.application.name}</Property>
        <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [${APP_NAME}] [%t] %-5level %logger{36} - %msg%n</Property>
        <Property name="LOG_PATH">./logs</Property>
    </Properties>
    
    <Appenders>
        <!-- Console Appender para desarrollo -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>
        
        <!-- File Appender con Spring Boot properties -->
        <RollingFile name="FileAppender" 
                     fileName="${LOG_PATH}/${APP_NAME}.log"
                     filePattern="${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
        
        <!-- Async Appender para producción -->
        <AsyncAppender name="AsyncFile" bufferSize="8192">
            <AppenderRef ref="FileAppender"/>
            <IncludeLocation>false</IncludeLocation>
        </AsyncAppender>
        
        <!-- JSON Appender para ELK Stack -->
        <RollingFile name="JsonAppender"
                     fileName="${LOG_PATH}/${APP_NAME}-json.log"
                     filePattern="${LOG_PATH}/${APP_NAME}-json-%d{yyyy-MM-dd}-%i.log.gz">
            <JsonTemplateLayout eventTemplateUri="classpath:JsonLayout.json"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="50MB"/>
            </Policies>
        </RollingFile>
    </Appenders>
    
    <Loggers>
        <!-- Spring Boot specific loggers -->
        <Logger name="org.springframework.boot" level="INFO"/>
        <Logger name="org.springframework.web" level="DEBUG"/>
        <Logger name="org.springframework.security" level="DEBUG"/>
        
        <!-- Application loggers -->
        <Logger name="com.empresa" level="${sys:logging.level.com.empresa:-INFO}"/>
        
        <Root level="INFO">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncFile"/>
            <AppenderRef ref="JsonAppender"/>
        </Root>
    </Loggers>
</Configuration>

Anotaciones Avanzadas para Logging en Spring Boot

@Slf4j y Configuración Automática
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

@Slf4j
@Service
public class PedidoService {
    
    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReady() {
        log.info("PedidoService listo para procesar pedidos");
    }
    
    public PedidoResponse procesarPedido(PedidoRequest request) {
        log.debug("Iniciando procesamiento de pedido: {}", request);
        
        // Validación con logging
        if (!validarPedido(request)) {
            log.warn("Pedido inválido recibido: {}", request.getId());
            throw new PedidoInvalidoException("Datos de pedido inválidos");
        }
        
        log.info("Pedido {} validado correctamente", request.getId());
        return procesarLogicaNegocio(request);
    }
    
    private boolean validarPedido(PedidoRequest request) {
        log.debug("Validando pedido con ID: {}", request.getId());
        return request.getId() != null && request.getMonto() > 0;
    }
}
@Timed para Medición de Rendimiento
import io.micrometer.core.annotation.Timed;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class AnalyticsService {
    
    @Timed(value = "analytics.procesamiento", description = "Tiempo de procesamiento de analytics")
    public AnalyticsResult procesarAnalytics(String data) {
        log.info("Iniciando análisis de datos");
        
        try {
            // Simulación de procesamiento costoso
            Thread.sleep(100);
            
            AnalyticsResult result = new AnalyticsResult();
            log.info("Análisis completado exitosamente");
            return result;
            
        } catch (Exception e) {
            log.error("Error en procesamiento de analytics", e);
            throw new AnalyticsException("Fallo en procesamiento", e);
        }
    }
}
Anotaciones Personalizadas para Logging
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
    String operation() default "";
    boolean logParameters() default false;
    boolean logResult() default false;
}

@Slf4j
@Aspect
@Component
public class LoggingAspect {
    
    @Around("@annotation(logExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String operation = logExecutionTime.operation().isEmpty() ? methodName : logExecutionTime.operation();
        
        // Log de parámetros si está habilitado
        if (logExecutionTime.logParameters()) {
            log.debug("Ejecutando {} con parámetros: {}", operation, Arrays.toString(joinPoint.getArgs()));
        }
        
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = joinPoint.proceed();
            long executionTime = System.currentTimeMillis() - startTime;
            
            log.info("Operación {} completada en {}ms", operation, executionTime);
            
            // Log del resultado si está habilitado
            if (logExecutionTime.logResult()) {
                log.debug("Resultado de {}: {}", operation, result);
            }
            
            return result;
            
        } catch (Exception e) {
            long executionTime = System.currentTimeMillis() - startTime;
            log.error("Operación {} falló después de {}ms: {}", operation, executionTime, e.getMessage(), e);
            throw e;
        }
    }
}

// Uso de la anotación personalizada
@Slf4j
@Service
public class FacturacionService {
    
    @LogExecutionTime(operation = "generacion-factura", logParameters = true, logResult = true)
    public Factura generarFactura(PedidoRequest pedido) {
        // Lógica de generación de factura
        return new Factura();
    }
}

Casos de Uso Avanzados de Log4j2 y SLF4J en Spring Boot

Sistema de Auditoría con Spring Events
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

// Evento personalizado de auditoría
public class AuditoriaEvent {
    private final String usuario;
    private final String accion;
    private final String recurso;
    private final LocalDateTime timestamp;
    
    public AuditoriaEvent(String usuario, String accion, String recurso) {
        this.usuario = usuario;
        this.accion = accion;
        this.recurso = recurso;
        this.timestamp = LocalDateTime.now();
    }
    
    // Getters...
}

@Slf4j
@Component
public class AuditoriaListener {
    
    private static final Logger auditLogger = LoggerFactory.getLogger("AUDIT");
    
    @EventListener
    public void handleAuditoriaEvent(AuditoriaEvent event) {
        auditLogger.info("AUDIT_EVENT - Usuario: {}, Acción: {}, Recurso: {}, Timestamp: {}", 
                        event.getUsuario(), event.getAccion(), event.getRecurso(), event.getTimestamp());
    }
}

@Slf4j
@RestController
public class SecureController {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @PostMapping("/api/secure/operation")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<String> operacionSegura(@AuthenticationPrincipal UserDetails user) {
        log.info("Operación segura solicitada por usuario: {}", user.getUsername());
        
        // Publicar evento de auditoría
        eventPublisher.publishEvent(new AuditoriaEvent(
            user.getUsername(), 
            "OPERACION_SEGURA", 
            "/api/secure/operation"
        ));
        
        // Lógica de negocio
        return ResponseEntity.ok("Operación completada");
    }
}

Logging Distribuido con Sleuth y Zipkin

// Configuración en application.yml
/*
spring:
  sleuth:
    sampler:
      probability: 1.0
    zipkin:
      base-url: http://localhost:9411
  application:
    name: logging-service
*/

@Slf4j
@RestController
public class DistributedController {
    
    @Autowired
    private ExternalService externalService;
    
    @GetMapping("/api/distributed/{id}")
    public ResponseEntity<String> procesarDistribuido(@PathVariable String id) {
        log.info("Procesando solicitud distribuida para ID: {}", id);
        
        try {
            // El tracing se propaga automáticamente
            String result = externalService.llamarServicioExterno(id);
            log.info("Solicitud distribuida completada para ID: {}", id);
            return ResponseEntity.ok(result);
            
        } catch (Exception e) {
            log.error("Error en procesamiento distribuido para ID: {}", id, e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error procesando solicitud");
        }
    }
}

@Slf4j
@Service
public class ExternalService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    public String llamarServicioExterno(String id) {
        log.info("Llamando servicio externo para ID: {}", id);
        
        // Spring Sleuth propaga automáticamente el trace context
        String response = restTemplate.getForObject("http://external-service/api/data/" + id, String.class);
        
        log.info("Respuesta recibida del servicio externo para ID: {}", id);
        return response;
    }
}

Configuración de Health Checks con Logging usando Log4j2 y SLF4J

@Slf4j
@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        try {
            // Verificar estado del sistema
            boolean systemHealthy = checkSystemHealth();
            
            if (systemHealthy) {
                log.debug("Health check exitoso");
                return Health.up()
                    .withDetail("database", "connected")
                    .withDetail("external_service", "available")
                    .build();
            } else {
                log.warn("Health check falló - sistema no saludable");
                return Health.down()
                    .withDetail("error", "System unhealthy")
                    .build();
            }
            
        } catch (Exception e) {
            log.error("Error durante health check", e);
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
    }
    
    private boolean checkSystemHealth() {
        // Lógica de verificación de salud
        return true;
    }
}

Log4j2 y SLF4J: Errores Comunes en Spring Boot Logging y Soluciones

Error 1: Conflicto de Dependencias de Logging

Problema Común:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- No excluir spring-boot-starter-logging causa conflictos -->
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Solución Correcta:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Error 2: Configuración Incorrecta de Perfiles

Problema Común:

@Slf4j
@Configuration
public class LoggingConfig {
    
    @PostConstruct
    public void configureLogging() {
        // Configuración hardcodeada
        Configurator.setLevel("com.empresa", Level.DEBUG);
    }
}

Solución Correcta:

// ✅ Configuración consciente de perfiles
@Slf4j
@Configuration
public class LoggingConfig {
    
    @Value("${logging.level.com.empresa:INFO}")
    private String appLogLevel;
    
    @PostConstruct
    public void configureLogging() {
        log.info("Configurando logging con nivel: {}", appLogLevel);
        Configurator.setLevel("com.empresa", Level.valueOf(appLogLevel));
    }
}

Error 3: Mal Uso de Slf4j con Campos Estáticos

Problema Común:

// ❌ Uso incorrecto causando NullPointerException
@Slf4j
public class ProblematicService {
    
    static {
        log.info("Inicializando servicio"); // log es null aquí
    }
}

Solución Correcta:

@Slf4j
public class CorrectService {
    
    // Logger estático manual para uso en bloques static
    private static final Logger staticLog = LoggerFactory.getLogger(CorrectService.class);
    
    static {
        staticLog.info("Inicializando servicio correctamente");
    }
    
    public void metodoInstancia() {
        log.info("Usando @Slf4j en método de instancia"); // Funciona correctamente
    }
}

Buenas Prácticas para Spring Boot Logging

Configuración Reactiva de Logging
@Slf4j
@RestController
public class LoggingManagementController {
    
    @PostMapping("/admin/logging/level")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<String> cambiarNivelLogging(
            @RequestParam String logger, 
            @RequestParam String level) {
        
        try {
            LoggerContext context = (LoggerContext) LogManager.getContext(false);
            LoggerConfig loggerConfig = context.getConfiguration().getLoggerConfig(logger);
            loggerConfig.setLevel(Level.valueOf(level.toUpperCase()));
            context.updateLoggers();
            
            log.info("Nivel de logging cambiado - Logger: {}, Nuevo nivel: {}", logger, level);
            return ResponseEntity.ok("Nivel de logging actualizado");
            
        } catch (Exception e) {
            log.error("Error cambiando nivel de logging", e);
            return ResponseEntity.badRequest().body("Error: " + e.getMessage());
        }
    }
}
Integración con Spring Boot Metrics
@Slf4j
@Component
public class LoggingMetricsConfig {
    
    private final Counter errorLogCounter;
    private final Counter warnLogCounter;
    private final Timer operationTimer;
    
    public LoggingMetricsConfig(MeterRegistry meterRegistry) {
        this.errorLogCounter = Counter.builder("logs.errors")
            .description("Contador de logs de error")
            .tag("application", "logging-service")
            .register(meterRegistry);
            
        this.warnLogCounter = Counter.builder("logs.warnings")
            .description("Contador de logs de warning")
            .register(meterRegistry);
            
        this.operationTimer = Timer.builder("operations.duration")
            .description("Duración de operaciones")
            .register(meterRegistry);
    }
    
    @EventListener
    public void handleLogEvent(LogEvent event) {
        if (event.getLevel().equals(Level.ERROR)) {
            errorLogCounter.increment();
        } else if (event.getLevel().equals(Level.WARN)) {
            warnLogCounter.increment();
        }
    }
}
Configuración de Logging para Testing
// Test configuration
@TestConfiguration
@Slf4j
public class TestLoggingConfig {
    
    @Bean
    @Primary
    public Logger testLogger() {
        return LoggerFactory.getLogger("TEST");
    }
}

@SpringBootTest
@ActiveProfiles("test")
@Slf4j
class PedidoServiceTest {
    
    @Autowired
    private PedidoService pedidoService;
    
    @Test
    void testProcesarPedido() {
        log.info("Iniciando test de procesamiento de pedido");
        
        PedidoRequest request = new PedidoRequest();
        request.setId("TEST-001");
        request.setMonto(100.0);
        
        PedidoResponse response = pedidoService.procesarPedido(request);
        
        assertThat(response).isNotNull();
        log.info("Test completado exitosamente");
    }
}

Integración con Herramientas de Monitoreo Enterprise

Configuración para Logstash y ELK Stack
# logback-spring.xml específico para Spring Boot
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProfile name="prod">
        <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
            <destination>logstash-server:5000</destination>
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp/>
                    <version/>
                    <logLevel/>
                    <message/>
                    <mdc/>
                    <arguments/>
                    <stackTrace/>
                    <pattern>
                        <pattern>
                            {
                                "application": "${spring.application.name:-}",
                                "profile": "${spring.profiles.active:-}",
                                "traceId": "%X{traceId:-}",
                                "spanId": "%X{spanId:-}"
                            }
                        </pattern>
                    </pattern>
                </providers>
            </encoder>
        </appender>
        
        <root level="INFO">
            <appender-ref ref="LOGSTASH"/>
        </root>
    </springProfile>
</configuration>

Configuración con Micrometer y Prometheus

@Slf4j
@Configuration
@EnableConfigurationProperties(LoggingProperties.class)
public class ObservabilityConfig {
    
    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
    
    @Bean
    public CountedAspect countedAspect(MeterRegistry registry) {
        return new CountedAspect(registry);
    }
    
    @EventListener
    public void handleApplicationEvent(ApplicationReadyEvent event) {
        log.info("Aplicación lista - Métricas de observabilidad configuradas");
    }
}

@ConfigurationProperties(prefix = "app.logging")
@Data
public class LoggingProperties {
    private boolean metricsEnabled = true;
    private String logstashHost = "localhost";
    private int logstashPort = 5000;
    private Map<String, String> customFields = new HashMap<>();
}

El logging avanzado con Log4j2 y SLF4J en Spring Boot representa una inversión estratégica fundamental para el desarrollo de aplicaciones empresariales robustas y observables. A través de esta guía completa, hemos explorado desde configuraciones básicas con anotaciones hasta implementaciones sofisticadas que incluyen auditoría automática, monitoreo distribuido y integración seamless con herramientas de observabilidad modernas.

La combinación de Spring Boot con Log4j2, SLF4J y anotaciones especializadas proporciona un ecosistema poderoso que simplifica significativamente la implementación de logging enterprise-grade. Las técnicas de configuración por perfiles, el uso inteligente de anotaciones como @Slf4j y @Timed, y la integración con Spring Boot Actuator transforman radicalmente la capacidad de diagnóstico y monitoreo de aplicaciones en producción.

Implementar estas prácticas avanzadas no solo mejorará la observabilidad de tus aplicaciones Spring Boot, sino que también facilitará la colaboración entre equipos de desarrollo y operaciones, reduciendo significativamente el tiempo de resolución de incidencias y mejorando la experiencia operacional general.


¿Te gustó este contenido avanzado?

¡Siguenos por facebook para recibir más guías profundas sobre Spring Boot, arquitecturas de microservicios y observabilidad!