-
Notifications
You must be signed in to change notification settings - Fork 78
Allow multiple LogStorage with primary and secondaries #417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory.java
Outdated
Show resolved
Hide resolved
...in/java/org/jenkinsci/plugins/workflow/configuration/PipelineLoggingGlobalConfiguration.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/FileLogStorageFactory.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/LogStorage.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/LogStorageFactory.java
Outdated
Show resolved
Hide resolved
...in/java/org/jenkinsci/plugins/workflow/configuration/PipelineLoggingGlobalConfiguration.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeBuildListener.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeBuildListener.java
Outdated
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageTestBase.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/LogStorage.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeBuildListener.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeBuildListener.java
Outdated
Show resolved
Hide resolved
src/main/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/config.jelly
Outdated
Show resolved
Hide resolved
269b2ee
to
bd88176
Compare
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeOutputStream.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
@Override | ||
public void close() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to note: While I was looking at the code yesterday I realized the logic here is a bit confusing. We close the delegate TaskListener
s, but we don't actually close the TeeOutputStream
or the PrintStream
returned by getLogger()
. At first I thought maybe we could simplify by just calling getLogger().close()
, which would eventually close the TeeOutputStream
and we could get rid of the rest of the method, but at least BufferedBuildListener
and CloudWatchSender
have special close
methods that do more than just closing the internal streams, so we really do need to try to close the TaskListener
s themselves.
This means that after a call to close()
, getLogger()
and outputStream
will still exist as unclosed streams even after their delegates have been been closed, which is a bit weird. We might be able to call getLogger().close()
and then close the listeners, but I can't remember if any of the relevant streams have problems with being closed twice. I would maybe look into this a bit just to make sure whether the TeeOutputStream
not being closed is ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I update with something like:
Exception exception = null;
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
exception = e;
}
}
then TeeLogStorageTest#smokes
fails with:
org.opentest4j.MultipleFailuresError: Multiple Failures (2 failures)
org.junit.ComparisonFailure: expected:<[<a href='http://nowhere.net/'>nikde</a>
]> but was:<[]>
java.lang.AssertionError:
Expected: is "starting\none #1\ntwo #1\ntwo #2\ninterrupting\none #2\none #3\npausing\nresuming\none #4\nthree #1\nending\n"
but: was "starting\none #1\ntwo #1\ntwo #2\ninterrupting\none #2\none #3\npausing\nresuming\none #4\nthree #1\nending\nha:////4AM+Xbq6l0DXt+Aa5ectJ4m2Ny0f1G1cNAXESjnHxoh7AAAAlB+LCAAAAAAAAP9b85aBtbiIQSajNKU4P08vOT+vOD8nVc+jsiC1KCczL9svvyTVzHb1RttJBUeZGJg8GdhyUvPSSzJ8GJhLi3JKGIR8shLLEvVzEvPS9YNLijLz0q0rihik0IxzhtAgwxgggJGJgaGiAMhgLWEQzigpKbDS18/LL89ILUrVy0st0QcAFd2f8JgAAAA=nikde\n"
...
Suppressed: org.junit.ComparisonFailure: expected:<[<a href='http://nowhere.net/'>nikde</a>
]> but was:<[]>
at org.jenkinsci.plugins.workflow.log.LogStorageTestBase.assertLog(LogStorageTestBase.java:324)
at org.jenkinsci.plugins.workflow.log.LogStorageTestBase.assertStepLog(LogStorageTestBase.java:305)
at org.jenkinsci.plugins.workflow.log.LogStorageTestBase.smokes(LogStorageTestBase.java:155)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you specifically need to use getLogger().close()
, not outputStream.close
, maybe so that the PrintStream
flushes its buffer and the stream, but I am not exactly sure why the former works but the latter does not without investigating more deeply. If things are ok without closing the PrintStream
or TeeOutputStream
itself that might be fine too, I would just check to make sure everything is getting GC'd as expected. Maybe there could be a difference in behavior if you tested writing lines without newlines or something like that, but I do not know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But now with the additional getLogger().close()
, the logger is flushed and closed, making TeeOutputStreamTest#primary_fails_close
fail, and the close() method is called twice:
Breakpoint reached
at org.jenkinsci.plugins.workflow.log.tee.TeeOutputStreamTest$4.close(TeeOutputStreamTest.java:118)
at org.jenkinsci.plugins.workflow.log.tee.RemoteCustomFileLogStorage$Writer.close(RemoteCustomFileLogStorage.java:138)
at org.jenkinsci.plugins.workflow.log.tee.TeeOutputStream$$Lambda/0x000000d80168a838.apply(Unknown Source:-1)
at org.jenkinsci.plugins.workflow.log.tee.TeeOutputStream.handleAction(TeeOutputStream.java:45)
at org.jenkinsci.plugins.workflow.log.tee.TeeOutputStream.close(TeeOutputStream.java:34)
at java.io.PrintStream.implClose(PrintStream.java:500)
at java.io.PrintStream.close(PrintStream.java:484)
at org.jenkinsci.plugins.workflow.log.tee.TeeBuildListener.close(TeeBuildListener.java:51)
...
Breakpoint reached
at org.jenkinsci.plugins.workflow.log.tee.TeeOutputStreamTest$4.close(TeeOutputStreamTest.java:118)
at org.jenkinsci.plugins.workflow.log.tee.RemoteCustomFileLogStorage$Writer.close(RemoteCustomFileLogStorage.java:138)
at org.jenkinsci.plugins.workflow.log.tee.RemoteCustomFileLogStorage$MyListener.close(RemoteCustomFileLogStorage.java:99)
at org.jenkinsci.plugins.workflow.log.tee.TeeBuildListener.close(TeeBuildListener.java:55)
at org.jenkinsci.plugins.workflow.log.tee.TeeOutputStreamTest.primary_fails_close(TeeOutputStreamTest.java:123)
at java.lang.invoke.LambdaForm$DMH/0x000000d801218c00.invokeVirtual(LambdaForm$DMH:-1)
at java.lang.invoke.LambdaForm$MH/0x000000d801324000.invoke(LambdaForm$MH:-1)
at java.lang.invoke.Invokers$Holder.invokeExact_MT(Invokers$Holder:-1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sorry, I was not saying that we definitely need it, only that if we do need it, we need to close the full PrintStream
, not just the OutputStream
, and yes it means the stream will be closed twice as I mentioned in #417 (comment). It's just not clear to me whether it is preferable to leave the outer streams open and just let them be cleaned up by GC after the delegate TaskListener
s are closed, or to close the PrintStream
, causing the TeeOutputStreamTest
stream to be closed twice. I will try to investigate in more detail later today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so now we close the stream twice, but everything seems to be ok IIUC. @jgreffe did you investigate to see if you prefer the behavior with the current code or how you had things prior to adding line 51?
src/main/java/org/jenkinsci/plugins/workflow/log/LogStorageFactory.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory.java
Show resolved
Hide resolved
...jenkinsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfiguration/config.jelly
Show resolved
Hide resolved
2b775e6
to
da22a49
Compare
src/test/java/org/jenkinsci/plugins/workflow/log/tee/TeeOutputStreamTest.java
Outdated
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/workflow/log/tee/RemoteCustomFileLogStorage.java
Outdated
Show resolved
Hide resolved
...jenkinsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfiguration/config.jelly
Outdated
Show resolved
Hide resolved
...nsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfiguration/config.properties
Outdated
Show resolved
Hide resolved
...main/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/config.properties
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some suggestions for the strings, but probably didn't catch all instances. Overall, I think the use of "Tee" and "factories" is a bit confusing, so I added some possible alternatives. I'll be OOTO until August 11th, so I added several options in most cases, depending on the route we may take.
...main/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/config.properties
Outdated
Show resolved
Hide resolved
...main/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/config.properties
Outdated
Show resolved
Hide resolved
...jenkinsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfiguration/config.jelly
Outdated
Show resolved
Hide resolved
...jenkinsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfiguration/config.jelly
Outdated
Show resolved
Hide resolved
...nsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfiguration/config.properties
Outdated
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/workflow/log/tee/RemoteCustomFileLogStorageFactory.java
Outdated
Show resolved
Hide resolved
src/main/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/config.jelly
Outdated
Show resolved
Hide resolved
...enkinsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfigurationJCasCTest.java
Outdated
Show resolved
Hide resolved
...main/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/config.properties
Outdated
Show resolved
Hide resolved
...jenkinsci/plugins/workflow/log/configuration/PipelineLoggingGlobalConfiguration/config.jelly
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/FileLogStorageFactory.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
@Override | ||
public void close() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so now we close the stream twice, but everything seems to be ok IIUC. @jgreffe did you investigate to see if you prefer the behavior with the current code or how you had things prior to adding line 51?
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory.java
Outdated
Show resolved
Hide resolved
3a985fc
to
b707a36
Compare
...in/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/help-secondary.html
Outdated
Show resolved
Hide resolved
...main/resources/org/jenkinsci/plugins/workflow/log/tee/TeeLogStorageFactory/help-primary.html
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/LogStorageFactoryDescriptor.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/LogStorageFactoryDescriptor.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/LogStorageFactoryDescriptor.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/log/tee/TeeBuildListener.java
Outdated
Show resolved
Hide resolved
Co-authored-by: Devin Nusbaum <[email protected]>
…istener.java Co-authored-by: Devin Nusbaum <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few more minor string suggestions. If there are any that I missed, please let me know.
@@ -0,0 +1,3 @@ | |||
<p> | |||
This logger will be used to read and write Pipeline build logs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logger will be used to read and write Pipeline build logs. | |
The primary logger is used to read and write Pipeline build logs. |
@@ -0,0 +1,3 @@ | |||
<p> | |||
Pipeline build logs will be duplicated as they are written and sent to this logger. This logger will never be used to read logs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pipeline build logs will be duplicated as they are written and sent to this logger. This logger will never be used to read logs. | |
If a secondary logger is selected, Pipeline build logs are duplicated as they are written and sent to this logger. This logger is never used to read logs. |
@@ -0,0 +1 @@ | |||
description=Specify the multiple loggers order. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
description=Specify the multiple loggers order. | |
description=Specify the primary and secondary Pipeline logger. |
@NonNull | ||
@Override | ||
public String getDisplayName() { | ||
return "A Log Storage Factory Mock"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return "A Log Storage Factory Mock"; | |
return "A mock Pipeline logger"; |
@NonNull | ||
@Override | ||
public String getDisplayName() { | ||
return "Another Log Storage Factory Mock 2"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return "Another Log Storage Factory Mock 2"; | |
return "Another mock Pipeline logger"; |
See jenkinsci/pipeline-cloudwatch-logs-plugin#132 and jenkinsci/workflow-durable-task-step-plugin#464 for downstream changes. This PR is a follow-up to #406 (comment).
This PR adjusts the
LogStorageFactory
API for two main purposes:LogStorageFactory
in case they have multiple plugins installed that implementLogStorageFactory
TeeLogStorageFactory
, which allows users to configure a "primary"LogStorageFactory
that handles reads and writes, and a "secondary" factory that only handle copies of all writesTee
logic internally (as done by the existingopentelemetry
plugin)LogStorage
implementations that simply want to copy log content elsewhere without reading it back into Jenkins, as considered in Security-2401 Fix credentials leakage to Splunk splunk-devops-plugin#17Specifically, this PR converts
LogStorageFactory
from anExtensionPoint
to aDescribable
, and introducesLogStorageFactoryDescriptor
. It also introduces aGlobalConfiguration
for Pipeline logging and a newTeeLogStorageFactory
logger implementation.Monosnap.screencast.2025-07-18.15-01-23.mp4
This change is not backwards compatible. All plugins that implement
LogStorageFactory
will need to be updated and released shortly after this PR is released to avoid compatibility issues.Testing done
See new and existing automated tests here and in downstream PRs. This PR has also been tested manually.
Submitter checklist