Volver a Spring Boot Intermedio
Spring Boot
Spring Boot Intermedio
Actuator y Observabilidad en Spring Boot
Spring Boot Actuator expone endpoints listos para producción: verificaciones de salud, métricas e información de ejecución.
Dependencias
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
Configuración de Actuator
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,loggers,caches,env
base-path: /actuator
endpoint:
health:
show-details: when-authorized
show-components: always
info:
env:
enabled: true
Endpoints Incorporados
| Endpoint | Descripción |
|---|---|
/actuator/health | Estado de salud de la aplicación |
/actuator/metrics | Lista de métricas disponibles |
/actuator/metrics/{nombre} | Valor de una métrica específica |
/actuator/env | Propiedades del entorno |
/actuator/loggers | Niveles de log (soporte de cambio en tiempo real) |
/actuator/prometheus | Métricas en formato Prometheus |
/actuator/info | Información de la aplicación |
/actuator/caches | Estadísticas de caché |
Indicador de Salud Personalizado
@Component
public class IndicadorSaludApiExterna implements HealthIndicator {
private final RestTemplate restTemplate;
@Override
public Health health() {
try {
ResponseEntity<String> response = restTemplate
.getForEntity("http://api-externa/ping", String.class);
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("url", "http://api-externa")
.withDetail("estado", response.getStatusCode())
.build();
}
return Health.down()
.withDetail("estado", response.getStatusCode())
.build();
} catch (Exception e) {
return Health.down()
.withException(e)
.withDetail("error", e.getMessage())
.build();
}
}
}
Métricas Personalizadas con Micrometer
@Service
public class ServicioPedidos {
private final Counter contadorPedidos;
private final Timer timerProcesamiento;
public ServicioPedidos(MeterRegistry registry, RepositorioPedidos repo) {
this.contadorPedidos = Counter.builder("pedidos.creados.total")
.description("Total de pedidos creados")
.tag("entorno", "produccion")
.register(registry);
this.timerProcesamiento = Timer.builder("pedido.procesamiento.duracion")
.description("Tiempo de procesamiento de pedidos")
.register(registry);
// Gauge refleja valor en vivo
Gauge.builder("pedidos.pendientes.cantidad", repo,
r -> r.contarPorEstado(Estado.PENDIENTE))
.description("Pedidos pendientes actuales")
.register(registry);
}
public Pedido crearPedido(CrearPedidoRequest request) {
contadorPedidos.increment();
return timerProcesamiento.record(() -> procesarPedido(request));
}
}
Logging Estructurado
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<!-- logback-spring.xml -->
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdc>true</includeMdc>
<customFields>{"servicio":"servicio-productos","entorno":"prod"}</customFields>
</encoder>
</appender>
// MDC para correlación de peticiones
@Component
public class FiltroLoggingMdc extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws Exception {
String traceId = Optional.ofNullable(request.getHeader("X-Trace-ID"))
.orElse(UUID.randomUUID().toString());
MDC.put("traceId", traceId);
MDC.put("usuarioId", extraerUsuarioId(request));
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
Configuración Prometheus + Grafana
# docker-compose.yml
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
# prometheus.yml
scrape_configs:
- job_name: 'spring-boot'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['host.docker.internal:8080']
scrape_interval: 15s
Información de la Aplicación
info:
app:
nombre: Servicio de Productos
version: '@project.version@'
descripcion: Gestiona el catálogo de productos
contacto:
equipo: equipo-backend
email: [email protected]
Seguridad para Actuator
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated());
return http.build();
}