6

If I have a Pipeline script method in Pipeline script (Jenkinsfile), my Global Pipeline Library's vars/ or in a src/ class, how can obtain the OutputStream for the console log? I want to write directly to the console log.

I know I can echo or println, but for this purpose I need to write without the extra output that yields. I also need to be able to pass the OutputStream to something else.

I know I can call TaskListener.getLogger() if I can get the TaskListener (really hudson.util.StreamTaskListener) instance, but how?

I tried:

  • I've looked into manager.listener.logger (from the groovy postbuild plugin) and in the early-build context I'm calling from it doesn't yield an OutputStream that writes to the job's Console Log.

    echo "listener is a ${manager.listener} - ${manager.listener.getClass().getName()} from ${manager} and has a ${manager.listener.logger} of class ${manager.listener.logger.getClass().getName()}"
    

    prints

    listener is a hudson.util.LogTaskListener@420c55c4 - hudson.util.LogTaskListener from org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder$BadgeManager@58ac0c55 and has a java.io.PrintStream@715b9f99 of class java.io.PrintStream
    
  • I know you can get it from a StepContext via context.get(TaskListener.class) but I'm not in a Step, I'm in a CpsScript (i.e. WorkflowScript i.e. Jenkinsfile).

  • Finding it from a CpsFlowExecution obtained from the DSL instance registered as the steps script-property, but I couldn't work out how to discover the TaskListener that's passed to it when it's created

How is it this hard? What am I missing? There's so much indirect magic I find it incredibly hard to navigate the system.

BTW, I'm aware direct access is blocked by Script Security, but I can create @Whitelisted methods, and anything in a global library's vars/ is always whitelisted anyway.

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
  • "I also need to be able to pass the OutputStream to something else" .. if you manage to do that, please explain how to do it. – Mzzl May 30 '19 at 02:12

2 Answers2

5

You can access the build object from the Jenkins root object:

def listener = Jenkins.get()
    .getItemByFullName(env.JOB_NAME)
    .getBuildByNumber(Integer.parseInt(env.BUILD_NUMBER))
    .getListener()

def logger = listener.getLogger() as PrintStream

logger.println("Listener: ${listener} Logger: ${logger}")

Result:

Listener: CloseableTaskListener[org.jenkinsci.plugins.workflow.log.BufferedBuildListener@6e9e6a16 / org.jenkinsci.plugins.workflow.log.BufferedBuildListener@6e9e6a16] Logger: java.io.PrintStream@423efc01
Mzzl
  • 3,926
  • 28
  • 39
3

After banging my head against this problem for a couple days I think I have a solution:

CpsThreadGroup.current().execution.owner.listener

It's ugly, and I don't know if it's correct or if there's a better way, but seems to work.

James Emerton
  • 4,129
  • 2
  • 25
  • 33
  • That's better than any hack I came up with, so thanks very much for the tip. Glad I'm not the only one intensely frustrated with this particular issue. I landed up giving up on my efforts to build a console filter that groups arbitrary ranges of console output into HTM5 `......` blocks via `withContext`: it became clear I'd have to write my own ConsoleAnnotation subclass and package in an extension, build it, etc, I could just bundle it as an @Extension in the global library because the code reading the console can't see that. It got too hard. – Craig Ringer Nov 21 '18 at 02:54
  • If I get time I want to return to it, since the current regexp-based console folding plugin does truly horrifying things to console log size and is not convenient for pipeline use. – Craig Ringer Nov 21 '18 at 02:57
  • I stumbled across another solution (untested) that seems to expose `StepContext.get()` to pipeline scripts: https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#withcontext-use-contextual-object-from-internal-apis-within-a-block – James Emerton Nov 21 '18 at 20:57
  • That tiny bullet point "context" .... ARGH. That does look promising, I'll look into it. – Craig Ringer Nov 22 '18 at 06:47
  • Jenkins 2.190.1. I get "No such property: CpsThreadGroup for class: groovy.lang.Binding". Should I upgrade some plugins? The above solution from @Mzzl is working for me. – Alexander Samoylov Oct 14 '19 at 13:23