Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/system-tests-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ jobs:
- sample: "sentry-samples-spring-boot-4"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-spring-boot-4-log4j2"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-spring-boot-4-webflux"
agent: "false"
agent-auto-init: "true"
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,33 @@
<scope>import</scope>
</dependency>
```
- Sentry can now configure Log4j2 automatically for Spring Boot 4 when `sentry-log4j2` is on the classpath and Log4j2 Core is the active logging backend ([#5403](https://github.com/getsentry/sentry-java/pull/5403))
- Enable automatic appender registration with:
```properties
sentry.logging.enabled=true
```
Automatic registration is disabled by default for now and will be enabled by default in the next major release.
- The appender is attached to the root logger by default. Configure one or more logger names with:
```properties
sentry.logging.loggers[0]=ROOT
sentry.logging.loggers[1]=com.example
```
- Configure the minimum level for creating breadcrumbs. The default is `INFO`:
```properties
sentry.logging.minimum-breadcrumb-level=INFO
```
- Configure the minimum level for creating Sentry error events. The default is `ERROR`:
```properties
sentry.logging.minimum-event-level=ERROR
```
- Configure the minimum level for sending Sentry structured logs. The default is `INFO`:
```properties
sentry.logging.minimum-level=INFO
```
Structured logs must also be enabled:
```properties
sentry.logs.enabled=true
```

### Fixes

Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ apiValidation {
"sentry-samples-spring-boot-webflux",
"sentry-samples-spring-boot-webflux-jakarta",
"sentry-samples-spring-boot-4",
"sentry-samples-spring-boot-4-log4j2",
"sentry-samples-spring-boot-4-opentelemetry",
"sentry-samples-spring-boot-4-opentelemetry-noagent",
"sentry-samples-spring-boot-4-otlp",
Expand Down
3 changes: 3 additions & 0 deletions sentry-log4j2/api/sentry-log4j2.api
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public class io/sentry/log4j2/SentryAppender : org/apache/logging/log4j/core/app
public static fun createAppender (Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/String;Ljava/lang/Boolean;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;)Lio/sentry/log4j2/SentryAppender;
protected fun createBreadcrumb (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/Breadcrumb;
protected fun createEvent (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/SentryEvent;
public fun getMinimumBreadcrumbLevel ()Lorg/apache/logging/log4j/Level;
public fun getMinimumEventLevel ()Lorg/apache/logging/log4j/Level;
public fun getMinimumLevel ()Lorg/apache/logging/log4j/Level;
public fun start ()V
}

Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ public void start() {
start(getOptionsConfiguration(null));
}

public @NotNull Level getMinimumBreadcrumbLevel() {
return minimumBreadcrumbLevel;
}

public @NotNull Level getMinimumEventLevel() {
return minimumEventLevel;
}

public @NotNull Level getMinimumLevel() {
return minimumLevel;
}

@NotNull
Sentry.OptionsConfiguration<SentryOptions> getOptionsConfiguration(
final @Nullable Sentry.OptionsConfiguration<SentryOptions> additionalOptionsConfiguration) {
Expand Down
122 changes: 122 additions & 0 deletions sentry-samples/sentry-samples-spring-boot-4-log4j2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Sentry Sample Spring Boot 4 with Log4j2

Sample application showing how to use Sentry with [Spring Boot 4](https://spring.io/projects/spring-boot) and [Log4j2](https://logging.apache.org/log4j/2.x/).

## How to run?

To see events triggered in this sample application in your Sentry dashboard, go to `src/main/resources/application.properties` and replace the test DSN with your own DSN.

Then, execute a command from the module directory:

```
../../gradlew bootRun
```

Make an HTTP request that will trigger events:

```
curl -XPOST --user user:password http://localhost:8080/person/ -H "Content-Type:application/json" -d '{"firstName":"John","lastName":"Smith"}'
```

## GraphQL

The following queries can be used to test the GraphQL integration.

### Greeting
```
{
greeting(name: "crash")
}
```

### Greeting with variables

```
query GreetingQuery($name: String) {
greeting(name: $name)
}
```
variables:
```
{
"name": "crash"
}
```

### Project

```
query ProjectQuery($slug: ID!) {
project(slug: $slug) {
slug
name
repositoryUrl
status
}
}
```
variables:
```
{
"slug": "statuscrash"
}
```

### Mutation

```
mutation AddProjectMutation($slug: ID!) {
addProject(slug: $slug)
}
```
variables:
```
{
"slug": "nocrash",
"name": "nocrash"
}
```

### Subscription

```
subscription SubscriptionNotifyNewTask($slug: ID!) {
notifyNewTask(projectSlug: $slug) {
id
name
assigneeId
assignee {
id
name
}
}
}
```
variables:
```
{
"slug": "crash"
}
```

### Data loader

```
query TasksAndAssigneesQuery($slug: ID!) {
tasks(projectSlug: $slug) {
id
name
assigneeId
assignee {
id
name
}
}
}
```
variables:
```
{
"slug": "crash"
}
```
113 changes: 113 additions & 0 deletions sentry-samples/sentry-samples-spring-boot-4-log4j2/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
alias(libs.plugins.springboot4)
alias(libs.plugins.spring.dependency.management)
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.spring)
id("io.sentry.systemtest")
}

group = "io.sentry.sample.spring-boot-4-log4j2"

version = "0.0.1-SNAPSHOT"

java.sourceCompatibility = JavaVersion.VERSION_17

java.targetCompatibility = JavaVersion.VERSION_17

configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

tasks.withType<KotlinCompile>().configureEach {
kotlin {
explicitApi()
// skip metadata version check, as Spring 7 / Spring Boot 4 is
// compiled against a newer version of Kotlin
compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict", "-Xskip-metadata-version-check")
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}
}

dependencies {
implementation(libs.springboot4.starter)
implementation(libs.springboot4.starter.actuator)
implementation(libs.springboot4.starter.aspectj)
implementation(libs.springboot4.starter.graphql)
implementation(libs.springboot4.starter.jdbc)
implementation(libs.springboot4.starter.quartz)
implementation(libs.springboot4.starter.security)
implementation(libs.springboot4.starter.web)
implementation(libs.springboot4.starter.webflux)
implementation(libs.springboot4.starter.websocket)
implementation(libs.springboot4.starter.restclient)
implementation(libs.springboot4.starter.webclient)
implementation(Config.Libs.aspectj)
implementation(Config.Libs.kotlinReflect)
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
implementation(projects.sentrySpringBoot4Starter)
implementation(projects.sentryGraphql22)
implementation(projects.sentryQuartz)
implementation(projects.sentryAsyncProfiler)

implementation(projects.sentryLog4j2)
implementation("org.springframework.boot:spring-boot-starter-log4j2")
modules {
module("org.springframework.boot:spring-boot-starter-logging") {
replacedBy(
"org.springframework.boot:spring-boot-starter-log4j2",
"Use Log4j2 instead of Logback",
)
}
}

// cache tracing
implementation(libs.springboot4.starter.cache)
implementation(libs.caffeine)

// kafka
implementation(libs.springboot4.starter.kafka)
implementation(projects.sentryKafka)

// database query tracing
implementation(projects.sentryJdbc)
runtimeOnly(libs.hsqldb)

testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(projects.sentry)
testImplementation(projects.sentrySystemTestSupport)
testImplementation(libs.apollo3.kotlin)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.slf4j2.api)
testImplementation(libs.springboot4.starter.test) {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
}

tasks.register<Test>("systemTest").configure {
group = "verification"
description = "Runs the System tests"

val test = project.extensions.getByType<SourceSetContainer>()["test"]
testClassesDirs = test.output.classesDirs
classpath = test.runtimeClasspath

maxParallelForks = 1

// Cap JVM args per test
minHeapSize = "128m"
maxHeapSize = "1g"

filter { includeTestsMatching("io.sentry.systemtest*") }
}

tasks.named("test").configure {
require(this is Test)

filter { excludeTestsMatching("io.sentry.systemtest.*") }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.sentry.samples.spring.boot4;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cache/")
public class CacheController {
private final TodoService todoService;

public CacheController(TodoService todoService) {
this.todoService = todoService;
}

@GetMapping("{id}")
Todo get(@PathVariable Long id) {
return todoService.get(id);
}

@PostMapping
Todo save(@RequestBody Todo todo) {
return todoService.save(todo);
}

@DeleteMapping("{id}")
void delete(@PathVariable Long id) {
todoService.delete(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.sentry.samples.spring.boot4;

import io.sentry.EventProcessor;
import io.sentry.Hint;
import io.sentry.SentryEvent;
import io.sentry.protocol.SentryRuntime;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.SpringBootVersion;
import org.springframework.stereotype.Component;

/**
* Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are
* sent to Sentry.
*/
@Component
public class CustomEventProcessor implements EventProcessor {
private final String springBootVersion;

public CustomEventProcessor(String springBootVersion) {
this.springBootVersion = springBootVersion;
}

public CustomEventProcessor() {
this(SpringBootVersion.getVersion());
}

@Override
public @NotNull SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
final SentryRuntime runtime = new SentryRuntime();
runtime.setVersion(springBootVersion);
runtime.setName("Spring Boot");
event.getContexts().setRuntime(runtime);
return event;
}
}
Loading
Loading