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