Part 5: Trace Observers¶
Trace observers let your plugin respond to workflow events, such as a task completing, a file being published, or the pipeline finishing. This enables use cases like custom reports, Slack notifications, metrics collection, or integration with external monitoring systems. In this section, you'll build an observer that counts completed tasks and prints a summary.
Starting from here?
If you're joining at this part, copy the solution from Part 4 to use as your starting point:
1. Understanding the existing trace observer¶
The "Pipeline is starting!" message when you ran the pipeline came from the GreetingObserver class in your plugin.
Look at the observer code:
/*
* Copyright 2025, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package training.plugin
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import nextflow.Session
import nextflow.trace.TraceObserver
/**
* Implements an observer that allows implementing custom
* logic on nextflow execution events.
*/
@Slf4j
@CompileStatic
class GreetingObserver implements TraceObserver { // (1)!
@Override
void onFlowCreate(Session session) { // (2)!
println "Pipeline is starting! 🚀"
}
@Override
void onFlowComplete() { // (3)!
println "Pipeline complete! 👋"
}
}
- Interface for hooking into workflow lifecycle events
- Called when the workflow starts; receives the session for accessing config
- Called when the workflow finishes successfully
There are two things to notice here:
class GreetingObserver implements TraceObserver:TraceObserveris an interface defined by Nextflow. If your class implements this interface, Nextflow can hook into it and call your methods when events happen.@Override: TheTraceObserverinterface defines methods likeonFlowCreateandonFlowComplete. When you write methods with these names and add the@Overrideannotation, Nextflow calls them at the appropriate time. Any methods you don't override are ignored.
The full set of lifecycle events you can hook into at the time of writing are:
| Method | When it's called |
|---|---|
onFlowCreate |
Workflow starts |
onFlowComplete |
Workflow finishes |
onProcessStart |
A task begins execution |
onProcessComplete |
A task finishes |
onProcessCached |
A cached task is reused |
onFilePublish |
A file is published |
For a complete list, see the TraceObserver interface in the Nextflow source.
2. Add a task counter observer¶
The goal is to build an observer that counts completed tasks and prints a summary at the end. Adding a new observer to a plugin requires two things: writing the observer class, and registering it in the factory so Nextflow loads it.
2.1. Create a minimal observer¶
Create a new file:
Start with the simplest possible observer that prints a message when any task completes:
- Import the required classes:
TraceObserver,TaskHandler, andTraceRecord - Create a class that
implements TraceObserver - Override
onProcessCompleteto run code when a task finishes
This is the minimum needed:
- Import the required classes (
TraceObserver,TaskHandler,TraceRecord) - Create a class that
implements TraceObserver - Override
onProcessCompleteto do something when a task finishes
2.2. Register the observer¶
The GreetingFactory creates observers.
Take a look at it:
/*
* Copyright 2025, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package training.plugin
import groovy.transform.CompileStatic
import nextflow.Session
import nextflow.trace.TraceObserver
import nextflow.trace.TraceObserverFactory
@CompileStatic
class GreetingFactory implements TraceObserverFactory {
@Override
Collection<TraceObserver> create(Session session) {
return List.<TraceObserver>of(new GreetingObserver())
}
}
Edit GreetingFactory.groovy to add the new observer:
Groovy list syntax
We've replaced the Java-style List.<TraceObserver>of(...) with Groovy's simpler list literal [...].
Both return a Collection, but the Groovy syntax is more readable when adding multiple items.
2.3. Build, install, and test¶
Why -ansi-log false?
By default, Nextflow's ANSI progress display overwrites previous lines to show a clean, updating view of progress. This means you'd only see the final task count, not the intermediate messages.
Using -ansi-log false disables this behavior and shows all output sequentially, which is essential when testing observers that print messages during execution.
You should see "✓ Task completed!" printed five times (once per task), interleaved with the existing pipeline output:
...
[9b/df7630] Submitted process > SAY_HELLO (4)
Decorated: *** Hello ***
✓ Task completed!
✓ Task completed!
Decorated: *** Holà ***
✓ Task completed!
...
Pipeline complete! 👋
The observer is working.
Each time a task finishes, Nextflow calls onProcessComplete, and our implementation prints a message.
Customize the message
Try changing the message in onProcessComplete to something of your own, rebuild, and rerun.
This confirms the full edit-build-run cycle works for observers.
2.4. Add counting logic¶
The minimal observer proves the hook works, but it doesn't track anything.
A class can hold variables (called fields or instance variables) that persist for the lifetime of the object. This means an observer can accumulate state across multiple events during a pipeline run.
The next version adds a counter variable (taskCount) that starts at zero.
Each time a task completes, the counter goes up by one.
When the entire workflow finishes, the observer prints the final total.
Update TaskCounterObserver.groovy with the highlighted changes:
taskCountis a variable that belongs to the observer object. It keeps its value between method calls, so it can accumulate a count across the whole workflow run.privatemeans only this class can access it.taskCount++adds one to the counter. This line runs every time a task completes, so the count grows as the workflow progresses.onFlowCompleteis a second lifecycle hook. It runs once when the workflow finishes, making it a good place to print a summary.
In summary:
taskCountpersists across method calls, accumulating a count over the whole runonProcessCompleteincrements the counter and prints the running total each time a task finishesonFlowCompleteruns once at the end, printing the final count
Rebuild and test:
Output
N E X T F L O W ~ version 25.10.2
Launching `greet.nf` [pensive_engelbart] DSL2 - revision: 85fefd90d0
Pipeline is starting! 🚀
Reversed: olleH
Reversed: ruojnoB
Reversed: àloH
Reversed: oaiC
Reversed: ollaH
[be/bd8e72] Submitted process > SAY_HELLO (2)
[5b/d24c2b] Submitted process > SAY_HELLO (1)
[14/1f9dbe] Submitted process > SAY_HELLO (3)
Decorated: *** Bonjour ***
Decorated: *** Hello ***
[85/a6b3ad] Submitted process > SAY_HELLO (4)
📊 Tasks completed so far: 1
📊 Tasks completed so far: 2
Decorated: *** Holà ***
📊 Tasks completed so far: 3
Decorated: *** Ciao ***
[3c/be6686] Submitted process > SAY_HELLO (5)
📊 Tasks completed so far: 4
Decorated: *** Hallo ***
📊 Tasks completed so far: 5
Pipeline complete! 👋
📈 Final task count: 5
The counter messages are interleaved with task submissions because observers run as tasks complete.
3. Track published files¶
The observer can also respond when files are published.
The onFilePublish method receives the destination and source paths, which you can use to log, validate, or process published outputs.
3.1. Add a publish directory¶
First, update greet.nf so the SAY_HELLO process publishes its output files:
3.2. Add the onFilePublish method¶
Add an onFilePublish method and the required import to TaskCounterObserver.groovy:
3.3. Build and test¶
You should see "Published:" messages for each output file alongside the task counter output:
...
📊 Tasks completed so far: 1
📁 Published: greeting.txt
📊 Tasks completed so far: 2
📁 Published: greeting.txt
...
📈 Final task count: 5
Pipeline complete! 👋
The onFilePublish method fires each time Nextflow publishes a file to the results directory.
This pattern is useful for building audit logs, triggering downstream actions, or validating outputs as they are produced.
Takeaway¶
You learned that:
- Trace observers hook into workflow lifecycle events like
onFlowCreate,onProcessComplete,onFilePublish, andonFlowComplete - Create observers by implementing
TraceObserverand registering them in a Factory - Observers can hold instance variables to accumulate state across events
- Observers are useful for custom logging, metrics collection, notifications, and reporting
What's next?¶
The task counter works, but it's always on.
In a real plugin, users should be able to enable or disable features, or adjust behavior, from nextflow.config without editing the plugin source code.
The next section shows how to make your observer configurable and how to share your finished plugin with others.