Skip to content

Commit edd3bc4

Browse files
edmundmillerbentshermanpditommaso
committed
Unify log observers into single logObserver field, auto-disable ANSI in agent mode
- Combine ansiLogObserver/agentLogObserver into single LogObserver interface + logObserver field on Session (only one active at a time) - Auto-disable ANSI log in CliOptions when agent mode detected, so task hashes and submission lines flow through ConsoleAppender - Both AnsiLogObserver and AgentLogObserver implement LogObserver - LoggerHelper uses instanceof dispatch for ANSI-specific features (appendSticky, started/stopped checks) Signed-off-by: Edmund Miller <edmund.miller@seqera.io> Co-authored-by: Ben Sherman <bentshermann@gmail.com> Co-authored-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
1 parent 928a16d commit edd3bc4

File tree

7 files changed

+79
-38
lines changed

7 files changed

+79
-38
lines changed

modules/nextflow/src/main/groovy/nextflow/Session.groovy

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import nextflow.script.WorkflowMetadata
7070
import nextflow.script.dsl.ProcessConfigBuilder
7171
import nextflow.spack.SpackConfig
7272
import nextflow.trace.AgentLogObserver
73-
import nextflow.trace.AnsiLogObserver
73+
import nextflow.trace.LogObserver
7474
import nextflow.trace.TraceObserver
7575
import nextflow.trace.TraceObserverFactory
7676
import nextflow.trace.TraceObserverFactoryV2
@@ -317,9 +317,7 @@ class Session implements ISession {
317317

318318
boolean disableJobsCancellation
319319

320-
AnsiLogObserver ansiLogObserver
321-
322-
AgentLogObserver agentLogObserver
320+
LogObserver logObserver
323321

324322
FilePorter getFilePorter() { filePorter }
325323

@@ -506,9 +504,10 @@ class Session implements ISession {
506504
result.add(statsObserver)
507505
// Add AgentLogObserver when agent mode is enabled
508506
if( agentLog ) {
509-
this.agentLogObserver = new AgentLogObserver()
510-
agentLogObserver.setStatsObserver(statsObserver)
511-
result.add(agentLogObserver)
507+
def observer = new AgentLogObserver()
508+
observer.setStatsObserver(statsObserver)
509+
this.logObserver = observer
510+
result.add(observer)
512511
}
513512
for( TraceObserverFactoryV2 f : Plugins.getExtensions(TraceObserverFactoryV2) ) {
514513
log.debug "Observer factory (v2): ${f.class.simpleName}"
@@ -844,8 +843,7 @@ class Session implements ISession {
844843
shutdown0()
845844
notifyError(null)
846845
// force termination
847-
ansiLogObserver?.forceTermination()
848-
agentLogObserver?.forceTermination()
846+
logObserver?.forceTermination()
849847
executorFactory?.signalExecutors()
850848
processesBarrier.forceTermination()
851849
monitorsBarrier.forceTermination()
@@ -1295,16 +1293,16 @@ class Session implements ISession {
12951293
}
12961294

12971295
void printConsole(String str, boolean newLine=false) {
1298-
if( ansiLogObserver )
1299-
ansiLogObserver.appendInfo(str)
1296+
if( logObserver )
1297+
logObserver.appendInfo(str)
13001298
else if( newLine )
13011299
System.out.println(str)
13021300
else
13031301
System.out.print(str)
13041302
}
13051303

13061304
void printConsole(Path file) {
1307-
ansiLogObserver ? ansiLogObserver.appendInfo(file.text) : Files.copy(file, System.out)
1305+
logObserver ? logObserver.appendInfo(file.text) : Files.copy(file, System.out)
13081306
}
13091307

13101308
private volatile ThreadPoolManager finalizePoolManager

modules/nextflow/src/main/groovy/nextflow/cli/CliOptions.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ class CliOptions {
118118
if( noColor ) {
119119
return ansiLog = false
120120
}
121+
122+
// Disable ANSI log in agent mode for plain, parseable output
123+
if( SysEnv.isAgentMode() ) {
124+
return ansiLog = false
125+
}
126+
121127
return Ansi.isEnabled()
122128
}
123129

modules/nextflow/src/main/groovy/nextflow/trace/AgentLogObserver.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import nextflow.trace.event.TaskEvent
3939
* @author Edmund Miller <edmund.miller@utdallas.edu>
4040
*/
4141
@Slf4j
42-
class AgentLogObserver implements TraceObserverV2 {
42+
class AgentLogObserver implements TraceObserverV2, LogObserver {
4343

4444
private Session session
4545
private WorkflowStatsObserver statsObserver

modules/nextflow/src/main/groovy/nextflow/trace/AnsiLogObserver.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import org.fusesource.jansi.AnsiConsole
3939
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
4040
*/
4141
@CompileStatic
42-
class AnsiLogObserver implements TraceObserverV2 {
42+
class AnsiLogObserver implements TraceObserverV2, LogObserver {
4343

4444
static final private String NEWLINE = '\n'
4545

modules/nextflow/src/main/groovy/nextflow/trace/DefaultObserverFactory.groovy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ class DefaultObserverFactory implements TraceObserverFactoryV2 {
4848

4949
protected void createAnsiLogObserver(Collection<TraceObserverV2> result) {
5050
if( session.ansiLog ) {
51-
session.ansiLogObserver = new AnsiLogObserver()
52-
result << session.ansiLogObserver
51+
def observer = new AnsiLogObserver()
52+
session.logObserver = observer
53+
result << observer
5354
}
5455
}
5556

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2013-2024, Seqera Labs
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+
* http://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+
17+
package nextflow.trace
18+
19+
/**
20+
* Common interface for log observer implementations (ANSI and Agent modes).
21+
*
22+
* Only one log observer is active at a time. This interface defines the
23+
* shared contract used by {@link nextflow.Session} and
24+
* {@link nextflow.util.LoggerHelper}.
25+
*
26+
* @author Edmund Miller <edmund.miller@utdallas.edu>
27+
*/
28+
interface LogObserver {
29+
30+
void appendInfo(String message)
31+
32+
void appendWarning(String message)
33+
34+
void appendError(String message)
35+
36+
void forceTermination()
37+
}

modules/nextflow/src/main/groovy/nextflow/util/LoggerHelper.groovy

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import groovyx.gpars.dataflow.DataflowReadChannel
5656
import groovyx.gpars.dataflow.DataflowWriteChannel
5757
import nextflow.Global
5858
import nextflow.Session
59+
import nextflow.trace.AnsiLogObserver
5960
import nextflow.cli.CliOptions
6061
import nextflow.cli.Launcher
6162
import nextflow.exception.AbortOperationException
@@ -714,33 +715,31 @@ class LoggerHelper {
714715
try {
715716
final message = fmtEvent(event, session, false)
716717

717-
// Forward to AgentLogObserver when agent mode is enabled
718-
final agentRenderer = session?.agentLogObserver
719-
if( agentRenderer ) {
718+
final observer = session?.logObserver
719+
if( observer instanceof AnsiLogObserver ) {
720+
final renderer = (AnsiLogObserver)observer
721+
if( !renderer.started || renderer.stopped )
722+
System.out.println(message)
723+
else if( event.marker == STICKY )
724+
renderer.appendSticky(message)
725+
else if( event.level==Level.ERROR )
726+
renderer.appendError(message)
727+
else if( event.level==Level.WARN )
728+
renderer.appendWarning(message)
729+
else
730+
renderer.appendInfo(message)
731+
}
732+
else if( observer ) {
720733
if( event.level==Level.ERROR )
721-
agentRenderer.appendError(message)
734+
observer.appendError(message)
722735
else if( event.level==Level.WARN )
723-
agentRenderer.appendWarning(message)
736+
observer.appendWarning(message)
724737
else
725-
agentRenderer.appendInfo(message)
726-
return
738+
observer.appendInfo(message)
727739
}
728-
729-
final renderer = session?.ansiLogObserver
730-
if( !renderer || !renderer.started || renderer.stopped )
740+
else {
731741
System.out.println(message)
732-
733-
else if( event.marker == STICKY )
734-
renderer.appendSticky(message)
735-
736-
else if( event.level==Level.ERROR )
737-
renderer.appendError(message)
738-
739-
else if( event.level==Level.WARN )
740-
renderer.appendWarning(message)
741-
742-
else
743-
renderer.appendInfo(message)
742+
}
744743
}
745744
catch (Throwable e) {
746745
e.printStackTrace()

0 commit comments

Comments
 (0)