Skip to content

Commit 744ff64

Browse files
lbloderadinauer
andauthored
log4j spring boot autoinstrumentation (#5403)
Co-authored-by: Alexander Dinauer <adinauer@users.noreply.github.com>
1 parent 7bf5574 commit 744ff64

98 files changed

Lines changed: 5335 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/system-tests-backend.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ jobs:
4646
- sample: "sentry-samples-spring-boot-webflux"
4747
agent: "false"
4848
agent-auto-init: "true"
49+
- sample: "sentry-samples-spring-boot-jakarta-log4j2"
50+
agent: "false"
51+
agent-auto-init: "true"
4952
- sample: "sentry-samples-spring-boot-jakarta-opentelemetry-noagent"
5053
agent: "false"
5154
agent-auto-init: "true"
@@ -73,6 +76,9 @@ jobs:
7376
- sample: "sentry-samples-spring-boot-4"
7477
agent: "false"
7578
agent-auto-init: "true"
79+
- sample: "sentry-samples-spring-boot-4-log4j2"
80+
agent: "false"
81+
agent-auto-init: "true"
7682
- sample: "sentry-samples-spring-boot-4-webflux"
7783
agent: "false"
7884
agent-auto-init: "true"

‎CHANGELOG.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- 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))
8+
- Enable automatic appender registration with:
9+
```properties
10+
sentry.logging.enabled=true
11+
```
12+
Automatic registration is disabled by default for now and will be enabled by default in the next major release.
13+
- The appender is attached to the root logger by default. To attach it to specific loggers instead, configure one or more non-overlapping logger names:
14+
```properties
15+
sentry.logging.loggers[0]=com.example
16+
sentry.logging.loggers[1]=org.example
17+
```
18+
- Configure the minimum level for creating breadcrumbs. The default is `INFO`:
19+
```properties
20+
sentry.logging.minimum-breadcrumb-level=INFO
21+
```
22+
- Configure the minimum level for creating Sentry error events. The default is `ERROR`:
23+
```properties
24+
sentry.logging.minimum-event-level=ERROR
25+
```
26+
- Configure the minimum level for sending Sentry structured logs. The default is `INFO`:
27+
```properties
28+
sentry.logging.minimum-level=INFO
29+
```
30+
Structured logs must also be enabled:
31+
```properties
32+
sentry.logs.enabled=true
33+
```
34+
- 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))
35+
- 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`)
36+
337
## 8.56.0
438

539
### Behavioral Changes

‎build.gradle.kts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,13 @@ apiValidation {
6666
"sentry-samples-spring-boot-opentelemetry",
6767
"sentry-samples-spring-boot-opentelemetry-noagent",
6868
"sentry-samples-spring-boot-jakarta",
69+
"sentry-samples-spring-boot-jakarta-log4j2",
6970
"sentry-samples-spring-boot-jakarta-opentelemetry",
7071
"sentry-samples-spring-boot-jakarta-opentelemetry-noagent",
7172
"sentry-samples-spring-boot-webflux",
7273
"sentry-samples-spring-boot-webflux-jakarta",
7374
"sentry-samples-spring-boot-4",
75+
"sentry-samples-spring-boot-4-log4j2",
7476
"sentry-samples-spring-boot-4-opentelemetry",
7577
"sentry-samples-spring-boot-4-opentelemetry-noagent",
7678
"sentry-samples-spring-boot-4-otlp",

‎sentry-log4j2/api/sentry-log4j2.api‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public class io/sentry/log4j2/SentryAppender : org/apache/logging/log4j/core/app
1212
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;
1313
protected fun createBreadcrumb (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/Breadcrumb;
1414
protected fun createEvent (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/SentryEvent;
15+
public fun getMinimumBreadcrumbLevel ()Lorg/apache/logging/log4j/Level;
16+
public fun getMinimumEventLevel ()Lorg/apache/logging/log4j/Level;
17+
public fun getMinimumLevel ()Lorg/apache/logging/log4j/Level;
1518
public fun start ()V
1619
}
1720

‎sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,18 @@ public void start() {
166166
start(getOptionsConfiguration(null));
167167
}
168168

169+
public @NotNull Level getMinimumBreadcrumbLevel() {
170+
return minimumBreadcrumbLevel;
171+
}
172+
173+
public @NotNull Level getMinimumEventLevel() {
174+
return minimumEventLevel;
175+
}
176+
177+
public @NotNull Level getMinimumLevel() {
178+
return minimumLevel;
179+
}
180+
169181
@NotNull
170182
Sentry.OptionsConfiguration<SentryOptions> getOptionsConfiguration(
171183
final @Nullable Sentry.OptionsConfiguration<SentryOptions> additionalOptionsConfiguration) {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Sentry Sample Spring Boot 4 with Log4j2
2+
3+
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/).
4+
5+
## How to run?
6+
7+
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.
8+
9+
Then, execute a command from the module directory:
10+
11+
```
12+
../../gradlew bootRun
13+
```
14+
15+
Make an HTTP request that will trigger events:
16+
17+
```
18+
curl -XPOST --user user:password http://localhost:8080/person/ -H "Content-Type:application/json" -d '{"firstName":"John","lastName":"Smith"}'
19+
```
20+
21+
## GraphQL
22+
23+
The following queries can be used to test the GraphQL integration.
24+
25+
### Greeting
26+
```
27+
{
28+
greeting(name: "crash")
29+
}
30+
```
31+
32+
### Greeting with variables
33+
34+
```
35+
query GreetingQuery($name: String) {
36+
greeting(name: $name)
37+
}
38+
```
39+
variables:
40+
```
41+
{
42+
"name": "crash"
43+
}
44+
```
45+
46+
### Project
47+
48+
```
49+
query ProjectQuery($slug: ID!) {
50+
project(slug: $slug) {
51+
slug
52+
name
53+
repositoryUrl
54+
status
55+
}
56+
}
57+
```
58+
variables:
59+
```
60+
{
61+
"slug": "statuscrash"
62+
}
63+
```
64+
65+
### Mutation
66+
67+
```
68+
mutation AddProjectMutation($slug: ID!) {
69+
addProject(slug: $slug)
70+
}
71+
```
72+
variables:
73+
```
74+
{
75+
"slug": "nocrash",
76+
"name": "nocrash"
77+
}
78+
```
79+
80+
### Subscription
81+
82+
```
83+
subscription SubscriptionNotifyNewTask($slug: ID!) {
84+
notifyNewTask(projectSlug: $slug) {
85+
id
86+
name
87+
assigneeId
88+
assignee {
89+
id
90+
name
91+
}
92+
}
93+
}
94+
```
95+
variables:
96+
```
97+
{
98+
"slug": "crash"
99+
}
100+
```
101+
102+
### Data loader
103+
104+
```
105+
query TasksAndAssigneesQuery($slug: ID!) {
106+
tasks(projectSlug: $slug) {
107+
id
108+
name
109+
assigneeId
110+
assignee {
111+
id
112+
name
113+
}
114+
}
115+
}
116+
```
117+
variables:
118+
```
119+
{
120+
"slug": "crash"
121+
}
122+
```
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import org.jetbrains.kotlin.config.KotlinCompilerVersion
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
4+
plugins {
5+
alias(libs.plugins.springboot4)
6+
alias(libs.plugins.spring.dependency.management)
7+
alias(libs.plugins.kotlin.jvm)
8+
alias(libs.plugins.kotlin.spring)
9+
id("io.sentry.systemtest")
10+
}
11+
12+
group = "io.sentry.sample.spring-boot-4-log4j2"
13+
14+
version = "0.0.1-SNAPSHOT"
15+
16+
java.sourceCompatibility = JavaVersion.VERSION_17
17+
18+
java.targetCompatibility = JavaVersion.VERSION_17
19+
20+
configure<JavaPluginExtension> {
21+
sourceCompatibility = JavaVersion.VERSION_17
22+
targetCompatibility = JavaVersion.VERSION_17
23+
}
24+
25+
tasks.withType<KotlinCompile>().configureEach {
26+
kotlin {
27+
explicitApi()
28+
// skip metadata version check, as Spring 7 / Spring Boot 4 is
29+
// compiled against a newer version of Kotlin
30+
compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict", "-Xskip-metadata-version-check")
31+
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
32+
compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
33+
compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
34+
}
35+
}
36+
37+
dependencies {
38+
implementation(libs.springboot4.starter)
39+
implementation(libs.springboot4.starter.actuator)
40+
implementation(libs.springboot4.starter.aspectj)
41+
implementation(libs.springboot4.starter.graphql)
42+
implementation(libs.springboot4.starter.jdbc)
43+
implementation(libs.springboot4.starter.quartz)
44+
implementation(libs.springboot4.starter.security)
45+
implementation(libs.springboot4.starter.web)
46+
implementation(libs.springboot4.starter.webflux)
47+
implementation(libs.springboot4.starter.websocket)
48+
implementation(libs.springboot4.starter.restclient)
49+
implementation(libs.springboot4.starter.webclient)
50+
implementation(Config.Libs.aspectj)
51+
implementation(Config.Libs.kotlinReflect)
52+
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
53+
implementation(projects.sentrySpringBoot4Starter)
54+
implementation(projects.sentryGraphql22)
55+
implementation(projects.sentryQuartz)
56+
implementation(projects.sentryAsyncProfiler)
57+
58+
implementation(projects.sentryLog4j2)
59+
implementation("org.springframework.boot:spring-boot-starter-log4j2")
60+
modules {
61+
module("org.springframework.boot:spring-boot-starter-logging") {
62+
replacedBy(
63+
"org.springframework.boot:spring-boot-starter-log4j2",
64+
"Use Log4j2 instead of Logback",
65+
)
66+
}
67+
}
68+
69+
// cache tracing
70+
implementation(libs.springboot4.starter.cache)
71+
implementation(libs.caffeine)
72+
73+
// kafka
74+
implementation(libs.springboot4.starter.kafka)
75+
implementation(projects.sentryKafka)
76+
77+
// database query tracing
78+
implementation(projects.sentryJdbc)
79+
runtimeOnly(libs.hsqldb)
80+
81+
testImplementation(kotlin(Config.kotlinStdLib))
82+
testImplementation(projects.sentry)
83+
testImplementation(projects.sentrySystemTestSupport)
84+
testImplementation(libs.apollo3.kotlin)
85+
testImplementation(libs.kotlin.test.junit)
86+
testImplementation(libs.slf4j2.api)
87+
testImplementation(libs.springboot4.starter.test) {
88+
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
89+
}
90+
}
91+
92+
tasks.register<Test>("systemTest").configure {
93+
group = "verification"
94+
description = "Runs the System tests"
95+
96+
val test = project.extensions.getByType<SourceSetContainer>()["test"]
97+
testClassesDirs = test.output.classesDirs
98+
classpath = test.runtimeClasspath
99+
100+
maxParallelForks = 1
101+
102+
// Cap JVM args per test
103+
minHeapSize = "128m"
104+
maxHeapSize = "1g"
105+
106+
filter { includeTestsMatching("io.sentry.systemtest*") }
107+
}
108+
109+
tasks.named("test").configure {
110+
require(this is Test)
111+
112+
filter { excludeTestsMatching("io.sentry.systemtest.*") }
113+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.sentry.samples.spring.boot4;
2+
3+
import org.springframework.web.bind.annotation.DeleteMapping;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.PathVariable;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestBody;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@RestController
12+
@RequestMapping("/cache/")
13+
public class CacheController {
14+
private final TodoService todoService;
15+
16+
public CacheController(TodoService todoService) {
17+
this.todoService = todoService;
18+
}
19+
20+
@GetMapping("{id}")
21+
Todo get(@PathVariable Long id) {
22+
return todoService.get(id);
23+
}
24+
25+
@PostMapping
26+
Todo save(@RequestBody Todo todo) {
27+
return todoService.save(todo);
28+
}
29+
30+
@DeleteMapping("{id}")
31+
void delete(@PathVariable Long id) {
32+
todoService.delete(id);
33+
}
34+
}

0 commit comments

Comments
 (0)