Introduction
In modern software applications, reliable error monitoring and tracking is essential. Especially in production environments, errors can occur that are not reproducible in development or test environments. To solve these problems effectively, developers need detailed information about errors and their context. This is where Sentry comes into play – a powerful open-source platform for real-time error monitoring.
In this article, we will cover the integration of Sentry into Spring Boot applications to ensure robust error monitoring.
What is Sentry?
Sentry is an open-source platform for error monitoring that helps developers detect, fix, and prevent problems in real-time. It offers:
- Real-time error monitoring and reporting
- Detailed error reports with stack traces
- Context for each error (users, environment variables, HTTP requests)
- Performance monitoring
- Release tracking and notifications
- Integration with various development workflows and tools
Integrating Sentry with Spring Boot
Step 1: Add the Dependency
To integrate Sentry into a Spring Boot application, you first need to add the corresponding dependency to your project.
Maven
<properties>
<sentry.version>8.13.2</sentry.version>
</properties>
<dependencies>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-bom</artifactId>
<version>${sentry.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Gradle
ext {
set('sentryVersion', "8.13.2")
}
dependencyManagement {
imports {
mavenBom "io.sentry:sentry-bom:${sentryVersion}"
}
}
dependencies {
implementation 'io.sentry:sentry-spring-boot-starter-jakarta'
}
Step 2: Configuration
Configuring Sentry in a Spring Boot application is very simple thanks to the starter dependency. Add the following configuration to your application.properties or application.yml:
application.properties
sentry.dsn=https://examplePublicKey@o0.ingest.sentry.io/0
sentry.traces-sample-rate=1.0
sentry.environment=production
application.yml
sentry:
dsn: https://examplePublicKey@o0.ingest.sentry.io/0
traces-sample-rate: 1.0
environment: production
The dsn is the Data Source Name that you receive from Sentry when you create a new project. The traces-sample-rate determines what percentage of transactions are sent to Sentry (1.0 means 100%).
Step 3: Manually Capturing Errors
Although Sentry automatically captures unhandled exceptions, you can also manually capture errors and events:
import io.sentry.Sentry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExampleController {
@GetMapping("/test-sentry")
public String testSentry() {
try {
throw new Exception("This is a test error");
} catch (Exception e) {
Sentry.captureException(e);
return "Error was sent to Sentry!";
}
}
}
Step 4: Adding User Context
An important aspect of error monitoring is understanding which users are affected by a problem. With Sentry, you can add user context to errors:
import io.sentry.Sentry;
import io.sentry.protocol.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserContextController {
@GetMapping("/user-action")
public String userAction() {
// Set user context
User user = new User();
user.setId("12345");
user.setEmail("user@example.com");
user.setUsername("username");
Sentry.setUser(user);
// Execution of an action that might fail
try {
// Potentially error-prone code
throw new Exception("Error in user action");
} catch (Exception e) {
Sentry.captureException(e);
return "Action failed, error has been logged";
}
}
}
Step 5: Performance Monitoring
Sentry also supports performance monitoring, which can help you identify bottlenecks:
import io.sentry.ITransaction;
import io.sentry.Sentry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PerformanceController {
@GetMapping("/slow-operation")
public String slowOperation() {
// Start transaction
ITransaction transaction = Sentry.startTransaction("SlowOperation", "task");
try {
// Measure an operation with a span
transaction.startChild("database-query")
.setDescription("Executing a slow database query")
.finish();
// Measure another operation
transaction.startChild("processing")
.setDescription("Data processing")
.finish();
return "Operation completed";
} finally {
// Ensure the transaction is always finished
transaction.finish();
}
}
}
Advanced Configuration
Spring Boot Profiles
A common requirement is to enable Sentry only in specific environments. You can achieve this with Spring Profiles:
# application-dev.yml
sentry:
enabled: false
# application-prod.yml
sentry:
enabled: true
dsn: https://examplePublicKey@o0.ingest.sentry.io/0
environment: production
Filtering Sensitive Data
To prevent sensitive data from being sent to Sentry, you can implement a BeforeSpanProcessor:
import io.sentry.ISpan;
import io.sentry.SpanDataConvention;
import io.sentry.SpanStatus;
import io.sentry.protocol.SentryTransaction;
import org.springframework.stereotype.Component;
@Component
public class SensitiveDataFilter implements io.sentry.BeforeSpanProcessor {
@Override
public void process(ISpan span, @Nullable Object payload) {
// Remove sensitive data from span attributes
if (span.getData().containsKey("db.statement")) {
String query = (String) span.getData().get("db.statement");
// Anonymize SQL query (e.g., 'SELECT * FROM users WHERE password = "123"' -> 'SELECT * FROM users WHERE password = "?"')
span.setData("db.statement", anonymizeQuery(query));
}
// Remove credit card data from HTTP requests
if (payload != null && payload instanceof SentryTransaction) {
// Implement your own filtering logic here
}
}
private String anonymizeQuery(String query) {
// Implement your anonymization logic here
return query.replaceAll("password\\s*=\\s*['\"](.*?)['\"]", "password = '?'");
}
}
Custom Exception Handlers
In some cases, you might want to customize how Sentry captures exceptions. You can do this with a custom ExceptionHandler:
import io.sentry.Sentry;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
// Create event with additional tags
Sentry.withScope(scope -> {
scope.setTag("error-type", "global");
scope.setLevel(io.sentry.SentryLevel.ERROR);
Sentry.captureException(e);
});
// Return a friendly message to the user
return new ResponseEntity<>("An error occurred. Our team has been notified.",
HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException e) {
// Different handling for RuntimeExceptions
Sentry.withScope(scope -> {
scope.setTag("error-type", "runtime");
scope.setLevel(io.sentry.SentryLevel.WARNING);
Sentry.captureException(e);
});
return new ResponseEntity<>("A temporary error occurred. Please try again later.",
HttpStatus.SERVICE_UNAVAILABLE);
}
}
Best Practices
-
Environment-Specific Configuration: Use different Sentry projects or environments for development, testing, and production.
-
Use Breadcrumbs: Add breadcrumbs to improve context for errors:
Sentry.addBreadcrumb("User clicked on 'Save'");
-
Structured Logging: Use structured logging for better insights:
Sentry.withScope(scope -> { scope.setExtra("orderId", orderId); scope.setExtra("customerId", customerId); Sentry.captureMessage("Order could not be completed"); });
-
Tags for Filtering: Use tags to group and filter events:
Sentry.setTag("feature", "payment");
-
Regular Updates: Keep the Sentry library up-to-date to benefit from new features and bug fixes.
Data Protection and GDPR Compliance
When using Sentry, it's important to consider data protection requirements:
-
Self-Hosting: For strict data protection requirements, you can host Sentry on your own servers.
-
PII Filtering: Implement filters for personally identifiable information (PII):
import io.sentry.EventProcessor; import io.sentry.Hint; import io.sentry.SentryEvent; import org.springframework.stereotype.Component; @Component public class PiiEventProcessor implements EventProcessor { @Override public SentryEvent process(SentryEvent event, Hint hint) { // Remove PII from data if (event.getRequest() != null && event.getRequest().getCookies() != null) { event.getRequest().getCookies().remove("authToken"); } // Anonymize IP addresses if (event.getUser() != null) { event.getUser().setIpAddress("{{redacted}}"); } return event; } }
-
Consent: Ensure that users are informed about the collection of error information and have given their consent if necessary.
Conclusion
Integrating Sentry into Spring Boot applications provides a powerful solution for error monitoring and resolution. With minimal configuration effort, you can gain valuable insights into production issues and resolve them more effectively.
The automatic capture of exceptions, combined with the ability to add custom context and performance metrics, makes Sentry an essential tool for any development team working on robust Spring Boot applications.
By following the best practices presented and considering data protection aspects, you can ensure that your error monitoring is not only effective but also compliant with legal requirements.