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

  1. Environment-Specific Configuration: Use different Sentry projects or environments for development, testing, and production.

  2. Use Breadcrumbs: Add breadcrumbs to improve context for errors:

    Sentry.addBreadcrumb("User clicked on 'Save'");
    
  3. 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");
    });
    
  4. Tags for Filtering: Use tags to group and filter events:

    Sentry.setTag("feature", "payment");
    
  5. 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:

  1. Self-Hosting: For strict data protection requirements, you can host Sentry on your own servers.

  2. 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;
        }
    }
    
  3. 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.

Further Resources