Introduction to Sentry Performance Monitoring
Sentry Performance provides distributed tracing and performance monitoring for Java applications. It helps identify bottlenecks, track slow transactions, and monitor application performance across microservices.
Maven Configuration
<properties>
<sentry.version>6.28.0</sentry.version>
<opentelemetry.version>1.28.0</opentelemetry.version>
</properties>
<dependencies>
<!-- Sentry Core -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry</artifactId>
<version>${sentry.version}</version>
</dependency>
<!-- Sentry Spring Boot Starter -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter</artifactId>
<version>${sentry.version}</version>
</dependency>
<!-- Sentry Logback Integration -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-logback</artifactId>
<version>${sentry.version}</version>
</dependency>
<!-- OpenTelemetry Integration -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-opentelemetry</artifactId>
<version>${sentry.version}</version>
</dependency>
<!-- Database Performance Monitoring -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-jdbc</artifactId>
<version>${sentry.version}</version>
</dependency>
</dependencies>
Core Configuration
Sentry Spring Boot Auto-Configuration
@Configuration
@EnableConfigurationProperties(SentryProperties.class)
public class SentryConfig {
private static final Logger logger = LoggerFactory.getLogger(SentryConfig.class);
@Bean
@ConditionalOnProperty(name = "sentry.enabled", havingValue = "true")
public SentryOptions sentryOptions(SentryProperties properties) {
try {
SentryOptions options = new SentryOptions();
// Basic configuration
options.setDsn(properties.getDsn());
options.setEnvironment(properties.getEnvironment());
options.setRelease(properties.getRelease());
options.setDebug(properties.isDebug());
// Performance monitoring
options.setTracesSampleRate(properties.getTracesSampleRate());
options.setProfilesSampleRate(properties.getProfilesSampleRate());
options.setEnableTracing(properties.isEnableTracing());
options.setTracePropagationTargets(properties.getTracePropagationTargets());
// Session tracking
options.setEnableSessionTracking(properties.isEnableSessionTracking());
options.setSessionTrackingIntervalMillis(properties.getSessionTrackingIntervalMillis());
// In-app excludes
options.setInAppIncludes(properties.getInAppIncludes());
options.setInAppExcludes(properties.getInAppExcludes());
// Transport configuration
options.setEnableShutdownHook(properties.isEnableShutdownHook());
options.setFlushTimeoutMillis(properties.getFlushTimeoutMillis());
// Before send callback for filtering
options.setBeforeSend((event, hint) -> {
// Filter out health check events
if (event.getTransaction() != null &&
event.getTransaction().contains("health")) {
return null;
}
return event;
});
// Before breadcrumb callback
options.setBeforeBreadcrumb((breadcrumb, hint) -> {
// Filter out noisy breadcrumbs
if ("http".equals(breadcrumb.getCategory()) &&
breadcrumb.getData().containsKey("url") &&
breadcrumb.getData().get("url").toString().contains("actuator")) {
return null;
}
return breadcrumb;
});
logger.info("Sentry initialized for environment: {}", properties.getEnvironment());
return options;
} catch (Exception e) {
logger.error("Failed to initialize Sentry", e);
throw new RuntimeException("Sentry initialization failed", e);
}
}
@Bean
@ConditionalOnMissingBean
public SentryTransactionPerformanceCollector sentryPerformanceCollector() {
return new SentryTransactionPerformanceCollector();
}
}
@ConfigurationProperties(prefix = "sentry")
public class SentryProperties {
private boolean enabled = true;
private String dsn;
private String environment = "development";
private String release = "1.0.0";
private boolean debug = false;
// Performance monitoring
private double tracesSampleRate = 1.0;
private double profilesSampleRate = 0.0;
private boolean enableTracing = true;
private List<String> tracePropagationTargets = Arrays.asList(".*");
// Session tracking
private boolean enableSessionTracking = true;
private long sessionTrackingIntervalMillis = 30000;
// Application boundaries
private List<String> inAppIncludes = Arrays.asList("com.yourcompany");
private List<String> inAppExcludes = Arrays.asList("org.springframework", "ch.qos.logback");
// Transport settings
private boolean enableShutdownHook = true;
private long flushTimeoutMillis = 15000;
// Getters and setters
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getDsn() { return dsn; }
public void setDsn(String dsn) { this.dsn = dsn; }
public String getEnvironment() { return environment; }
public void setEnvironment(String environment) { this.environment = environment; }
public String getRelease() { return release; }
public void setRelease(String release) { this.release = release; }
public boolean isDebug() { return debug; }
public void setDebug(boolean debug) { this.debug = debug; }
public double getTracesSampleRate() { return tracesSampleRate; }
public void setTracesSampleRate(double tracesSampleRate) { this.tracesSampleRate = tracesSampleRate; }
public double getProfilesSampleRate() { return profilesSampleRate; }
public void setProfilesSampleRate(double profilesSampleRate) { this.profilesSampleRate = profilesSampleRate; }
public boolean isEnableTracing() { return enableTracing; }
public void setEnableTracing(boolean enableTracing) { this.enableTracing = enableTracing; }
public List<String> getTracePropagationTargets() { return tracePropagationTargets; }
public void setTracePropagationTargets(List<String> tracePropagationTargets) {
this.tracePropagationTargets = tracePropagationTargets;
}
public boolean isEnableSessionTracking() { return enableSessionTracking; }
public void setEnableSessionTracking(boolean enableSessionTracking) {
this.enableSessionTracking = enableSessionTracking;
}
public long getSessionTrackingIntervalMillis() { return sessionTrackingIntervalMillis; }
public void setSessionTrackingIntervalMillis(long sessionTrackingIntervalMillis) {
this.sessionTrackingIntervalMillis = sessionTrackingIntervalMillis;
}
public List<String> getInAppIncludes() { return inAppIncludes; }
public void setInAppIncludes(List<String> inAppIncludes) { this.inAppIncludes = inAppIncludes; }
public List<String> getInAppExcludes() { return inAppExcludes; }
public void setInAppExcludes(List<String> inAppExcludes) { this.inAppExcludes = inAppExcludes; }
public boolean isEnableShutdownHook() { return enableShutdownHook; }
public void setEnableShutdownHook(boolean enableShutdownHook) {
this.enableShutdownHook = enableShutdownHook;
}
public long getFlushTimeoutMillis() { return flushTimeoutMillis; }
public void setFlushTimeoutMillis(long flushTimeoutMillis) {
this.flushTimeoutMillis = flushTimeoutMillis;
}
}
Manual Transaction Instrumentation
Custom Transaction Management
@Service
public class OrderProcessingService {
private final ITransaction transaction;
public OrderProcessingService() {
this.transaction = Sentry.startTransaction("Order Processing", "task");
}
public Order processOrder(OrderRequest request) {
// Set transaction context
transaction.setOperation("order.process");
transaction.setData("order.id", request.getOrderId());
transaction.setData("customer.id", request.getCustomerId());
ISpan processSpan = transaction.startChild("process");
try {
// Process order steps with spans
validateOrder(request);
reserveInventory(request);
processPayment(request);
sendConfirmation(request);
// Mark as successful
processSpan.setStatus(SpanStatus.OK);
transaction.setStatus(SpanStatus.OK);
return createOrderResponse(request);
} catch (Exception e) {
// Record failure
processSpan.setThrowable(e);
processSpan.setStatus(SpanStatus.INTERNAL_ERROR);
transaction.setThrowable(e);
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
processSpan.finish();
transaction.finish();
}
}
private void validateOrder(OrderRequest request) {
ISpan span = transaction.startChild("validation");
span.setDescription("Validate order requirements");
try {
if (request.getAmount() <= 0) {
throw new ValidationException("Invalid order amount");
}
// Simulate validation logic
Thread.sleep(50);
span.setStatus(SpanStatus.OK);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
span.setThrowable(e);
span.setStatus(SpanStatus.INTERNAL_ERROR);
throw new RuntimeException("Validation interrupted", e);
} catch (Exception e) {
span.setThrowable(e);
span.setStatus(SpanStatus.INVALID_ARGUMENT);
throw e;
} finally {
span.finish();
}
}
private void processPayment(OrderRequest request) {
ISpan span = transaction.startChild("payment");
span.setOperation("payment.process");
span.setData("payment.amount", request.getAmount());
span.setData("payment.method", request.getPaymentMethod());
try {
boolean success = paymentGateway.charge(request);
if (!success) {
throw new PaymentException("Payment processing failed");
}
span.setStatus(SpanStatus.OK);
} catch (Exception e) {
span.setThrowable(e);
span.setStatus(SpanStatus.FAILED_PRECONDITION);
throw e;
} finally {
span.finish();
}
}
}
Spring Boot Integration
Web Request Tracing
@Component
public class SentryPerformanceFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(SentryPerformanceFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Start transaction for this request
String transactionName = httpRequest.getMethod() + " " + httpRequest.getRequestURI();
ITransaction transaction = Sentry.startTransaction(transactionName, "http.server");
// Set transaction context
transaction.setOperation("http.server");
transaction.setData("http.method", httpRequest.getMethod());
transaction.setData("http.url", httpRequest.getRequestURL().toString());
transaction.setData("http.query_string", httpRequest.getQueryString());
transaction.setData("http.user_agent", httpRequest.getHeader("User-Agent"));
try {
chain.doFilter(request, response);
// Record successful completion
transaction.setData("http.status_code", httpResponse.getStatus());
transaction.setStatus(SpanStatus.OK);
} catch (Exception e) {
// Record failure
transaction.setThrowable(e);
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
transaction.finish();
}
}
}
@Configuration
public class SentryWebConfiguration {
@Bean
public FilterRegistrationBean<SentryPerformanceFilter> sentryPerformanceFilter() {
FilterRegistrationBean<SentryPerformanceFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new SentryPerformanceFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
@Bean
public SentryRequestListener sentryRequestListener() {
return new SentryRequestListener();
}
}
@Component
public class SentryRequestListener {
@EventListener
public void handleRequestEvent(ServletRequestHandledEvent event) {
// Capture request timing metrics
Map<String, Object> data = new HashMap<>();
data.put("request_url", event.getRequestUrl());
data.put("client_address", event.getClientAddress());
data.put("method", event.getMethod());
data.put("processing_time_ms", event.getProcessingTimeMillis());
Sentry.addBreadcrumb(Breadcrumb.http(
event.getRequestUrl(),
event.getMethod(),
(Integer) event.getStatusCode()
));
// Record custom metric
Sentry.metrics().timing("http.request.duration",
event.getProcessingTimeMillis(),
TimeUnit.MILLISECONDS,
Map.of(
"method", event.getMethod(),
"status", String.valueOf(event.getStatusCode()),
"url", event.getRequestUrl()
));
}
}
Database Performance Monitoring
JDBC Integration
@Configuration
public class SentryJdbcConfig {
@Bean
@Primary
public DataSource dataSource(SentryProperties properties) {
DataSource realDataSource = createRealDataSource();
if (properties.isEnabled() && properties.isEnableTracing()) {
return SentryDataSource.create(realDataSource);
}
return realDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
@Aspect
@Component
public class RepositoryPerformanceAspect {
private static final Logger logger = LoggerFactory.getLogger(RepositoryPerformanceAspect.class);
@Around("execution(* org.springframework.data.repository.Repository+.*(..))")
public Object traceRepositoryMethods(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String repositoryName = joinPoint.getTarget().getClass().getSimpleName();
ISpan span = Sentry.startSpan("db.repository", repositoryName + "." + methodName);
try {
Object result = joinPoint.proceed();
// Record result metrics
if (result instanceof Collection) {
span.setData("result.count", ((Collection<?>) result).size());
}
span.setStatus(SpanStatus.OK);
return result;
} catch (Exception e) {
span.setThrowable(e);
span.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
span.finish();
}
}
}
@Service
public class OrderRepositoryService {
private final JdbcTemplate jdbcTemplate;
public OrderRepositoryService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@SentryTransaction(operation = "db.query")
public List<Order> findOrdersByCustomer(String customerId, Date startDate, Date endDate) {
String sql = """
SELECT * FROM orders
WHERE customer_id = ?
AND created_date BETWEEN ? AND ?
ORDER BY created_date DESC
""";
// This query will be automatically traced by Sentry JDBC integration
return jdbcTemplate.query(sql, new OrderRowMapper(), customerId, startDate, endDate);
}
@SentryTransaction(operation = "db.update")
public void updateOrderStatus(String orderId, String status) {
String sql = "UPDATE orders SET status = ? WHERE id = ?";
// This update will be automatically traced
jdbcTemplate.update(sql, status, orderId);
// Add custom breadcrumb
Sentry.addBreadcrumb(Breadcrumb.info(
"Order status updated",
Map.of("order_id", orderId, "new_status", status)
));
}
}
Custom Performance Monitoring
Business Transaction Monitoring
@Component
public class BusinessTransactionMonitor {
public <T> T monitorBusinessTransaction(String transactionName,
String operation,
Supplier<T> operationLogic) {
ITransaction transaction = Sentry.startTransaction(transactionName, "business");
transaction.setOperation(operation);
try {
T result = operationLogic.get();
transaction.setStatus(SpanStatus.OK);
return result;
} catch (Exception e) {
transaction.setThrowable(e);
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
transaction.finish();
}
}
public void monitorAsyncOperation(String operationName, CompletableFuture<?> future) {
ISpan span = Sentry.startSpan("async", operationName);
future.whenComplete((result, throwable) -> {
if (throwable != null) {
span.setThrowable(throwable);
span.setStatus(SpanStatus.INTERNAL_ERROR);
} else {
span.setStatus(SpanStatus.OK);
}
span.finish();
});
}
}
@Service
public class OrderService {
private final BusinessTransactionMonitor monitor;
public OrderService(BusinessTransactionMonitor monitor) {
this.monitor = monitor;
}
public Order processComplexOrder(ComplexOrderRequest request) {
return monitor.monitorBusinessTransaction(
"Complex Order Processing",
"order.complex.process",
() -> {
// Complex business logic with multiple steps
validateComplexOrder(request);
calculatePricing(request);
applyDiscounts(request);
processMultiplePayments(request);
scheduleFulfillment(request);
return createComplexOrderResponse(request);
});
}
public CompletableFuture<Order> processOrderAsync(OrderRequest request) {
CompletableFuture<Order> future = CompletableFuture.supplyAsync(() -> {
return processOrder(request);
});
monitor.monitorAsyncOperation("async.order.process", future);
return future;
}
}
Performance Metrics Collection
Custom Metrics and Monitoring
@Component
public class OrderPerformanceMetrics {
private final Timer orderProcessingTimer;
private final Counter orderSuccessCounter;
private final Counter orderFailureCounter;
private final Gauge inventoryLevelGauge;
public OrderPerformanceMetrics() {
// Initialize metrics
this.orderProcessingTimer = Sentry.metrics().timer("order.processing.duration");
this.orderSuccessCounter = Sentry.metrics().counter("order.success.total");
this.orderFailureCounter = Sentry.metrics().counter("order.failure.total");
this.inventoryLevelGauge = Sentry.metrics().gauge("inventory.level");
}
public void recordOrderSuccess(long processingTimeMs, String orderType, String customerTier) {
orderSuccessCounter.increment(1, Map.of(
"order_type", orderType,
"customer_tier", customerTier
));
orderProcessingTimer.record(processingTimeMs, TimeUnit.MILLISECONDS, Map.of(
"status", "success",
"order_type", orderType
));
// Record custom event for business analytics
Sentry.captureMessage(
String.format("Order processed successfully in %d ms", processingTimeMs),
SentryLevel.INFO,
scope -> {
scope.setContexts("order", Map.of(
"processing_time_ms", processingTimeMs,
"type", orderType,
"customer_tier", customerTier
));
}
);
}
public void recordOrderFailure(long processingTimeMs, String orderType, String errorReason) {
orderFailureCounter.increment(1, Map.of(
"order_type", orderType,
"error_reason", errorReason
));
orderProcessingTimer.record(processingTimeMs, TimeUnit.MILLISECONDS, Map.of(
"status", "failure",
"order_type", orderType
));
}
public void updateInventoryMetrics(Map<String, Integer> inventoryLevels) {
inventoryLevels.forEach((productId, level) -> {
inventoryLevelGauge.set(level, Map.of("product_id", productId));
});
}
public void recordSlowOperation(String operation, long durationMs, Map<String, String> tags) {
if (durationMs > 1000) { // Threshold for slow operations
Sentry.captureMessage(
String.format("Slow operation detected: %s took %d ms", operation, durationMs),
SentryLevel.WARNING,
scope -> {
scope.setContexts("performance", Map.of(
"operation", operation,
"duration_ms", durationMs,
"tags", tags
));
}
);
}
}
}
Distributed Tracing
Cross-Service Tracing
@Component
public class DistributedTracingService {
public <T> T executeWithContextPropagation(String operation, Supplier<T> operationLogic) {
// Get current Sentry trace context
TraceContext traceContext = Sentry.getTraceContext();
if (traceContext != null) {
// Propagate trace context to downstream services
propagateTraceContext(traceContext);
}
ISpan span = Sentry.startSpan("rpc", operation);
try {
T result = operationLogic.get();
span.setStatus(SpanStatus.OK);
return result;
} catch (Exception e) {
span.setThrowable(e);
span.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
span.finish();
}
}
private void propagateTraceContext(TraceContext traceContext) {
// Extract trace headers for propagation
Map<String, String> traceHeaders = new HashMap<>();
if (traceContext.getTraceId() != null) {
traceHeaders.put("sentry-trace", traceContext.getTraceId());
}
if (traceContext.getBaggage() != null) {
traceHeaders.put("baggage", traceContext.getBaggage());
}
// Store in thread-local for HTTP clients to pick up
SentryTraceHeaderStorage.setHeaders(traceHeaders);
}
}
@Component
public class TracingRestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// Get trace headers from storage
Map<String, String> traceHeaders = SentryTraceHeaderStorage.getHeaders();
if (traceHeaders != null) {
traceHeaders.forEach(request.getHeaders()::set);
}
// Start span for outgoing request
ISpan span = Sentry.startSpan("http.client",
request.getMethod() + " " + request.getURI().getHost());
span.setData("http.method", request.getMethod().name());
span.setData("http.url", request.getURI().toString());
try {
ClientHttpResponse response = execution.execute(request, body);
span.setData("http.status_code", response.getRawStatusCode());
span.setStatus(SpanStatus.OK);
return response;
} catch (IOException e) {
span.setThrowable(e);
span.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
span.finish();
}
}
}
// Thread-local storage for trace headers
public class SentryTraceHeaderStorage {
private static final ThreadLocal<Map<String, String>> TRACE_HEADERS =
new ThreadLocal<>();
public static void setHeaders(Map<String, String> headers) {
TRACE_HEADERS.set(headers);
}
public static Map<String, String> getHeaders() {
return TRACE_HEADERS.get();
}
public static void clear() {
TRACE_HEADERS.remove();
}
}
Error Tracking with Performance Context
Enhanced Error Reporting
@Service
public class ErrorTrackingService {
public void trackErrorWithPerformanceContext(String errorMessage, Throwable throwable) {
ITransaction currentTransaction = Sentry.getCurrentTransaction();
Sentry.withScope(scope -> {
// Add performance context to error
if (currentTransaction != null) {
scope.setContexts("performance", Map.of(
"transaction_id", currentTransaction.getEventId(),
"transaction_name", currentTransaction.getName(),
"transaction_operation", currentTransaction.getOperation()
));
}
// Add custom fingerprints for grouping
scope.setFingerprints(Arrays.asList(
errorMessage,
throwable.getClass().getSimpleName()
));
// Capture the exception
Sentry.captureException(throwable);
});
}
public void trackBusinessError(String errorCode, String message, Map<String, Object> context) {
Sentry.withScope(scope -> {
scope.setTag("error_type", "business");
scope.setTag("error_code", errorCode);
scope.setContexts("business", context);
scope.setLevel(SentryLevel.WARNING);
Sentry.captureMessage(message);
});
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
private final ErrorTrackingService errorTrackingService;
public GlobalExceptionHandler(ErrorTrackingService errorTrackingService) {
this.errorTrackingService = errorTrackingService;
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e, HttpServletRequest request) {
// Track error with performance context
errorTrackingService.trackErrorWithPerformanceContext(
"Unhandled exception in web request", e);
// Create error response
ErrorResponse errorResponse = new ErrorResponse(
"INTERNAL_ERROR",
"An unexpected error occurred",
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResponse);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
// Track business error
errorTrackingService.trackBusinessError(
e.getErrorCode(),
e.getMessage(),
e.getContext()
);
ErrorResponse errorResponse = new ErrorResponse(
e.getErrorCode(),
e.getMessage(),
null
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(errorResponse);
}
}
Configuration Examples
application.yml
sentry:
enabled: true
dsn: ${SENTRY_DSN:}
environment: ${ENVIRONMENT:development}
release: ${APP_VERSION:1.0.0}
debug: false
traces-sample-rate: 0.5
profiles-sample-rate: 0.1
enable-tracing: true
enable-session-tracking: true
session-tracking-interval-millis: 30000
in-app-includes:
- "com.yourcompany"
- "org.yourproject"
in-app-excludes:
- "org.springframework"
- "ch.qos.logback"
- "io.sentry"
trace-propagation-targets:
- ".*\\.yourcompany\\.com"
- ".*\\.yourapi\\.com"
logging:
level:
io.sentry: INFO
Programmatic Configuration
@Configuration
@Profile("production")
public class ProductionSentryConfig {
@Bean
public SentryOptions customSentryOptions() {
SentryOptions options = new SentryOptions();
options.setDsn(System.getenv("SENTRY_DSN"));
options.setEnvironment("production");
options.setRelease(System.getenv("APP_VERSION"));
// Sample 10% of transactions in production
options.setTracesSampleRate(0.1);
options.setProfilesSampleRate(0.05);
// Custom tags for all events
options.setTag("data_center", System.getenv("DATA_CENTER"));
options.setTag("service_instance", System.getenv("HOSTNAME"));
// Custom transport for high throughput
options.setTransportFactory(new AsyncHttpTransportFactory());
return options;
}
}
Testing and Development
Test Configuration
@SpringBootTest
@TestPropertySource(properties = {
"sentry.enabled=false",
"sentry.dsn=http://test@localhost/1"
})
public abstract class BaseIntegrationTest {
@MockBean
protected IHub sentryHub;
@BeforeEach
void setupSentryMocks() {
// Mock Sentry behavior for tests
when(sentryHub.isEnabled()).thenReturn(false);
when(sentryHub.captureTransaction(any(Transaction.class))).thenReturn(SentryId.EMPTY_ID);
}
}
@Configuration
@Profile("test")
public class TestSentryConfig {
@Bean
@Primary
public IHub mockSentryHub() {
return new NoOpHub();
}
}
@Component
@Primary
@Profile("test")
public class NoOpPerformanceMonitor extends BusinessTransactionMonitor {
@Override
public <T> T monitorBusinessTransaction(String transactionName,
String operation,
Supplier<T> operationLogic) {
return operationLogic.get();
}
}
Performance Analysis Utilities
@Component
public class PerformanceAnalyzer {
private final Map<String, PerformanceStats> stats = new ConcurrentHashMap<>();
public void recordOperation(String operation, long durationMs, boolean success) {
stats.computeIfAbsent(operation, k -> new PerformanceStats())
.recordOperation(durationMs, success);
}
public PerformanceReport generateReport() {
Map<String, OperationStats> operationStats = new HashMap<>();
stats.forEach((operation, stat) -> {
operationStats.put(operation, stat.getStats());
});
return new PerformanceReport(operationStats);
}
public void reportSlowOperations() {
stats.forEach((operation, stat) -> {
OperationStats opStats = stat.getStats();
if (opStats.getP95() > 1000) { // 1 second threshold
Sentry.captureMessage(
String.format("Slow operation detected: %s (p95: %d ms)",
operation, opStats.getP95()),
SentryLevel.WARNING
);
}
});
}
private static class PerformanceStats {
private final List<Long> durations = new CopyOnWriteArrayList<>();
private final AtomicLong successCount = new AtomicLong();
private final AtomicLong failureCount = new AtomicLong();
public void recordOperation(long durationMs, boolean success) {
durations.add(durationMs);
if (success) {
successCount.incrementAndGet();
} else {
failureCount.incrementAndGet();
}
}
public OperationStats getStats() {
if (durations.isEmpty()) {
return new OperationStats(0, 0, 0, 0, 0);
}
List<Long> sorted = new ArrayList<>(durations);
Collections.sort(sorted);
return new OperationStats(
sorted.get(0), // min
sorted.get(sorted.size() - 1), // max
sorted.get(sorted.size() / 2), // median
calculatePercentile(sorted, 95), // p95
successCount.get() + failureCount.get() // total
);
}
private long calculatePercentile(List<Long> sorted, double percentile) {
int index = (int) Math.ceil(percentile / 100.0 * sorted.size());
return sorted.get(Math.min(index, sorted.size() - 1));
}
}
}
Conclusion
This Sentry Performance implementation for Java provides:
- Comprehensive Tracing - Automatic and manual transaction monitoring
- Spring Boot Integration - Seamless Spring ecosystem integration
- Database Performance - JDBC and repository-level performance monitoring
- Business Metrics - Custom metrics for business transactions
- Distributed Tracing - Cross-service context propagation
- Error Correlation - Link errors with performance data
- Custom Monitoring - Business-specific performance tracking
- Testing Support - Mock implementations for testing environments
The implementation enables full-stack performance monitoring with minimal code changes while providing deep insights into application performance bottlenecks and user experience impacts.