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