Greeting, I’m Wanchalerm Popee, a senior backend engineer of AnyTag/AnyCreator production team at AnyMind group. In this article, we’re going to set up Sentry integrated with Spring Boot, an application for monitoring and error tracking.
Covering topics
- What is Sentry?
- Sentry Project Setup
- Integrating Sentry with Spring Boot
- Testing our first event
- Grouping exceptions by application runtime environment
- Capturing errors events only unhandled exceptions
- Conclusion
What is Sentry?
Sentry is the tool to monitor and track application error especially unhandled exception that may happen unintentionally. It automates the exception handling for every popular programming languages, and platforms, so it would be the best choice if you’re running the microservices and want to have centralized tracking tool in one place. To know more about Sentry, please visit the website sentry.io.
Supported Sentry platforms.
Sentry Project Setup
1.) Steps to create new Sentry account is very easy and straight forward. If you don’t have please register an account first here .
2.) Next is to create new project and choose the platform. In our case, let’s choose Spring Boot!
3.) Congratulations! Now everything is ready, and you will be able to see Sentry Dashboard like this!
Integrating Sentry with Spring Boot
1.) Install Sentry SDK
First of all, we need to install Sentry SDK which will be used to capture data in our application’s runtime. We can simply install SDK dependency using Gradle.
implementation 'io.sentry:sentry-spring-boot-starter:5.2.0'
2.) Make sure you define the sentry.dsn
, which as know as Data Source Name, on application properties file (src/main/application.yml
),
so our project can know where error reports should be logged to.
sentry:
dsn: https://examplePublicKey@o0.ingest.sentry.io/0
3.) Now we’re all set! Next let’s make our first event.
Testing our first event
1.) Before we start, I will create simple REST API to have unhandled IndexOutOfBoundsException
unintentionally.
package com.example.demosentry.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
data class APIResponse(val ok: Boolean)
@RestController
class SampleController {
@GetMapping("/unhandled/error")
fun unhandledException(): APIResponse {
// let create unhandled error exception
val aList = listOf("a", "b", "c")
print(aList[5])
return APIResponse(
ok = true
)
}
}
2.) I will call my API and in the result, it should raise an exception. In meantime, the event will be passed to Sentry.
3.) Open the Sentry dashboard and verify whether we have received the error message.
Finally! the error event has been captured! The best thing is, it’s showing the entire details together with the line number where an exception is happened.
Grouping exceptions by application runtime environment
It would be nice in the real world application if the exceptions can be grouped by runtime environment such as STAGING or PRODUCTION because it can help developer to easily filter the issues. Fortunately, Sentry provides this feature, and it’s very easy to set up within few seconds.
Basically, if you’re running application on multiple environments, you properly need to set active profile by passing SPRING_PROFILES_ACTIVE
environment variable.
In order to make it works, let’s split application properties files for staging
and production
. Lastly, passing sentry.environment
property like following will be required.
For STAGING
/src/main/resources/application-staging.yml
sentry:
dsn: https://examplePublicKey@o0.ingest.sentry.io/0
environment: staging
For PRODUCTION
/src/main/resources/application-production.yml
sentry:
dsn: https://examplePublicKey@o0.ingest.sentry.io/0
environment: production
Let’s open Sentry dashboard again and check what it looks like after the new events have been captured!
As you can see, that’s all of we need, Sentry automatically creates production
and staging
environment filtering for us.
In my opinion as developer, this is a very useful feature because I can save my times to filter out the unwanted issues and
efficiently focus on what I’m looking for.
Capturing errors events only unhandled exceptions
Most of the time, the custom domain error may be needed for a specific application instead of regularly raising an internal server error
even thought it’s handled error in our try-catch or validation. Alternatively, it can be considered as the business domain error.
Those kinds of exception should not be caught and reported to Sentry because it’s not actual unexpected errors.
Luckily, Sentry comes with an ability to catch only an exception’s certain type by implementing the SentryOptions.BeforeSendCallback
bean.
In the next example, we will create an additional custom error called DomainException
which is implementing RuntimeException
.
Apparently this is our business domain error which should not be reported to Sentry. Next step is, we need to
implement SentryOptions.BeforeSendCallback
to not catch an exception (return null
) if DomainException
has been raised from our runtime.
package com.example.demosentry
import io.sentry.SentryEvent
import io.sentry.SentryOptions
import org.springframework.stereotype.Component
class DomainException(message:String): RuntimeException(message)
@Component
class SentryConfig : SentryOptions.BeforeSendCallback {
override fun execute(event: SentryEvent, hint: Any?): SentryEvent? {
if (event.throwable is DomainException) {
return null
}
return event
}
}
Also let’s add new REST API which have a handled exception properly.
package com.example.demosentry.controller
import com.example.demosentry.DomainException
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
data class APIResponse(val ok: Boolean)
@RestController
class SampleController {
@GetMapping("/unhandled/error")
fun unhandledException(): APIResponse {
// let create unhandled error exception
val aList = listOf("a", "b", "c")
print(aList[5])
return APIResponse(
ok = true
)
}
@GetMapping("/handled/error")
fun handledException(): APIResponse {
try {
val aList = listOf("a", "b", "c")
print(aList[5])
} catch (e: Exception) {
throw DomainException("there is something wrong but I know")
}
return APIResponse(
ok = true
)
}
}
Lastly, I will call my new API by Postman and open the Sentry dashboard UI to see what will happen.
At the result, we’re not be able to see the exception which was raised by DomainException
and yeah! this is what we expected!
Conclusion
Picking an application for monitoring and error tracking nowadays is very common in the real world because opening and checking log files line-by-line to find where is my error would be very painful especially the large-scale application. One of popular tool that we, AnyTag/AnyCreator team, chose is Sentry where developer can easily integrate with many programming languages, and platforms. Personally I’ve been using it a few years and feel very happy. Here are reasons.
- I can identify the error and solve it very quickly which is partially one of our company values, Move Faster.
- Steps to setup are simple and Sentry doc is easy to read.
- Pay on-demand if we exceed the quota of the plan. The less errors sent, the less we pay. Or in other words, the more we fix the error, the less we pay the cost. It sounds motivating you to fix error? 🙂
Thank you for reading.