Skip to content

Commit b1714e6

Browse files
committed
Add initial support for observability with JFR
This commit introduces new JFR events to monitor and trace job and step executions. The most important design decision is to have a single event for each action, instead of two separate events (like ChunkTransactionEvent instead of ChunkTransactionStartEvent and ChunkTransactionStopEvent). This design has two main advantages: - Ability to time events and have a non-zero duration - Avoid the need to correlate events on the consumer side Further improvements ideas: - Create an observability template to time actions - Use AOP for observability aspects Resolves #4972
1 parent 683cff5 commit b1714e6

File tree

18 files changed

+557
-5
lines changed

18 files changed

+557
-5
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.batch.core.job.parameters.JobParametersValidator;
3838
import org.springframework.batch.core.listener.JobExecutionListener;
3939
import org.springframework.batch.core.SpringBatchVersion;
40+
import org.springframework.batch.core.observability.jfr.events.job.JobExecutionEvent;
4041
import org.springframework.batch.core.step.Step;
4142
import org.springframework.batch.core.step.StepExecution;
4243
import org.springframework.batch.core.launch.NoSuchJobException;
@@ -277,6 +278,9 @@ public final void execute(JobExecution execution) {
277278
}
278279

279280
JobSynchronizationManager.register(execution);
281+
JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(execution.getJobInstance().getJobName(),
282+
execution.getJobInstance().getId(), execution.getId());
283+
jobExecutionEvent.begin();
280284
String activeJobMeterName = "job.active";
281285
LongTaskTimer longTaskTimer = BatchMetrics.createLongTaskTimer(this.meterRegistry, activeJobMeterName,
282286
"Active jobs", Tag.of(BatchMetrics.METRICS_PREFIX + activeJobMeterName + ".name",
@@ -349,6 +353,8 @@ public final void execute(JobExecution execution) {
349353
execution.setExitStatus(exitStatus.and(newExitStatus));
350354
}
351355
stopObservation(execution, observation);
356+
jobExecutionEvent.exitStatus = execution.getExitStatus().getExitCode();
357+
jobExecutionEvent.commit();
352358
longTaskTimerSample.stop();
353359
execution.setEndTime(LocalDateTime.now());
354360

@@ -364,6 +370,7 @@ public final void execute(JobExecution execution) {
364370
finally {
365371
JobSynchronizationManager.release();
366372
}
373+
System.out.println("execution = " + execution);
367374

368375
}
369376

spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.batch.core.launch.JobOperator;
2828
import org.springframework.batch.core.launch.NoSuchJobException;
2929
import org.springframework.batch.core.launch.NoSuchJobExecutionException;
30+
import org.springframework.batch.core.observability.jfr.events.job.JobLaunchEvent;
3031
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
3132
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
3233
import org.springframework.batch.core.repository.JobRepository;
@@ -92,6 +93,7 @@ public JobExecution start(Job job, JobParameters jobParameters)
9293
JobRestartException, JobParametersInvalidException {
9394
Assert.notNull(job, "Job must not be null");
9495
Assert.notNull(jobParameters, "JobParameters must not be null");
96+
new JobLaunchEvent(job.getName(), jobParameters.toString()).commit();
9597
return super.start(job, jobParameters);
9698
}
9799

spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2023 the original author or authors.
2+
* Copyright 2019-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,6 +55,10 @@ public final class BatchMetrics {
5555

5656
public static final String STATUS_FAILURE = "FAILURE";
5757

58+
public static final String STATUS_COMMITTED = "COMMITTED";
59+
60+
public static final String STATUS_ROLLED_BACK = "ROLLED_BACK";
61+
5862
private BatchMetrics() {
5963
}
6064

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.observability.jfr.events.job;
17+
18+
import jdk.jfr.Category;
19+
import jdk.jfr.Description;
20+
import jdk.jfr.Event;
21+
import jdk.jfr.Label;
22+
23+
@Label("Job Execution")
24+
@Description("Job Execution Event")
25+
@Category({ "Spring Batch", "Job" })
26+
public class JobExecutionEvent extends Event {
27+
28+
@Label("Job Name")
29+
public String jobName;
30+
31+
@Label("Job Instance Id")
32+
public long jobInstanceId;
33+
34+
@Label("Job Execution Id")
35+
public long jobExecutionId;
36+
37+
@Label("Job Exit Status")
38+
public String exitStatus;
39+
40+
public JobExecutionEvent(String jobName, long jobInstanceId, long jobExecutionId) {
41+
this.jobName = jobName;
42+
this.jobInstanceId = jobInstanceId;
43+
this.jobExecutionId = jobExecutionId;
44+
}
45+
46+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.observability.jfr.events.job;
17+
18+
import jdk.jfr.Category;
19+
import jdk.jfr.Description;
20+
import jdk.jfr.Event;
21+
import jdk.jfr.Label;
22+
23+
@Label("Job Launch Request")
24+
@Description("Job Launch Request Event")
25+
@Category({ "Spring Batch", "Job" })
26+
public class JobLaunchEvent extends Event {
27+
28+
@Label("Job Name")
29+
public String jobName;
30+
31+
@Label("Job Parameters")
32+
public String jobParameters;
33+
34+
public JobLaunchEvent(String jobName, String jobParameters) {
35+
this.jobParameters = jobParameters;
36+
this.jobName = jobName;
37+
}
38+
39+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.observability.jfr.events.step;
17+
18+
import jdk.jfr.Category;
19+
import jdk.jfr.Description;
20+
import jdk.jfr.Event;
21+
import jdk.jfr.Label;
22+
23+
@Label("Step Execution")
24+
@Description("Step Execution Event")
25+
@Category({ "Spring Batch", "Step" })
26+
public class StepExecutionEvent extends Event {
27+
28+
@Label("Step Name")
29+
public String stepName;
30+
31+
@Label("Job Name")
32+
public String jobName;
33+
34+
@Label("Step Execution Id")
35+
public long stepExecutionId;
36+
37+
@Label("Job Execution Id")
38+
public long jobExecutionId;
39+
40+
@Label("Step Exit Status")
41+
public String exitStatus;
42+
43+
public StepExecutionEvent(String stepName, String jobName, long stepExecutionId, long jobExecutionId) {
44+
this.stepName = stepName;
45+
this.jobName = jobName;
46+
this.stepExecutionId = stepExecutionId;
47+
this.jobExecutionId = jobExecutionId;
48+
}
49+
50+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.observability.jfr.events.step.chunk;
17+
18+
import jdk.jfr.Category;
19+
import jdk.jfr.Description;
20+
import jdk.jfr.Event;
21+
import jdk.jfr.Label;
22+
23+
@Label("Chunk Scan")
24+
@Description("Chunk Scan Event")
25+
@Category({ "Spring Batch", "Step", "Chunk" })
26+
public class ChunkScanEvent extends Event {
27+
28+
@Label("Step Name")
29+
public String stepName;
30+
31+
@Label("Step Execution Id")
32+
public long stepExecutionId;
33+
34+
@Label("Skip Count")
35+
public long skipCount;
36+
37+
public ChunkScanEvent(String stepName, long stepExecutionId) {
38+
this.stepName = stepName;
39+
this.stepExecutionId = stepExecutionId;
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.observability.jfr.events.step.chunk;
17+
18+
import jdk.jfr.Category;
19+
import jdk.jfr.Description;
20+
import jdk.jfr.Event;
21+
import jdk.jfr.Label;
22+
23+
@Label("Chunk Transaction")
24+
@Description("Chunk Transaction Event")
25+
@Category({ "Spring Batch", "Step", "Chunk" })
26+
public class ChunkTransactionEvent extends Event {
27+
28+
@Label("Step Name")
29+
public String stepName;
30+
31+
@Label("Step Execution Id")
32+
public long stepExecutionId;
33+
34+
@Label("Transaction Status")
35+
public String transactionStatus;
36+
37+
public ChunkTransactionEvent(String stepName, long stepExecutionId) {
38+
this.stepName = stepName;
39+
this.stepExecutionId = stepExecutionId;
40+
}
41+
42+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.observability.jfr.events.step.chunk;
17+
18+
import jdk.jfr.Category;
19+
import jdk.jfr.Description;
20+
import jdk.jfr.Event;
21+
import jdk.jfr.Label;
22+
23+
@Label("Chunk Write")
24+
@Description("Chunk Write Event")
25+
@Category({ "Spring Batch", "Step", "Chunk" })
26+
public class ChunkWriteEvent extends Event {
27+
28+
@Label("Step Name")
29+
public String stepName;
30+
31+
@Label("Step Execution Id")
32+
public long stepExecutionId;
33+
34+
@Label("Chunk Write Status")
35+
public String chunkWriteStatus;
36+
37+
@Label("Item Count")
38+
public long itemCount;
39+
40+
public ChunkWriteEvent(String stepName, long stepExecutionId, long itemCount) {
41+
this.itemCount = itemCount;
42+
this.stepName = stepName;
43+
this.stepExecutionId = stepExecutionId;
44+
}
45+
46+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.observability.jfr.events.step.chunk;
17+
18+
import jdk.jfr.Category;
19+
import jdk.jfr.Description;
20+
import jdk.jfr.Event;
21+
import jdk.jfr.Label;
22+
23+
@Label("Item Process")
24+
@Description("Item Process Event")
25+
@Category({ "Spring Batch", "Step", "Chunk" })
26+
public class ItemProcessEvent extends Event {
27+
28+
@Label("Step Name")
29+
public String stepName;
30+
31+
@Label("Step Execution Id")
32+
public long stepExecutionId;
33+
34+
@Label("Item Process Status")
35+
public String itemProcessStatus;
36+
37+
public ItemProcessEvent(String stepName, long stepExecutionId) {
38+
this.stepName = stepName;
39+
this.stepExecutionId = stepExecutionId;
40+
}
41+
42+
}

0 commit comments

Comments
 (0)