google-site-verification: google61fe8ba583a51912.html
Airbrake Integration in Java: Complete Error Monitoring Guide

Airbrake provides powerful error monitoring and performance tracking for Java applications. This guide demonstrates comprehensive Airbrake integration for error tracking, performance monitoring, and deployment tracking.

Why Use Airbrake?

  • Error Monitoring: Real-time error tracking and alerting
  • Performance Insights: Application performance monitoring (APM)
  • Deployment Tracking: Track deployments and their impact
  • Smart Grouping: Intelligent error grouping and deduplication
  • Rich Context: Detailed error context and environment information

Prerequisites

  • Airbrake Account with project ID and API key
  • Java 8+ application
  • Maven/Gradle for dependency management

Step 1: Project Dependencies

Maven (pom.xml):

<dependencies> <!-- Airbrake Notifier --> <dependency> <groupId>io.airbrake</groupId> <artifactId>airbrake-java</artifactId> <version>2.2.10</version> </dependency> <!-- Airbrake Spring Boot Starter --> <dependency> <groupId>io.airbrake</groupId> <artifactId>airbrake-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.0</version> </dependency> <!-- Logging Bridge (Optional) --> <dependency> <groupId>io.airbrake</groupId> <artifactId>airbrake-logback</artifactId> <version>1.0.0</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies>

Step 2: Airbrake Configuration

application.yml:

# Airbrake Configuration airbrake: enabled: true project-id: ${AIRBRAKE_PROJECT_ID:} project-key: ${AIRBRAKE_API_KEY:} environment: ${AIRBRAKE_ENVIRONMENT:development} host: ${AIRBRAKE_HOST:https://api.airbrake.io} # Error reporting settings error-reporting: enabled: true async: true queue-size: 100 workers: 5 # Performance monitoring performance-stats: enabled: true host: ${AIRBRAKE_APM_HOST:https://api.airbrake.io} annotations: enabled: true # Deployment tracking deployment: enabled: true environment: ${AIRBRAKE_ENVIRONMENT:development} username: ${DEPLOYMENT_USER:system} repository: ${GIT_REPOSITORY:} revision: ${GIT_COMMIT:} # Filter configuration filters: - class: io.airbrake.javabrake.Filter enabled: true # HTTP settings http: connect-timeout: 5000 read-timeout: 10000 # Application info app: name: ${spring.application.name:unknown} version: ${app.version:1.0.0} environment: ${spring.profiles.active:local} # Logging (for Airbrake log appender) logging: level: io.airbrake: DEBUG config: classpath:logback-spring.xml

Step 3: Airbrake Configuration Classes

@Configuration @ConfigurationProperties(prefix = "airbrake") @Data @Slf4j public class AirbrakeConfig { private boolean enabled = false; private String projectId; private String projectKey; private String environment = "development"; private String host = "https://api.airbrake.io"; private ErrorReporting errorReporting = new ErrorReporting(); private PerformanceStats performanceStats = new PerformanceStats(); private Deployment deployment = new Deployment(); private Http http = new Http(); @Data public static class ErrorReporting { private boolean enabled = true; private boolean async = true; private int queueSize = 100; private int workers = 5; } @Data public static class PerformanceStats { private boolean enabled = true; private String host = "https://api.airbrake.io"; private boolean annotations = true; } @Data public static class Deployment { private boolean enabled = true; private String environment = "development"; private String username = "system"; private String repository; private String revision; private String version; } @Data public static class Http { private int connectTimeout = 5000; private int readTimeout = 10000; } public boolean isValid() { return enabled && projectId != null && !projectId.isEmpty() && projectKey != null && !projectKey.isEmpty(); } } @Configuration @Slf4j public class AirbrakeAutoConfiguration { private final AirbrakeConfig airbrakeConfig; private final ObjectMapper objectMapper; public AirbrakeAutoConfiguration(AirbrakeConfig airbrakeConfig, ObjectMapper objectMapper) { this.airbrakeConfig = airbrakeConfig; this.objectMapper = objectMapper; } @Bean @ConditionalOnProperty(name = "airbrake.enabled", havingValue = "true") public AirbrakeNotifier airbrakeNotifier() { if (!airbrakeConfig.isValid()) { log.warn("Airbrake configuration is invalid. Disabling Airbrake integration."); return null; } try { Config config = new Config(); config.setProjectId(Long.parseLong(airbrakeConfig.getProjectId())); config.setProjectKey(airbrakeConfig.getProjectKey()); config.setEnvironment(airbrakeConfig.getEnvironment()); config.setHost(airbrakeConfig.getHost()); // Configure async settings if (airbrakeConfig.getErrorReporting().isAsync()) { config.setAsyncEnabled(true); config.setAsyncQueueSize(airbrakeConfig.getErrorReporting().getQueueSize()); config.setAsyncWorkers(airbrakeConfig.getErrorReporting().getWorkers()); } // Configure HTTP settings config.setConnectTimeout(airbrakeConfig.getHttp().getConnectTimeout()); config.setReadTimeout(airbrakeConfig.getHttp().getReadTimeout()); AirbrakeNotifier notifier = new AirbrakeNotifier(config); // Add custom filters notifier.addFilter(this::filterSensitiveData); notifier.addFilter(this::addCustomContext); log.info("Airbrake Notifier initialized for project: {}", airbrakeConfig.getProjectId()); return notifier; } catch (Exception e) { log.error("Failed to initialize Airbrake Notifier", e); return null; } } @Bean @ConditionalOnBean(AirbrakeNotifier.class) public AirbrakePerformanceStats airbrakePerformanceStats() { try { Config config = new Config(); config.setProjectId(Long.parseLong(airbrakeConfig.getProjectId())); config.setProjectKey(airbrakeConfig.getProjectKey()); config.setHost(airbrakeConfig.getPerformanceStats().getHost()); AirbrakePerformanceStats stats = new AirbrakePerformanceStats(config); log.info("Airbrake Performance Stats initialized"); return stats; } catch (Exception e) { log.error("Failed to initialize Airbrake Performance Stats", e); return null; } } @Bean @ConditionalOnBean(AirbrakeNotifier.class) public AirbrakeService airbrakeService(AirbrakeNotifier notifier, AirbrakePerformanceStats performanceStats) { return new AirbrakeService(notifier, performanceStats, airbrakeConfig, objectMapper); } // Filter to remove sensitive data private Notice filterSensitiveData(Notice notice) { if (notice.getContext() != null && notice.getContext().getEnvironment() != null) { Map<String, String> env = notice.getContext().getEnvironment(); // Remove sensitive environment variables env.keySet().removeIf(key -> key.toLowerCase().contains("password") || key.toLowerCase().contains("secret") || key.toLowerCase().contains("key") || key.toLowerCase().contains("token") ); } // Filter sensitive parameters if (notice.getParams() != null) { notice.getParams().keySet().removeIf(key -> key.toLowerCase().contains("password") || key.toLowerCase().contains("credit_card") || key.toLowerCase().contains("ssn") ); } return notice; } // Filter to add custom context private Notice addCustomContext(Notice notice) { // Add application context if (notice.getContext() == null) { notice.setContext(new Context()); } // Add custom environment information Map<String, String> customEnv = new HashMap<>(); customEnv.put("java.version", System.getProperty("java.version")); customEnv.put("java.vm.name", System.getProperty("java.vm.name")); customEnv.put("os.name", System.getProperty("os.name")); customEnv.put("os.version", System.getProperty("os.version")); notice.getContext().setEnvironment(customEnv); return notice; } }

Step 4: Core Airbrake Service

@Service @Slf4j public class AirbrakeService { private final AirbrakeNotifier notifier; private final AirbrakePerformanceStats performanceStats; private final AirbrakeConfig config; private final ObjectMapper objectMapper; public AirbrakeService(AirbrakeNotifier notifier, AirbrakePerformanceStats performanceStats, AirbrakeConfig config, ObjectMapper objectMapper) { this.notifier = notifier; this.performanceStats = performanceStats; this.config = config; this.objectMapper = objectMapper; } // Basic error reporting public void reportError(Throwable throwable) { reportError(throwable, null, null); } public void reportError(Throwable throwable, String severity) { reportError(throwable, severity, null); } public void reportError(Throwable throwable, String severity, Map<String, Object> context) { if (!config.isEnabled() || notifier == null) { log.debug("Airbrake disabled, not reporting error: {}", throwable.getMessage()); return; } try { Notice notice = notifier.buildNotice(throwable); if (severity != null) { notice.setSeverity(severity); } if (context != null) { addCustomContext(notice, context); } notifier.notifyAsync(notice); log.debug("Reported error to Airbrake: {}", throwable.getMessage()); } catch (Exception e) { log.error("Failed to report error to Airbrake", e); } } // Custom error reporting with message public void reportError(String message, String severity, Map<String, Object> context) { if (!config.isEnabled() || notifier == null) { log.debug("Airbrake disabled, not reporting error: {}", message); return; } try { Notice notice = notifier.buildNotice(message); if (severity != null) { notice.setSeverity(severity); } if (context != null) { addCustomContext(notice, context); } notifier.notifyAsync(notice); log.debug("Reported custom error to Airbrake: {}", message); } catch (Exception e) { log.error("Failed to report custom error to Airbrake", e); } } // HTTP request error reporting public void reportHttpError(HttpServletRequest request, Throwable throwable, String severity, Map<String, Object> context) { if (!config.isEnabled() || notifier == null) { return; } try { Notice notice = notifier.buildNotice(throwable); // Add HTTP context if (request != null) { addHttpContext(notice, request); } if (severity != null) { notice.setSeverity(severity); } if (context != null) { addCustomContext(notice, context); } notifier.notifyAsync(notice); log.debug("Reported HTTP error to Airbrake: {}", throwable.getMessage()); } catch (Exception e) { log.error("Failed to report HTTP error to Airbrake", e); } } // Performance monitoring public void recordMetric(String name, double value, Map<String, Object> tags) { if (!config.isEnabled() || performanceStats == null || !config.getPerformanceStats().isEnabled()) { return; } try { Metric metric = new Metric(name, value); if (tags != null) { metric.setTags(tags); } performanceStats.notifyMetric(metric); } catch (Exception e) { log.error("Failed to record metric to Airbrake", e); } } // Route performance tracking public void recordRoutePerformance(String route, String method, long durationMs, int statusCode, Map<String, Object> context) { if (!config.isEnabled() || performanceStats == null || !config.getPerformanceStats().isEnabled()) { return; } try { RouteBreakdown breakdown = new RouteBreakdown(route, method); breakdown.setTime(durationMs); breakdown.setStatusCode(statusCode); if (context != null) { breakdown.setContext(context); } performanceStats.notifyRoute(breakdown); } catch (Exception e) { log.error("Failed to record route performance to Airbrake", e); } } // Query performance tracking public void recordQueryPerformance(String query, String operation, long durationMs, Map<String, Object> context) { if (!config.isEnabled() || performanceStats == null || !config.getPerformanceStats().isEnabled()) { return; } try { QueryBreakdown breakdown = new QueryBreakdown(query, operation); breakdown.setTime(durationMs); if (context != null) { breakdown.setContext(context); } performanceStats.notifyQuery(breakdown); } catch (Exception e) { log.error("Failed to record query performance to Airbrake", e); } } // Deployment tracking public void trackDeployment(String version, String environment, String username, String repository, String revision) { if (!config.isEnabled() || !config.getDeployment().isEnabled()) { return; } try { Deployment deployment = new Deployment(); deployment.setVersion(version); deployment.setEnvironment(environment); deployment.setUsername(username); deployment.setRepository(repository); deployment.setRevision(revision); notifier.trackDeployment(deployment); log.info("Tracked deployment to Airbrake: {} in {}", version, environment); } catch (Exception e) { log.error("Failed to track deployment to Airbrake", e); } } // Track current deployment public void trackCurrentDeployment() { trackDeployment( config.getDeployment().getVersion() != null ? config.getDeployment().getVersion() : config.getAppVersion(), config.getDeployment().getEnvironment(), config.getDeployment().getUsername(), config.getDeployment().getRepository(), config.getDeployment().getRevision() ); } // Add custom context to notice private void addCustomContext(Notice notice, Map<String, Object> context) { if (notice.getContext() == null) { notice.setContext(new Context()); } if (context != null) { // Add to parameters if (notice.getParams() == null) { notice.setParams(new HashMap<>()); } notice.getParams().putAll(context); // Add to environment if specific keys are present Map<String, String> env = new HashMap<>(); context.forEach((key, value) -> { if (value instanceof String) { env.put(key, (String) value); } }); notice.getContext().setEnvironment(env); } } // Add HTTP context to notice private void addHttpContext(Notice notice, HttpServletRequest request) { if (notice.getContext() == null) { notice.setContext(new Context()); } // Set HTTP context notice.getContext().setUrl(getRequestURL(request)); notice.getContext().setMethod(request.getMethod()); notice.getContext().setUserAgent(request.getHeader("User-Agent")); notice.getContext().setUserAddr(getClientIp(request)); // Add HTTP headers as parameters if (notice.getParams() == null) { notice.setParams(new HashMap<>()); } Map<String, String> headers = new HashMap<>(); Collections.list(request.getHeaderNames()) .forEach(headerName -> headers.put(headerName, request.getHeader(headerName))); notice.getParams().put("headers", headers); } private String getRequestURL(HttpServletRequest request) { StringBuffer url = request.getRequestURL(); if (request.getQueryString() != null) { url.append("?").append(request.getQueryString()); } return url.toString(); } private String getClientIp(HttpServletRequest request) { String xForwardedFor = request.getHeader("X-Forwarded-For"); if (xForwardedFor != null && !xForwardedFor.isEmpty()) { return xForwardedFor.split(",")[0].trim(); } return request.getRemoteAddr(); } // Check if Airbrake is configured and enabled public boolean isEnabled() { return config.isEnabled() && notifier != null; } // Get Airbrake configuration public AirbrakeConfig getConfig() { return config; } }

Step 5: Spring Boot Integration

@ControllerAdvice @Slf4j public class AirbrakeExceptionHandler { private final AirbrakeService airbrakeService; public AirbrakeExceptionHandler(AirbrakeService airbrakeService) { this.airbrakeService = airbrakeService; } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException(Exception e, HttpServletRequest request) { // Report to Airbrake Map<String, Object> context = new HashMap<>(); context.put("request_url", getRequestURL(request)); context.put("request_method", request.getMethod()); context.put("user_agent", request.getHeader("User-Agent")); context.put("client_ip", getClientIp(request)); airbrakeService.reportError(e, "error", context); // Create error response ErrorResponse errorResponse = new ErrorResponse( "INTERNAL_ERROR", "An unexpected error occurred", Instant.now() ); log.error("Unhandled exception occurred", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); } @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e, HttpServletRequest request) { // Report business exceptions with warning severity Map<String, Object> context = new HashMap<>(); context.put("error_code", e.getErrorCode()); context.put("business_context", e.getContext()); airbrakeService.reportError(e, "warning", context); ErrorResponse errorResponse = new ErrorResponse( e.getErrorCode(), e.getMessage(), Instant.now() ); log.warn("Business exception occurred: {}", e.getMessage()); return ResponseEntity.status(e.getHttpStatus()).body(errorResponse); } @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException e) { // Don't report 404 errors to Airbrake ErrorResponse errorResponse = new ErrorResponse( "RESOURCE_NOT_FOUND", e.getMessage(), Instant.now() ); log.info("Resource not found: {}", e.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); } private String getRequestURL(HttpServletRequest request) { StringBuffer url = request.getRequestURL(); if (request.getQueryString() != null) { url.append("?").append(request.getQueryString()); } return url.toString(); } private String getClientIp(HttpServletRequest request) { String xForwardedFor = request.getHeader("X-Forwarded-For"); if (xForwardedFor != null && !xForwardedFor.isEmpty()) { return xForwardedFor.split(",")[0].trim(); } return request.getRemoteAddr(); } } // Custom exceptions for better error handling public class BusinessException extends RuntimeException { private final String errorCode; private final HttpStatus httpStatus; private final Map<String, Object> context; public BusinessException(String errorCode, String message, HttpStatus httpStatus) { super(message); this.errorCode = errorCode; this.httpStatus = httpStatus; this.context = new HashMap<>(); } public BusinessException(String errorCode, String message, HttpStatus httpStatus, Map<String, Object> context) { super(message); this.errorCode = errorCode; this.httpStatus = httpStatus; this.context = context != null ? new HashMap<>(context) : new HashMap<>(); } // Getters public String getErrorCode() { return errorCode; } public HttpStatus getHttpStatus() { return httpStatus; } public Map<String, Object> getContext() { return new HashMap<>(context); } public void addContext(String key, Object value) { context.put(key, value); } } public class ResourceNotFoundException extends BusinessException { public ResourceNotFoundException(String resourceType, String resourceId) { super("RESOURCE_NOT_FOUND", String.format("%s with id %s not found", resourceType, resourceId), HttpStatus.NOT_FOUND); addContext("resource_type", resourceType); addContext("resource_id", resourceId); } } @Data @AllArgsConstructor @NoArgsConstructor class ErrorResponse { private String errorCode; private String message; private Instant timestamp; private String path; public ErrorResponse(String errorCode, String message, Instant timestamp) { this.errorCode = errorCode; this.message = message; this.timestamp = timestamp; } }

Step 6: Performance Monitoring Integration

@Aspect @Component @Slf4j public class AirbrakePerformanceAspect { private final AirbrakeService airbrakeService; public AirbrakePerformanceAspect(AirbrakeService airbrakeService) { this.airbrakeService = airbrakeService; } @Around("@annotation(MonitorPerformance)") public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable { if (!airbrakeService.isEnabled()) { return joinPoint.proceed(); } String methodName = getMethodName(joinPoint); long startTime = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long duration = System.currentTimeMillis() - startTime; // Record performance metric Map<String, Object> context = new HashMap<>(); context.put("class", joinPoint.getTarget().getClass().getSimpleName()); context.put("method", methodName); airbrakeService.recordMetric("method.performance", duration, context); return result; } catch (Throwable throwable) { long duration = System.currentTimeMillis() - startTime; // Record error performance Map<String, Object> context = new HashMap<>(); context.put("class", joinPoint.getTarget().getClass().getSimpleName()); context.put("method", methodName); context.put("error", throwable.getClass().getSimpleName()); airbrakeService.recordMetric("method.error", duration, context); throw throwable; } } @Around("execution(* com.yourcompany..*Controller.*(..))") public Object monitorControllerPerformance(ProceedingJoinPoint joinPoint) throws Throwable { if (!airbrakeService.isEnabled()) { return joinPoint.proceed(); } String controllerName = joinPoint.getTarget().getClass().getSimpleName(); String methodName = joinPoint.getSignature().getName(); long startTime = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long duration = System.currentTimeMillis() - startTime; // Get HTTP response status if available int statusCode = 200; // Default if (result instanceof ResponseEntity) { statusCode = ((ResponseEntity<?>) result).getStatusCodeValue(); } // Record route performance Map<String, Object> context = new HashMap<>(); context.put("controller", controllerName); context.put("method", methodName); airbrakeService.recordRoutePerformance( controllerName + "." + methodName, "HTTP", duration, statusCode, context ); return result; } catch (Throwable throwable) { long duration = System.currentTimeMillis() - startTime; // Record error performance Map<String, Object> context = new HashMap<>(); context.put("controller", controllerName); context.put("method", methodName); context.put("error", throwable.getClass().getSimpleName()); airbrakeService.recordRoutePerformance( controllerName + "." + methodName, "HTTP", duration, 500, context ); throw throwable; } } private String getMethodName(ProceedingJoinPoint joinPoint) { return joinPoint.getSignature().getName(); } } // Custom annotation for performance monitoring @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MonitorPerformance { String value() default ""; boolean enabled() default true; } // Servlet Filter for HTTP request performance monitoring @Component @Slf4j public class AirbrakePerformanceFilter implements Filter { private final AirbrakeService airbrakeService; public AirbrakePerformanceFilter(AirbrakeService airbrakeService) { this.airbrakeService = airbrakeService; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!airbrakeService.isEnabled()) { chain.doFilter(request, response); return; } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; long startTime = System.currentTimeMillis(); try { chain.doFilter(request, response); } finally { long duration = System.currentTimeMillis() - startTime; // Record HTTP request performance Map<String, Object> context = new HashMap<>(); context.put("path", httpRequest.getRequestURI()); context.put("method", httpRequest.getMethod()); context.put("query_string", httpRequest.getQueryString()); context.put("user_agent", httpRequest.getHeader("User-Agent")); context.put("client_ip", getClientIp(httpRequest)); airbrakeService.recordRoutePerformance( httpRequest.getRequestURI(), httpRequest.getMethod(), duration, httpResponse.getStatus(), context ); } } private String getClientIp(HttpServletRequest request) { String xForwardedFor = request.getHeader("X-Forwarded-For"); if (xForwardedFor != null && !xForwardedFor.isEmpty()) { return xForwardedFor.split(",")[0].trim(); } return request.getRemoteAddr(); } }

Step 7: Service Implementation with Airbrake

@Service @Slf4j public class OrderService { private final OrderRepository orderRepository; private final PaymentService paymentService; private final AirbrakeService airbrakeService; public OrderService(OrderRepository orderRepository, PaymentService paymentService, AirbrakeService airbrakeService) { this.orderRepository = orderRepository; this.paymentService = paymentService; this.airbrakeService = airbrakeService; } @MonitorPerformance public Order processOrder(Order order) { Map<String, Object> context = new HashMap<>(); context.put("order_id", order.getId()); context.put("customer_id", order.getCustomerId()); context.put("order_amount", order.getAmount()); try { // Validate order validateOrder(order); // Process payment PaymentResult paymentResult = paymentService.processPayment(order); context.put("payment_id", paymentResult.getPaymentId()); // Save order Order savedOrder = orderRepository.save(order); // Record success metric airbrakeService.recordMetric("order.processed", 1, context); log.info("Order processed successfully: {}", order.getId()); return savedOrder; } catch (PaymentException e) { // Report payment errors with specific context context.put("payment_error", e.getErrorCode()); context.put("payment_message", e.getMessage()); airbrakeService.reportError(e, "error", context); airbrakeService.recordMetric("order.payment_failed", 1, context); throw new BusinessException("PAYMENT_FAILED", "Payment processing failed", HttpStatus.BAD_REQUEST, context); } catch (Exception e) { // Report general errors airbrakeService.reportError(e, "error", context); airbrakeService.recordMetric("order.processing_failed", 1, context); throw new BusinessException("ORDER_PROCESSING_FAILED", "Order processing failed", HttpStatus.INTERNAL_SERVER_ERROR, context); } } @MonitorPerformance("getOrdersByCustomer") public List<Order> getOrdersByCustomer(String customerId, Pageable pageable) { Map<String, Object> context = new HashMap<>(); context.put("customer_id", customerId); context.put("page_size", pageable.getPageSize()); context.put("page_number", pageable.getPageNumber()); try { List<Order> orders = orderRepository.findByCustomerId(customerId, pageable); // Record performance metric airbrakeService.recordMetric("orders.retrieved", orders.size(), context); return orders; } catch (Exception e) { airbrakeService.reportError(e, "warning", context); throw new BusinessException("ORDERS_RETRIEVAL_FAILED", "Failed to retrieve orders", HttpStatus.INTERNAL_SERVER_ERROR, context); } } public void batchProcessOrders(List<Order> orders) { Map<String, Object> batchContext = new HashMap<>(); batchContext.put("batch_size", orders.size()); int successCount = 0; int errorCount = 0; for (Order order : orders) { try { processOrder(order); successCount++; } catch (Exception e) { errorCount++; log.error("Failed to process order in batch: {}", order.getId(), e); } } // Record batch metrics batchContext.put("success_count", successCount); batchContext.put("error_count", errorCount); batchContext.put("success_rate", orders.size() > 0 ? (double) successCount / orders.size() : 0); airbrakeService.recordMetric("batch.orders.processed", 1, batchContext); airbrakeService.recordMetric("batch.orders.success", successCount, batchContext); airbrakeService.recordMetric("batch.orders.errors", errorCount, batchContext); } private void validateOrder(Order order) { if (order.getAmount() <= 0) { Map<String, Object> context = new HashMap<>(); context.put("order_id", order.getId()); context.put("invalid_amount", order.getAmount()); airbrakeService.reportError( "Invalid order amount: " + order.getAmount(), "warning", context ); throw new BusinessException("INVALID_ORDER_AMOUNT", "Order amount must be positive", HttpStatus.BAD_REQUEST, context); } } } @RestController @Slf4j public class OrderController { private final OrderService orderService; private final AirbrakeService airbrakeService; public OrderController(OrderService orderService, AirbrakeService airbrakeService) { this.orderService = orderService; this.airbrakeService = airbrakeService; } @PostMapping("/orders") public ResponseEntity<Order> createOrder(@RequestBody Order order) { log.info("Creating order for customer: {}", order.getCustomerId()); try { Order processedOrder = orderService.processOrder(order); return ResponseEntity.ok(processedOrder); } catch (BusinessException e) { // Business exceptions are already handled by AirbrakeExceptionHandler throw e; } catch (Exception e) { // Unexpected exceptions Map<String, Object> context = new HashMap<>(); context.put("endpoint", "/orders"); context.put("http_method", "POST"); context.put("customer_id", order.getCustomerId()); airbrakeService.reportError(e, "error", context); throw e; } } @GetMapping("/orders/customer/{customerId}") public ResponseEntity<List<Order>> getCustomerOrders( @PathVariable String customerId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { try { Pageable pageable = PageRequest.of(page, size); List<Order> orders = orderService.getOrdersByCustomer(customerId, pageable); return ResponseEntity.ok(orders); } catch (BusinessException e) { throw e; } catch (Exception e) { Map<String, Object> context = new HashMap<>(); context.put("endpoint", "/orders/customer/{customerId}"); context.put("http_method", "GET"); context.put("customer_id", customerId); airbrakeService.reportError(e, "error", context); throw e; } } }

Step 8: Logback Integration

logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?> <configuration> <springProperty scope="context" name="app_name" source="spring.application.name" defaultValue="unknown"/> <springProperty scope="context" name="app_version" source="app.version" defaultValue="1.0.0"/> <springProperty scope="context" name="environment" source="spring.profiles.active" defaultValue="local"/> <!-- Airbrake Appender --> <appender name="AIRBRAKE" class="io.airbrake.logback.AirbrakeAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <projectId>${AIRBRAKE_PROJECT_ID}</projectId> <projectKey>${AIRBRAKE_API_KEY}</projectKey> <environment>${environment}</environment> <enabled>${airbrake.enabled:false}</enabled> <async>true</async> <queueSize>100</queueSize> <workers>5</workers> </appender> <!-- Console Appender --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- Root Logger --> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="AIRBRAKE"/> </root> <!-- Application-specific loggers --> <logger name="com.yourcompany" level="DEBUG" additivity="false"> <appender-ref ref="CONSOLE"/> <appender-ref ref="AIRBRAKE"/> </logger> </configuration>

Step 9: Testing

@SpringBootTest @Slf4j class AirbrakeIntegrationTest { @Autowired private AirbrakeService airbrakeService; @Autowired private OrderService orderService; @MockBean private OrderRepository orderRepository; @MockBean private PaymentService paymentService; @Test void testErrorReporting() { Exception testException = new RuntimeException("Test exception"); // This should not throw an exception airbrakeService.reportError(testException, "error", Map.of("test_id", "123", "component", "test")); assertTrue(airbrakeService.isEnabled()); } @Test void testPerformanceMonitoring() { // This should not throw an exception airbrakeService.recordMetric("test.metric", 42.0, Map.of("test", "value")); airbrakeService.recordRoutePerformance("/test", "GET", 150L, 200, Map.of("test", "value")); } @Test void testOrderProcessingWithAirbrake() { Order testOrder = new Order("test-order", "test-customer", 100.0); when(orderRepository.save(any(Order.class))).thenReturn(testOrder); when(paymentService.processPayment(any(Order.class))) .thenReturn(new PaymentResult("payment-123", "success")); Order result = orderService.processOrder(testOrder); assertNotNull(result); assertEquals("test-order", result.getId()); verify(orderRepository, times(1)).save(any(Order.class)); verify(paymentService, times(1)).processPayment(any(Order.class)); } @Test void testOrderProcessingWithPaymentError() { Order testOrder = new Order("test-order", "test-customer", 100.0); when(paymentService.processPayment(any(Order.class))) .thenThrow(new PaymentException("PAYMENT_DECLINED", "Insufficient funds")); assertThrows(BusinessException.class, () -> { orderService.processOrder(testOrder); }); verify(paymentService, times(1)).processPayment(any(Order.class)); } } // Test configuration @TestConfiguration class AirbrakeTestConfig { @Bean @Primary public AirbrakeService testAirbrakeService() { AirbrakeConfig config = new AirbrakeConfig(); config.setEnabled(false); // Disable for tests return new AirbrakeService(null, null, config, new ObjectMapper()); } }

Step 10: Deployment Tracking

@Component @Slf4j public class DeploymentTracker { private final AirbrakeService airbrakeService; public DeploymentTracker(AirbrakeService airbrakeService) { this.airbrakeService = airbrakeService; } @EventListener(ApplicationReadyEvent.class) public void trackApplicationStartup() { log.info("Application started, tracking deployment to Airbrake"); airbrakeService.trackCurrentDeployment(); } // Manual deployment tracking public void trackDeployment(String version, String environment, String username) { airbrakeService.trackDeployment( version, environment, username, getGitRepository(), getGitRevision() ); } private String getGitRepository() { try { // Implementation to get Git repository URL return System.getenv("GIT_REPOSITORY"); } catch (Exception e) { return "unknown"; } } private String getGitRevision() { try { // Implementation to get Git commit hash return System.getenv("GIT_COMMIT"); } catch (Exception e) { return "unknown"; } } }

Key Features Implemented

  1. Error Monitoring: Comprehensive error tracking and reporting
  2. Performance Monitoring: APM integration for routes, queries, and methods
  3. Deployment Tracking: Track deployments and their impact
  4. Smart Filtering: Sensitive data filtering and context enrichment
  5. Spring Integration: Seamless Spring Boot integration
  6. Logging Integration: Logback appender for automatic error reporting
  7. Custom Context: Rich error context and business data
  8. Exception Handling: Comprehensive exception handling with Airbrake integration

Best Practices

  1. Meaningful Context: Add relevant business context to errors
  2. Security: Always filter sensitive data before reporting
  3. Performance: Use async reporting for non-blocking operations
  4. Error Grouping: Use consistent error messages for smart grouping
  5. Monitoring: Set up alerts based on error frequency and severity
  6. Deployment Tracking: Always track deployments for correlation
  7. Testing: Test Airbrake integration in development environment

This comprehensive Airbrake integration provides robust error monitoring, performance tracking, and deployment tracking for Java applications, enabling effective error detection, analysis, and resolution in production environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper