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 @@ -46,6 +46,9 @@ jobs:
- sample: "sentry-samples-spring-boot-webflux"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-spring-boot-jakarta-log4j2"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-spring-boot-jakarta-opentelemetry-noagent"
agent: "false"
agent-auto-init: "true"
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
```properties
sentry.logs.enabled=true
```
- Sentry can now configure Log4j2 automatically for Spring Boot 3 when `sentry-log4j2` is on the classpath and Log4j2 Core is the active logging backend ([#6072](https://github.com/getsentry/sentry-java/pull/6072))
- Disabled by default for now; enable it and configure levels the same way as described in the Spring Boot 4 entry above (`sentry.logging.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 @@ -66,6 +66,7 @@ apiValidation {
"sentry-samples-spring-boot-opentelemetry",
"sentry-samples-spring-boot-opentelemetry-noagent",
"sentry-samples-spring-boot-jakarta",
"sentry-samples-spring-boot-jakarta-log4j2",
"sentry-samples-spring-boot-jakarta-opentelemetry",
"sentry-samples-spring-boot-jakarta-opentelemetry-noagent",
"sentry-samples-spring-boot-webflux",
Expand Down
122 changes: 122 additions & 0 deletions sentry-samples/sentry-samples-spring-boot-jakarta-log4j2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Sentry Sample Spring Boot 3.0+ with Log4j2

Sample application showing how to use Sentry with [Spring Boot](https://spring.io/projects/spring-boot) from version `3.0` onwards 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"
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
alias(libs.plugins.springboot3)
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-jakarta-log4j2"

version = "0.0.1-SNAPSHOT"

java.sourceCompatibility = JavaVersion.VERSION_17

java.targetCompatibility = JavaVersion.VERSION_17

dependencyManagement {
imports {
mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}")
}
}

// Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version
extra["kotlin-coroutines.version"] = "1.9.0"

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

tasks.withType<KotlinCompile>().configureEach { compilerOptions.jvmTarget = JvmTarget.JVM_17 }

tasks.withType<KotlinCompile>().configureEach {
kotlin {
compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict")
compilerOptions.jvmTarget = JvmTarget.JVM_17
}
}

dependencies {
implementation(libs.springboot3.starter)
implementation(libs.springboot3.starter.actuator)
implementation(libs.springboot3.starter.aop)
implementation(libs.springboot3.starter.graphql)
implementation(libs.springboot3.starter.jdbc)
implementation(libs.springboot3.starter.quartz)
implementation(libs.springboot3.starter.security)
implementation(libs.springboot3.starter.web)
implementation(libs.springboot3.starter.webflux)
implementation(libs.springboot3.starter.websocket)
implementation(Config.Libs.aspectj)
implementation(Config.Libs.kotlinReflect)
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
implementation(projects.sentrySpringBootStarterJakarta)
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",
)
}
}
implementation(projects.sentryGraphql22)
implementation(projects.sentryQuartz)
implementation(projects.sentryAsyncProfiler)
implementation(projects.sentryOpenfeature)

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

// kafka
implementation(libs.spring.kafka3)
implementation(projects.sentryKafka)

// OpenFeature SDK
implementation(libs.openfeature)

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

testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(projects.sentry)
testImplementation(projects.sentrySystemTestSupport)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.slf4j2.api)
testImplementation(libs.springboot3.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.boot.jakarta;

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.boot.jakarta;

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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.sentry.samples.spring.boot.jakarta;

import io.sentry.spring.jakarta.checkin.SentryCheckIn;
import io.sentry.spring.jakarta.tracing.SentryTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
* {@link SentryTransaction} added on the class level, creates transaction around each method
* execution of every method of the annotated class.
*/
@Component
@SentryTransaction(operation = "scheduled")
public class CustomJob {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomJob.class);

@SentryCheckIn("monitor_slug_1")
// @Scheduled(fixedRate = 3 * 60 * 1000L)
void execute() throws InterruptedException {
LOGGER.info("Executing scheduled job");
Thread.sleep(2000L);
}
}
Loading
Loading