Titliel – Raygun Crash Reporting in Java

Article: Comprehensive Error Monitoring with Raygun in Java Applications

Raygun provides powerful crash reporting and error monitoring capabilities for applications. This article covers comprehensive integration strategies for Java applications with advanced features like error grouping, user tracking, and performance monitoring.

Core Raygun Integration

package com.titliel.raygun; import com.raygun.raygun4java.RaygunClient; import com.raygun.raygun4java.messages.RaygunMessage; import com.raygun.raygun4java.messages.RaygunMessageBuilder; import com.raygun.raygun4java.messages.RaygunErrorMessage; import com.raygun.raygun4java.messages.RaygunEnvironmentMessage; import com.raygun.raygun4java.messages.RaygunRequestMessage; import com.raygun.raygun4java.messages.RaygunUserInfo; import com.raygun.raygun4java.messages.RaygunIdentifier; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; /** * Titliel Raygun Crash Reporter * Advanced error monitoring with custom grouping and analytics */ public class TitlielRaygunReporter { private final RaygunClient raygunClient; private final RaygunConfig config; private final ErrorAnalytics analytics; private final ExecutorService executorService; private final LinkedBlockingQueue<ErrorEvent> errorQueue; private final Map<String, ErrorRateLimiter> rateLimiters; private volatile boolean shutdown = false; public TitlielRaygunReporter(String apiKey) { this.raygunClient = new RaygunClient(apiKey); this.config = RaygunConfig.getInstance(); this.analytics = new ErrorAnalytics(); this.executorService = Executors.newFixedThreadPool(2); this.errorQueue = new LinkedBlockingQueue<>(1000); this.rateLimiters = new ConcurrentHashMap<>(); initialize(); } private void initialize() { // Start background error processing executorService.submit(this::processErrorQueue); // Start analytics reporting executorService.submit(this::reportAnalytics); // Register shutdown hook Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); } /** * Report exception with custom data */ public void reportError(Throwable throwable, ErrorContext context) { if (!shouldReportError(throwable, context)) { return; } ErrorEvent errorEvent = new ErrorEvent(throwable, context); try { if (!errorQueue.offer(errorEvent, 100, java.util.concurrent.TimeUnit.MILLISECONDS)) { // Queue full, handle accordingly handleQueueFull(errorEvent); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Fallback to synchronous reporting reportErrorSync(errorEvent); } } /** * Report error with custom message and severity */ public void reportError(String message, ErrorSeverity severity, ErrorContext context) { CustomException exception = new CustomException(message); context.setCustomMessage(message); context.setSeverity(severity); reportError(exception, context); } /** * Report handled exception (non-fatal) */ public void reportHandledError(Throwable throwable, String feature, Map<String, Object> customData) { ErrorContext context = new ErrorContext() .setFeature(feature) .setCustomData(customData) .setHandled(true) .setSeverity(ErrorSeverity.WARNING); reportError(throwable, context); } /** * Report performance issue as error */ public void reportPerformanceIssue(String operation, long duration, long threshold) { String message = String.format("Performance issue: %s took %dms (threshold: %dms)", operation, duration, threshold); PerformanceException exception = new PerformanceException(message); ErrorContext context = new ErrorContext() .setFeature("performance") .setCustomData(Map.of( "operation", operation, "duration", duration, "threshold", threshold, "type", "performance_issue" )) .setSeverity(ErrorSeverity.WARNING); reportError(exception, context); } /** * Track user session for error correlation */ public void setUserSession(UserSession session) { RaygunUserInfo userInfo = new RaygunUserInfo(session.getUserId()); userInfo.setEmail(session.getEmail()); userInfo.setFullName(session.getFullName()); userInfo.setFirstName(session.getFirstName()); userInfo.setUuid(session.getSessionId()); // Add custom user data Map<String, Object> customData = new HashMap<>(); customData.put("signupDate", session.getSignupDate()); customData.put("accountType", session.getAccountType()); customData.put("lastLogin", session.getLastLogin()); raygunClient.setUserInfo(userInfo); raygunClient.setCustomData(customData); } /** * Set application version for release tracking */ public void setApplicationVersion(String version) { raygunClient.setVersion(version); } /** * Add custom grouping key for error aggregation */ public void setCustomGroupingKey(String key, String value) { raygunClient.getCustomData().put("groupingKey." + key, value); } private void processErrorQueue() { while (!shutdown || !errorQueue.isEmpty()) { try { ErrorEvent errorEvent = errorQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS); if (errorEvent != null) { reportErrorSync(errorEvent); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } private void reportErrorSync(ErrorEvent errorEvent) { try { RaygunMessage message = buildRaygunMessage(errorEvent); raygunClient.send(message); // Update analytics analytics.recordError(errorEvent); } catch (Exception e) { System.err.println("Failed to report error to Raygun: " + e.getMessage()); // Fallback to local logging logErrorLocally(errorEvent, e); } } private RaygunMessage buildRaygunMessage(ErrorEvent errorEvent) { RaygunMessageBuilder messageBuilder = RaygunMessageBuilder.New() .SetEnvironmentDetails(buildEnvironmentMessage()) .SetMachineName(config.getMachineName()) .SetVersion(config.getAppVersion()); // Build error details RaygunErrorMessage errorMessage = buildErrorMessage(errorEvent.getThrowable()); messageBuilder.SetErrorDetails(errorMessage); // Add custom data Map<String, Object> customData = buildCustomData(errorEvent.getContext()); messageBuilder.SetCustomData(customData); // Add user information if (errorEvent.getContext().getUser() != null) { RaygunUserInfo userInfo = buildUserInfo(errorEvent.getContext().getUser()); messageBuilder.SetUserInfo(userInfo); } // Add request information if available if (errorEvent.getContext().getRequest() != null) { RaygunRequestMessage requestMessage = buildRequestMessage(errorEvent.getContext().getRequest()); messageBuilder.SetRequestDetails(requestMessage); } // Set tags List<String> tags = buildTags(errorEvent); messageBuilder.SetTags(tags); return messageBuilder.Build(); } private RaygunErrorMessage buildErrorMessage(Throwable throwable) { // Custom error message builder with enhanced stack traces return new RaygunErrorMessage(throwable) { @Override public String getClassName() { return throwable.getClass().getName(); } @Override public String getMessage() { String customMessage = extractCustomMessage(throwable); return customMessage != null ? customMessage : throwable.getMessage(); } }; } private Map<String, Object> buildCustomData(ErrorContext context) { Map<String, Object> customData = new HashMap<>(); // Basic context customData.put("feature", context.getFeature()); customData.put("handled", context.isHandled()); customData.put("severity", context.getSeverity().name()); customData.put("timestamp", new Date()); // Custom data from context if (context.getCustomData() != null) { customData.putAll(context.getCustomData()); } // Application-specific data customData.put("environment", config.getEnvironment()); customData.put("deployment", config.getDeploymentId()); customData.put("sessionId", context.getSessionId()); // Performance metrics if available if (context.getPerformanceMetrics() != null) { customData.put("performance", context.getPerformanceMetrics()); } return customData; } private List<String> buildTags(ErrorEvent errorEvent) { List<String> tags = new ArrayList<>(); tags.add("environment:" + config.getEnvironment()); tags.add("severity:" + errorEvent.getContext().getSeverity().name()); tags.add("handled:" + errorEvent.getContext().isHandled()); tags.add("feature:" + errorEvent.getContext().getFeature()); // Add error type tags tags.add("type:" + errorEvent.getThrowable().getClass().getSimpleName()); return tags; } private boolean shouldReportError(Throwable throwable, ErrorContext context) { // Check rate limiting String rateLimitKey = getRateLimitKey(throwable, context); ErrorRateLimiter rateLimiter = rateLimiters.computeIfAbsent(rateLimitKey, k -> new ErrorRateLimiter(config.getMaxErrorsPerMinute())); if (!rateLimiter.allowRequest()) { analytics.recordRateLimitedError(throwable, context); return false; } // Check ignored exceptions if (isIgnoredException(throwable)) { return false; } // Check environment filters if (!shouldReportInEnvironment()) { return false; } return true; } private boolean isIgnoredException(Throwable throwable) { List<String> ignoredPatterns = config.getIgnoredExceptions(); String exceptionName = throwable.getClass().getName(); return ignoredPatterns.stream() .anyMatch(pattern -> exceptionName.matches(pattern)); } private boolean shouldReportInEnvironment() { String environment = config.getEnvironment(); List<String> reportEnvironments = config.getReportEnvironments(); return reportEnvironments.contains(environment) || reportEnvironments.contains("all"); } private String getRateLimitKey(Throwable throwable, ErrorContext context) { return throwable.getClass().getName() + ":" + context.getFeature(); } private void handleQueueFull(ErrorEvent errorEvent) { // Implement fallback strategy analytics.recordDroppedError(errorEvent); if (config.isFallbackLoggingEnabled()) { logErrorLocally(errorEvent, new Exception("Error queue full")); } } private void logErrorLocally(ErrorEvent errorEvent, Exception reportingError) { // Log to local file or system logs System.err.println("Failed to report error to Raygun: " + reportingError.getMessage()); System.err.println("Original error: " + errorEvent.getThrowable().getMessage()); } private void reportAnalytics() { while (!shutdown) { try { Thread.sleep(60000); // Report every minute ErrorAnalytics.Snapshot snapshot = analytics.createSnapshot(); reportAnalyticsToRaygun(snapshot); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } private void reportAnalyticsToRaygun(ErrorAnalytics.Snapshot snapshot) { // Report analytics as custom errors or use Raygun's metrics API Map<String, Object> analyticsData = new HashMap<>(); analyticsData.put("totalErrors", snapshot.getTotalErrors()); analyticsData.put("uniqueErrors", snapshot.getUniqueErrors()); analyticsData.put("errorRate", snapshot.getErrorRate()); analyticsData.put("topFeatures", snapshot.getTopFeatures(5)); ErrorContext context = new ErrorContext() .setFeature("analytics") .setCustomData(analyticsData) .setHandled(true) .setSeverity(ErrorSeverity.INFO); reportError("Error Analytics Report", ErrorSeverity.INFO, context); } public void shutdown() { shutdown = true; executorService.shutdown(); try { if (!executorService.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } // Process remaining errors processErrorQueue(); } // Helper methods for building Raygun messages private RaygunEnvironmentMessage buildEnvironmentMessage() { RaygunEnvironmentMessage env = new RaygunEnvironmentMessage(); env.setProcessorCount(Runtime.getRuntime().availableProcessors()); env.setOsVersion(System.getProperty("os.version")); env.setArchitecture(System.getProperty("os.arch")); env.setTotalPhysicalMemory(Runtime.getRuntime().totalMemory()); env.setAvailablePhysicalMemory(Runtime.getRuntime().freeMemory()); return env; } private RaygunUserInfo buildUserInfo(User user) { RaygunUserInfo userInfo = new RaygunUserInfo(user.getId()); userInfo.setEmail(user.getEmail()); userInfo.setFullName(user.getFullName()); userInfo.setFirstName(user.getFirstName()); // Add custom user properties Map<String, Object> customData = new HashMap<>(); customData.put("company", user.getCompany()); customData.put("department", user.getDepartment()); customData.put("role", user.getRole()); userInfo.setCustomData(customData); return userInfo; } private RaygunRequestMessage buildRequestMessage(RequestInfo request) { RaygunRequestMessage requestMessage = new RaygunRequestMessage(); requestMessage.setHostName(request.getHost()); requestMessage.setUrl(request.getUrl()); requestMessage.setHttpMethod(request.getMethod()); requestMessage.setIpAddress(request.getIpAddress()); requestMessage.setQueryString(request.getQueryParams()); requestMessage.setHeaders(request.getHeaders()); requestMessage.setFormData(request.getFormData()); return requestMessage; } private String extractCustomMessage(Throwable throwable) { if (throwable instanceof CustomException) { return throwable.getMessage(); } return null; } }

Data Models and Context

package com.titliel.raygun; import java.util.*; /** * Error context with comprehensive metadata */ public class ErrorContext { private String feature; private boolean handled = false; private ErrorSeverity severity = ErrorSeverity.ERROR; private String customMessage; private Map<String, Object> customData; private User user; private RequestInfo request; private String sessionId; private Map<String, Object> performanceMetrics; private Date timestamp; public ErrorContext() { this.timestamp = new Date(); this.customData = new HashMap<>(); } // Fluent builder methods public ErrorContext setFeature(String feature) { this.feature = feature; return this; } public ErrorContext setHandled(boolean handled) { this.handled = handled; return this; } public ErrorContext setSeverity(ErrorSeverity severity) { this.severity = severity; return this; } public ErrorContext setCustomMessage(String customMessage) { this.customMessage = customMessage; return this; } public ErrorContext setCustomData(Map<String, Object> customData) { this.customData = customData; return this; } public ErrorContext setUser(User user) { this.user = user; return this; } public ErrorContext setRequest(RequestInfo request) { this.request = request; return this; } public ErrorContext setSessionId(String sessionId) { this.sessionId = sessionId; return this; } public ErrorContext setPerformanceMetrics(Map<String, Object> performanceMetrics) { this.performanceMetrics = performanceMetrics; return this; } public ErrorContext addCustomData(String key, Object value) { if (this.customData == null) { this.customData = new HashMap<>(); } this.customData.put(key, value); return this; } // Getters public String getFeature() { return feature; } public boolean isHandled() { return handled; } public ErrorSeverity getSeverity() { return severity; } public String getCustomMessage() { return customMessage; } public Map<String, Object> getCustomData() { return Collections.unmodifiableMap(customData); } public User getUser() { return user; } public RequestInfo getRequest() { return request; } public String getSessionId() { return sessionId; } public Map<String, Object> getPerformanceMetrics() { return performanceMetrics; } public Date getTimestamp() { return timestamp; } } /** * Error severity levels */ public enum ErrorSeverity { INFO, WARNING, ERROR, CRITICAL } /** * User information for error correlation */ public class User { private final String id; private String email; private String fullName; private String firstName; private String company; private String department; private String role; public User(String id) { this.id = id; } // Fluent builder methods public User withEmail(String email) { this.email = email; return this; } public User withFullName(String fullName) { this.fullName = fullName; return this; } public User withFirstName(String firstName) { this.firstName = firstName; return this; } public User withCompany(String company) { this.company = company; return this; } public User withDepartment(String department) { this.department = department; return this; } public User withRole(String role) { this.role = role; return this; } // Getters public String getId() { return id; } public String getEmail() { return email; } public String getFullName() { return fullName; } public String getFirstName() { return firstName; } public String getCompany() { return company; } public String getDepartment() { return department; } public String getRole() { return role; } } /** * Request information for web errors */ public class RequestInfo { private String host; private String url; private String method; private String ipAddress; private Map<String, String> queryParams; private Map<String, String> headers; private Map<String, String> formData; // Fluent builder methods public RequestInfo setHost(String host) { this.host = host; return this; } public RequestInfo setUrl(String url) { this.url = url; return this; } public RequestInfo setMethod(String method) { this.method = method; return this; } public RequestInfo setIpAddress(String ipAddress) { this.ipAddress = ipAddress; return this; } public RequestInfo setQueryParams(Map<String, String> queryParams) { this.queryParams = queryParams; return this; } public RequestInfo setHeaders(Map<String, String> headers) { this.headers = headers; return this; } public RequestInfo setFormData(Map<String, String> formData) { this.formData = formData; return this; } // Getters public String getHost() { return host; } public String getUrl() { return url; } public String getMethod() { return method; } public String getIpAddress() { return ipAddress; } public Map<String, String> getQueryParams() { return queryParams; } public Map<String, String> getHeaders() { return headers; } public Map<String, String> getFormData() { return formData; } } /** * User session information */ public class UserSession { private String userId; private String sessionId; private String email; private String fullName; private String firstName; private Date signupDate; private Date lastLogin; private String accountType; // Getters and setters public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public Date getSignupDate() { return signupDate; } public void setSignupDate(Date signupDate) { this.signupDate = signupDate; } public Date getLastLogin() { return lastLogin; } public void setLastLogin(Date lastLogin) { this.lastLogin = lastLogin; } public String getAccountType() { return accountType; } public void setAccountType(String accountType) { this.accountType = accountType; } } /** * Error event container */ class ErrorEvent { private final Throwable throwable; private final ErrorContext context; private final Date timestamp; public ErrorEvent(Throwable throwable, ErrorContext context) { this.throwable = throwable; this.context = context; this.timestamp = new Date(); } // Getters public Throwable getThrowable() { return throwable; } public ErrorContext getContext() { return context; } public Date getTimestamp() { return timestamp; } } /** * Custom exception types */ class CustomException extends Exception { public CustomException(String message) { super(message); } public CustomException(String message, Throwable cause) { super(message, cause); } } class PerformanceException extends Exception { public PerformanceException(String message) { super(message); } }

Error Analytics and Rate Limiting

package com.titliel.raygun; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; /** * Error analytics and tracking */ public class ErrorAnalytics { private final AtomicLong totalErrors = new AtomicLong(0); private final AtomicLong rateLimitedErrors = new AtomicLong(0); private final AtomicLong droppedErrors = new AtomicLong(0); private final Map<String, ErrorStats> errorStats = new ConcurrentHashMap<>(); private final Map<String, FeatureStats> featureStats = new ConcurrentHashMap<>(); private final long startTime = System.currentTimeMillis(); public void recordError(ErrorEvent errorEvent) { totalErrors.incrementAndGet(); String errorKey = getErrorKey(errorEvent.getThrowable()); String feature = errorEvent.getContext().getFeature(); // Update error statistics errorStats.computeIfAbsent(errorKey, k -> new ErrorStats()) .recordError(errorEvent); // Update feature statistics featureStats.computeIfAbsent(feature, k -> new FeatureStats()) .recordError(errorEvent); } public void recordRateLimitedError(Throwable throwable, ErrorContext context) { rateLimitedErrors.incrementAndGet(); } public void recordDroppedError(ErrorEvent errorEvent) { droppedErrors.incrementAndGet(); } public Snapshot createSnapshot() { long currentTime = System.currentTimeMillis(); long runningTime = currentTime - startTime; Snapshot snapshot = new Snapshot(); snapshot.setTotalErrors(totalErrors.get()); snapshot.setRateLimitedErrors(rateLimitedErrors.get()); snapshot.setDroppedErrors(droppedErrors.get()); snapshot.setRunningTime(runningTime); snapshot.setErrorRate(calculateErrorRate(runningTime)); snapshot.setUniqueErrors(errorStats.size()); snapshot.setTopFeatures(getTopFeatures(10)); snapshot.setTopErrors(getTopErrors(10)); return snapshot; } private double calculateErrorRate(long runningTime) { double minutes = runningTime / 60000.0; return minutes > 0 ? totalErrors.get() / minutes : 0.0; } private Map<String, Long> getTopFeatures(int count) { return featureStats.entrySet().stream() .sorted((e1, e2) -> Long.compare(e2.getValue().getCount(), e1.getValue().getCount())) .limit(count) .collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().getCount() )); } private Map<String, Long> getTopErrors(int count) { return errorStats.entrySet().stream() .sorted((e1, e2) -> Long.compare(e2.getValue().getCount(), e1.getValue().getCount())) .limit(count) .collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().getCount() )); } private String getErrorKey(Throwable throwable) { return throwable.getClass().getName() + ":" + throwable.getMessage(); } /** * Analytics snapshot */ public static class Snapshot { private long totalErrors; private long rateLimitedErrors; private long droppedErrors; private long runningTime; private double errorRate; private long uniqueErrors; private Map<String, Long> topFeatures; private Map<String, Long> topErrors; // Getters and setters public long getTotalErrors() { return totalErrors; } public void setTotalErrors(long totalErrors) { this.totalErrors = totalErrors; } public long getRateLimitedErrors() { return rateLimitedErrors; } public void setRateLimitedErrors(long rateLimitedErrors) { this.rateLimitedErrors = rateLimitedErrors; } public long getDroppedErrors() { return droppedErrors; } public void setDroppedErrors(long droppedErrors) { this.droppedErrors = droppedErrors; } public long getRunningTime() { return runningTime; } public void setRunningTime(long runningTime) { this.runningTime = runningTime; } public double getErrorRate() { return errorRate; } public void setErrorRate(double errorRate) { this.errorRate = errorRate; } public long getUniqueErrors() { return uniqueErrors; } public void setUniqueErrors(long uniqueErrors) { this.uniqueErrors = uniqueErrors; } public Map<String, Long> getTopFeatures() { return topFeatures; } public void setTopFeatures(Map<String, Long> topFeatures) { this.topFeatures = topFeatures; } public Map<String, Long> getTopErrors() { return topErrors; } public void setTopErrors(Map<String, Long> topErrors) { this.topErrors = topErrors; } public Map<String, Long> getTopFeatures(int count) { return topFeatures.entrySet().stream() .sorted(Map.Entry.<String, Long>comparingByValue().reversed()) .limit(count) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue )); } } } /** * Error statistics */ class ErrorStats { private final AtomicLong count = new AtomicLong(0); private final AtomicLong lastOccurrence = new AtomicLong(0); private final Map<String, AtomicLong> severityCounts = new ConcurrentHashMap<>(); public void recordError(ErrorEvent errorEvent) { count.incrementAndGet(); lastOccurrence.set(System.currentTimeMillis()); String severity = errorEvent.getContext().getSeverity().name(); severityCounts.computeIfAbsent(severity, k -> new AtomicLong(0)) .incrementAndGet(); } public long getCount() { return count.get(); } public long getLastOccurrence() { return lastOccurrence.get(); } public Map<String, Long> getSeverityCounts() { return severityCounts.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().get() )); } } /** * Feature-specific statistics */ class FeatureStats { private final AtomicLong count = new AtomicLong(0); private final AtomicLong errorCount = new AtomicLong(0); private final AtomicLong warningCount = new AtomicLong(0); public void recordError(ErrorEvent errorEvent) { count.incrementAndGet(); switch (errorEvent.getContext().getSeverity()) { case ERROR, CRITICAL -> errorCount.incrementAndGet(); case WARNING -> warningCount.incrementAndGet(); } } public long getCount() { return count.get(); } public long getErrorCount() { return errorCount.get(); } public long getWarningCount() { return warningCount.get(); } public double getErrorRate() { return count.get() > 0 ? (double) errorCount.get() / count.get() : 0.0; } } /** * Rate limiting for error reporting */ class ErrorRateLimiter { private final int maxRequestsPerMinute; private final Queue<Long> requestTimestamps; public ErrorRateLimiter(int maxRequestsPerMinute) { this.maxRequestsPerMinute = maxRequestsPerMinute; this.requestTimestamps = new LinkedList<>(); } public synchronized boolean allowRequest() { long currentTime = System.currentTimeMillis(); long oneMinuteAgo = currentTime - 60000; // Remove old timestamps while (!requestTimestamps.isEmpty() && requestTimestamps.peek() < oneMinuteAgo) { requestTimestamps.poll(); } // Check if under rate limit if (requestTimestamps.size() < maxRequestsPerMinute) { requestTimestamps.offer(currentTime); return true; } return false; } }

Spring Boot Integration

package com.titliel.raygun.spring; import com.titliel.raygun.TitlielRaygunReporter; import com.titliel.raygun.ErrorContext; import com.titliel.raygun.RequestInfo; import com.titliel.raygun.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * Spring Boot integration for Raygun error reporting */ @Component public class RaygunSpringInterceptor implements HandlerInterceptor { @Autowired private TitlielRaygunReporter raygunReporter; private static final ThreadLocal<Long> requestStartTime = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { requestStartTime.set(System.currentTimeMillis()); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { if (ex != null) { // Build error context from request ErrorContext context = buildErrorContext(request, response); // Report the error raygunReporter.reportError(ex, context); } // Record performance metrics recordPerformanceMetrics(request, response); requestStartTime.remove(); } private ErrorContext buildErrorContext(HttpServletRequest request, HttpServletResponse response) { RequestInfo requestInfo = new RequestInfo() .setHost(request.getServerName()) .setUrl(request.getRequestURL().toString()) .setMethod(request.getMethod()) .setIpAddress(getClientIp(request)) .setQueryParams(extractQueryParams(request)) .setHeaders(extractHeaders(request)) .setFormData(extractFormData(request)); return new ErrorContext() .setFeature("web-request") .setHandled(false) .setRequest(requestInfo) .addCustomData("http.status", response.getStatus()) .addCustomData("http.userAgent", request.getHeader("User-Agent")) .addCustomData("http.referer", request.getHeader("Referer")) .addCustomData("session.id", request.getSession(false) != null ? request.getSession().getId() : "no-session"); } private void recordPerformanceMetrics(HttpServletRequest request, HttpServletResponse response) { Long startTime = requestStartTime.get(); if (startTime != null) { long duration = System.currentTimeMillis() - startTime; // Report slow requests as performance issues if (duration > 5000) { // 5 second threshold Map<String, Object> metrics = new HashMap<>(); metrics.put("duration", duration); metrics.put("url", request.getRequestURI()); metrics.put("method", request.getMethod()); ErrorContext context = new ErrorContext() .setFeature("performance") .setHandled(true) .setCustomData(metrics); raygunReporter.reportPerformanceIssue( request.getMethod() + " " + request.getRequestURI(), duration, 5000 ); } } } 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(); } private Map<String, String> extractQueryParams(HttpServletRequest request) { Map<String, String> params = new HashMap<>(); Enumeration<String> paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { String paramName = paramNames.nextElement(); params.put(paramName, request.getParameter(paramName)); } return params; } private Map<String, String> extractHeaders(HttpServletRequest request) { Map<String, String> headers = new HashMap<>(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); headers.put(headerName, request.getHeader(headerName)); } return headers; } private Map<String, String> extractFormData(HttpServletRequest request) { // For POST requests with form data if ("POST".equalsIgnoreCase(request.getMethod()) && request.getContentType() != null && request.getContentType().contains("application/x-www-form-urlencoded")) { return extractQueryParams(request); // Form data is available as parameters } return new HashMap<>(); } } /** * Spring configuration for Raygun */ package com.titliel.raygun.spring; import com.titliel.raygun.TitlielRaygunReporter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class RaygunConfig implements WebMvcConfigurer { @Value("${raygun.api.key:}") private String raygunApiKey; @Value("${raygun.enabled:false}") private boolean raygunEnabled; @Bean public TitlielRaygunReporter raygunReporter() { if (raygunEnabled && !raygunApiKey.isEmpty()) { return new TitlielRaygunReporter(raygunApiKey); } return null; } @Bean public RaygunSpringInterceptor raygunInterceptor() { return new RaygunSpringInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { if (raygunEnabled) { registry.addInterceptor(raygunInterceptor()); } } }

Configuration Management

package com.titliel.raygun; import java.io.InputStream; import java.util.*; /** * Raygun configuration manager */ public class RaygunConfig { private static RaygunConfig instance; private final Properties properties; private RaygunConfig() { this.properties = loadProperties(); } public static synchronized RaygunConfig getInstance() { if (instance == null) { instance = new RaygunConfig(); } return instance; } private Properties loadProperties() { Properties props = new Properties(); try (InputStream input = getClass().getClassLoader() .getResourceAsStream("raygun.properties")) { if (input != null) { props.load(input); } else { setDefaults(props); } } catch (Exception e) { setDefaults(props); } return props; } private void setDefaults(Properties props) { props.setProperty("raygun.environment", "development"); props.setProperty("raygun.app.version", "1.0.0"); props.setProperty("raygun.max.errors.per.minute", "100"); props.setProperty("raygun.fallback.logging.enabled", "true"); props.setProperty("raygun.report.environments", "production,staging"); props.setProperty("raygun.ignored.exceptions", "java.lang.InterruptedException,org.springframework.dao.OptimisticLockingFailureException"); } // Getters public String getEnvironment() { return properties.getProperty("raygun.environment", "development"); } public String getAppVersion() { return properties.getProperty("raygun.app.version", "1.0.0"); } public String getDeploymentId() { return properties.getProperty("raygun.deployment.id", System.getenv("DEPLOYMENT_ID")); } public String getMachineName() { return properties.getProperty("raygun.machine.name", System.getenv("HOSTNAME")); } public int getMaxErrorsPerMinute() { return Integer.parseInt(properties.getProperty("raygun.max.errors.per.minute", "100")); } public boolean isFallbackLoggingEnabled() { return Boolean.parseBoolean(properties.getProperty("raygun.fallback.logging.enabled", "true")); } public List<String> getReportEnvironments() { String environments = properties.getProperty("raygun.report.environments", "production,staging"); return Arrays.asList(environments.split(",")); } public List<String> getIgnoredExceptions() { String exceptions = properties.getProperty("raygun.ignored.exceptions", ""); return Arrays.asList(exceptions.split(",")); } }

Usage Examples

package com.titliel.service; import com.titliel.raygun.TitlielRaygunReporter; import com.titliel.raygun.ErrorContext; import com.titliel.raygun.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; @Service public class UserService { @Autowired private TitlielRaygunReporter raygunReporter; public User createUser(User user) { try { // Set user context for error tracking raygunReporter.setUserSession(createUserSession(user)); // Business logic validateUser(user); User savedUser = userRepository.save(user); return savedUser; } catch (ValidationException e) { // Report handled validation error ErrorContext context = new ErrorContext() .setFeature("user.validation") .setHandled(true) .setCustomData(Map.of( "userId", user.getId(), "email", user.getEmail(), "validationRule", e.getRule() )); raygunReporter.reportHandledError(e, "user-creation", context.getCustomData()); throw e; } catch (DatabaseException e) { // Report critical database error ErrorContext context = new ErrorContext() .setFeature("user.persistence") .setHandled(false) .setCustomData(Map.of( "userId", user.getId(), "database", "users-db", "operation", "insert" )); raygunReporter.reportError(e, context); throw e; } } public void processUserBatch(List<User> users) { long startTime = System.currentTimeMillis(); try { for (User user : users) { processUser(user); } } catch (Exception e) { // Report batch processing error with performance context long duration = System.currentTimeMillis() - startTime; Map<String, Object> performanceMetrics = new HashMap<>(); performanceMetrics.put("batchSize", users.size()); performanceMetrics.put("processingTime", duration); performanceMetrics.put("successCount", calculateSuccessCount(users)); ErrorContext context = new ErrorContext() .setFeature("batch.processing") .setHandled(true) .setPerformanceMetrics(performanceMetrics) .setCustomData(Map.of( "batchId", UUID.randomUUID().toString(), "failedUser", getFailedUser(e) )); raygunReporter.reportHandledError(e, "user-batch", context.getCustomData()); } finally { // Report performance metrics long duration = System.currentTimeMillis() - startTime; if (duration > 30000) { // 30 second threshold raygunReporter.reportPerformanceIssue( "processUserBatch", duration, 30000 ); } } } private UserSession createUserSession(User user) { UserSession session = new UserSession(); session.setUserId(user.getId()); session.setEmail(user.getEmail()); session.setFullName(user.getFullName()); session.setSessionId(UUID.randomUUID().toString()); return session; } private void validateUser(User user) { if (user.getEmail() == null || !user.getEmail().contains("@")) { throw new ValidationException("Invalid email format", "email_validation"); } } // Helper methods private int calculateSuccessCount(List<User> users) { // Implementation details return users.size() - 1; // Example } private String getFailedUser(Exception e) { // Extract failed user from exception return "unknown"; } } /** * Custom exception for validation */ class ValidationException extends RuntimeException { private final String rule; public ValidationException(String message, String rule) { super(message); this.rule = rule; } public String getRule() { return rule; } } class DatabaseException extends RuntimeException { public DatabaseException(String message, Throwable cause) { super(message, cause); } }

Maven Dependencies

<dependencies> <!-- Raygun Java Client --> <dependency> <groupId>com.raygun</groupId> <artifactId>raygun4java</artifactId> <version>2.4.0</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.0</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> </dependencies>

Application Properties

# Raygun Configuration raygun.api.key=your-api-key-here raygun.enabled=true raygun.environment=production raygun.app.version=1.2.0 # Error Reporting Settings raygun.max.errors.per.minute=100 raygun.fallback.logging.enabled=true raygun.report.environments=production,staging # Ignored Exceptions raygun.ignored.exceptions=java.lang.InterruptedException,org.springframework.dao.OptimisticLockingFailureException

Best Practices

1. Error Classification

  • Use appropriate severity levels
  • Distinguish between handled and unhandled errors
  • Group similar errors effectively

2. User Context

  • Always set user information for error correlation
  • Include relevant user properties
  • Maintain session context

3. Performance Monitoring

  • Report performance issues as errors
  • Include timing and resource metrics
  • Set appropriate thresholds

4. Rate Limiting

  • Implement error rate limiting
  • Monitor dropped error rates
  • Adjust limits based on volume

5. Environment Management

  • Configure different settings per environment
  • Use appropriate API keys
  • Monitor error rates across environments

This Titliel Raygun integration provides comprehensive error monitoring with advanced features like performance tracking, user correlation, and intelligent error grouping for enterprise Java applications.

Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection

Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.

Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.

Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.

Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.

Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.

Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.

Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.

JAVA CODE COMPILER

FREE ONLINE JAVA CODE COMPILER

Leave a Reply

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


Macro Nepal Helper