Saltar al contenido principal

Quiz: Gestión de Excepciones y Logging

Ponga a prueba sus conocimientos sobre la gestión de errores y el logging.

30 preguntas - Haga clic en cada pregunta para revelar la respuesta.


Preguntas 1-15: Gestión de Excepciones​

Pregunta 1: ¿Qué es @ControllerAdvice?

Respuesta: Un componente que gestiona las excepciones globalmente para todos los controladores:

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(404).body(new ErrorResponse(ex.getMessage()));
}
}

Pregunta 2: ¿Cuál es la diferencia entre @ControllerAdvice y @RestControllerAdvice?

Respuesta:

  • @ControllerAdvice: puede devolver vistas u objetos
  • @RestControllerAdvice: equivale a @ControllerAdvice + @ResponseBody

Para APIs REST, utilice @RestControllerAdvice.


Pregunta 3: ¿Cómo crear una excepción personalizada?

Respuesta:

public class ResourceNotFoundException extends RuntimeException {

private final String resourceName;
private final Object resourceId;

public ResourceNotFoundException(String resourceName, Object resourceId) {
super(String.format("%s not found with id: %s", resourceName, resourceId));
this.resourceName = resourceName;
this.resourceId = resourceId;
}

// Getters
}

Pregunta 4: ¿Cómo mapear una excepción a un código HTTP?

Respuesta:

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(ex.getMessage());
}

// O con ResponseEntity
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(ex.getMessage()));
}

Pregunta 5: ¿Cómo gestionar los errores de validación?

Respuesta:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(
MethodArgumentNotValidException ex) {

Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);

return ResponseEntity.badRequest().body(errors);
}

Pregunta 6: ¿Qué es ProblemDetail (RFC 7807)?

Respuesta: Formato estándar para las respuestas de error HTTP:

@ExceptionHandler(ResourceNotFoundException.class)
public ProblemDetail handleNotFound(ResourceNotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
problem.setTitle("Resource Not Found");
problem.setProperty("resourceId", ex.getResourceId());
return problem;
}

Resultado JSON:

{
"type": "about:blank",
"title": "Resource Not Found",
"status": 404,
"detail": "User not found with id: 123",
"resourceId": 123
}

Pregunta 7: ¿Cómo gestionar todas las excepciones no controladas?

Respuesta:

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
log.error("Unexpected error", ex);

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("An unexpected error occurred"));
}

Pregunta 8: ¿Cómo agregar información a la respuesta de error?

Respuesta:

public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
private Map<String, Object> details;

// Constructores, getters
}

@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(
BusinessException ex, HttpServletRequest request) {

ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Business Rule Violation")
.message(ex.getMessage())
.path(request.getRequestURI())
.build();

return ResponseEntity.badRequest().body(error);
}

Pregunta 9: ¿Cómo dirigir un @ControllerAdvice a ciertos controladores?

Respuesta:

// Por anotación
@ControllerAdvice(annotations = RestController.class)

// Por paquete
@ControllerAdvice(basePackages = "com.example.api")

// Por clase
@ControllerAdvice(assignableTypes = {UserController.class, OrderController.class})

Pregunta 10: ¿Cómo gestionar las excepciones en los filtros?

Respuesta: Los filtros se ejecutan antes que los controladores, por lo que @ControllerAdvice no aplica:

public class ExceptionHandlerFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
try {
chain.doFilter(request, response);
} catch (JwtException ex) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Invalid token\"}");
}
}
}

Pregunta 11: ¿Cómo gestionar los errores async?

Respuesta:

@ControllerAdvice
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("Async error in {}: {}", method.getName(), ex.getMessage());
}
}

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
}

Pregunta 12: ¿Cómo lanzar un error 404 para un recurso no encontrado?

Respuesta:

@Service
public class UserService {

public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "User not found: " + id));
}
}

// O con excepción personalizada
throw new ResourceNotFoundException("User", id);

Pregunta 13: ¿Cómo desactivar la página de error por defecto de Spring Boot?

Respuesta:

server:
error:
whitelabel:
enabled: false

# O personalizar
include-message: always
include-binding-errors: always
include-stacktrace: never
include-exception: false

Pregunta 14: ¿Cómo gestionar los errores 404 globalmente?

Respuesta:

spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(NoHandlerFoundException ex) {
return ResponseEntity.status(404)
.body(new ErrorResponse("Endpoint not found: " + ex.getRequestURL()));
}

Pregunta 15: ¿Cuáles son las buenas prácticas para las excepciones?

Respuesta:

  1. Crear excepciones de negocio específicas
  2. No exponer detalles técnicos al cliente
  3. Registrar las excepciones del servidor
  4. Utilizar códigos HTTP apropiados
  5. Formato de respuesta coherente
  6. Incluir un ID de correlación para depuración

Preguntas 16-30: Logging​

Pregunta 16: ¿Qué framework de logging utiliza Spring Boot por defecto?

Respuesta: SLF4J como fachada con Logback como implementación.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger log = LoggerFactory.getLogger(MyClass.class);

O con Lombok: @Slf4j


Pregunta 17: ¿Cuáles son los niveles de log?

Respuesta: De menor a mayor verbosidad:

  1. ERROR: errores críticos
  2. WARN: situaciones potencialmente problemáticas
  3. INFO: información general
  4. DEBUG: detalles para depuración
  5. TRACE: muy detallado
log.error("Payment failed: {}", ex.getMessage());
log.warn("Slow query: {} ms", duration);
log.info("User {} logged in", username);
log.debug("Processing item: {}", item);
log.trace("Entering method with args: {}", Arrays.toString(args));

Pregunta 18: ¿Cómo configurar los niveles de log?

Respuesta:

logging:
level:
root: INFO
com.example: DEBUG
org.springframework.security: TRACE
org.hibernate.SQL: DEBUG

Pregunta 19: ¿Por qué usar los placeholders en vez de concatenación?

Respuesta: Rendimiento: el mensaje solo se construye si el nivel está activo:

// Correcto - evaluación perezosa
log.debug("Processing user: {}", user);

// Incorrecto - siempre evaluado
log.debug("Processing user: " + user.toString());

Pregunta 20: ¿Cómo configurar Logback con un archivo XML?

Respuesta: Crear src/main/resources/logback-spring.xml:

<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>

Pregunta 21: ¿Cómo escribir los logs en JSON?

Respuesta: Con Logstash encoder:

<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

Pregunta 22: ¿Qué es el MDC (Mapped Diagnostic Context)?

Respuesta: Contexto thread-local para añadir información a los logs:

// Añadir al inicio de la petición
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("userId", user.getId().toString());

// En el pattern
<pattern>%d [%X{requestId}] [%X{userId}] %msg%n</pattern>

// Limpiar al final
MDC.clear();

Pregunta 23: ¿Cómo implementar un filtro MDC?

Respuesta:

@Component
public class MdcFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
try {
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("clientIp", request.getRemoteAddr());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}

Pregunta 24: ¿Cómo configurar diferentes logs por perfil?

Respuesta:

<!-- logback-spring.xml -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>

<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="JSON"/>
</root>
</springProfile>

Pregunta 25: ¿Cómo ocultar datos sensibles en los logs?

Respuesta:

// No registrar datos sensibles
log.info("User login: {}", user.getEmail()); // ¡No la contraseña!

// Ocultar parcialmente
String maskedCard = card.substring(0, 4) + "****" + card.substring(12);
log.info("Payment with card: {}", maskedCard);

// Patrón de ocultamiento en logback
<pattern>%replace(%msg){'password=[^,]*','password=***'}%n</pattern>

Pregunta 26: ¿Cómo registrar las solicitudes HTTP?

Respuesta:

@Component
public class RequestLoggingFilter extends CommonsRequestLoggingFilter {

public RequestLoggingFilter() {
setIncludeQueryString(true);
setIncludePayload(true);
setMaxPayloadLength(10000);
setIncludeHeaders(false);
}
}

O activar en la configuración:

logging:
level:
org.springframework.web.filter.CommonsRequestLoggingFilter: DEBUG

Pregunta 27: ¿Cómo registrar las solicitudes SQL?

Respuesta:

logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE # Parámetros

# O con Spring
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true

Pregunta 28: ¿Cómo archivar y comprimir los logs?

Respuesta:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>

Pregunta 29: ¿Cómo enviar los logs a un servicio externo?

Respuesta: Con appenders especializados:

<!-- Logstash -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5000</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

<!-- O async para el rendimiento -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="LOGSTASH"/>
</appender>

Pregunta 30: ¿Cuáles son las buenas prácticas de logging?

Respuesta:

  1. Utilizar el nivel de log adecuado
  2. Incluir el contexto (userId, requestId)
  3. No registrar datos sensibles
  4. Utilizar los placeholders
  5. Registrar las excepciones con el stacktrace
  6. Logs estructurados en producción (JSON)
  7. Rotación y archivado
  8. Centralización para microservicios

Puntuación​

PuntuaciónNivel
27-30Excelente - Excepciones y logging dominados
21-26Bien - Algunas revisiones
15-20Medio - Repase los capítulos
Menos de 15A revisar

Volver al Módulo 10