Skip to content

Commit 6f39eb9

Browse files
authored
Merge pull request #17 from jglick/logs-JENKINS-38381
[JEP-210] Log handling rewrite
2 parents f5a22b6 + 999cef3 commit 6f39eb9

File tree

12 files changed

+1150
-3
lines changed

12 files changed

+1150
-3
lines changed

.mvn/extensions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
<extension>
33
<groupId>io.jenkins.tools.incrementals</groupId>
44
<artifactId>git-changelist-maven-extension</artifactId>
5-
<version>1.0-beta-3</version>
5+
<version>1.0-beta-4</version>
66
</extension>
77
</extensions>

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<parent>
2929
<groupId>org.jenkins-ci.plugins</groupId>
3030
<artifactId>plugin</artifactId>
31-
<version>3.14</version>
31+
<version>3.21</version>
3232
<relativePath />
3333
</parent>
3434
<groupId>org.jenkins-ci.plugins.workflow</groupId>

src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionOwner.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import javax.annotation.CheckForNull;
3636
import javax.annotation.Nonnull;
3737
import jenkins.model.TransientActionFactory;
38+
import org.jenkinsci.plugins.workflow.log.LogStorage;
3839
import org.jenkinsci.plugins.workflow.steps.StepContext;
3940

4041
/**
@@ -121,9 +122,15 @@ public String getUrlOfExecution() throws IOException {
121122
/**
122123
* Gets a listener to which we may print general messages.
123124
* Normally {@link StepContext#get} should be used, but in some cases there is no associated step.
125+
* <p>The listener should be remotable: if sent to an agent, messages printed to it should still appear in the log.
126+
* The same will then apply to calls to {@link StepContext#get} on {@link TaskListener}.
124127
*/
125128
public @Nonnull TaskListener getListener() throws IOException {
126-
return TaskListener.NULL;
129+
try {
130+
return LogStorage.of(this).overallListener();
131+
} catch (InterruptedException x) {
132+
throw new IOException(x);
133+
}
127134
}
128135

129136
/**
@@ -167,6 +174,9 @@ private static class DummyOwner extends FlowExecutionOwner {
167174
@Override public int hashCode() {
168175
return 0;
169176
}
177+
@Override public TaskListener getListener() throws IOException {
178+
return TaskListener.NULL;
179+
}
170180
}
171181

172182
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2018 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package org.jenkinsci.plugins.workflow.log;
26+
27+
import hudson.Functions;
28+
import hudson.console.AnnotatedLargeText;
29+
import hudson.model.BuildListener;
30+
import hudson.model.TaskListener;
31+
import java.io.IOException;
32+
import java.nio.charset.StandardCharsets;
33+
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
34+
import org.jenkinsci.plugins.workflow.graph.FlowNode;
35+
import org.kohsuke.accmod.Restricted;
36+
import org.kohsuke.accmod.restrictions.Beta;
37+
import org.kohsuke.stapler.framework.io.ByteBuffer;
38+
39+
/**
40+
* Placeholder for storage broken by some kind of access error.
41+
*/
42+
@Restricted(Beta.class)
43+
public final class BrokenLogStorage implements LogStorage {
44+
45+
private final Throwable x;
46+
47+
public BrokenLogStorage(Throwable x) {
48+
this.x = x;
49+
}
50+
51+
@Override public BuildListener overallListener() throws IOException, InterruptedException {
52+
throw new IOException(x);
53+
}
54+
55+
@Override public TaskListener nodeListener(FlowNode node) throws IOException, InterruptedException {
56+
throw new IOException(x);
57+
}
58+
59+
@Override public AnnotatedLargeText<FlowExecutionOwner.Executable> overallLog(FlowExecutionOwner.Executable build, boolean complete) {
60+
return new BrokenAnnotatedLargeText<>();
61+
}
62+
63+
@Override public AnnotatedLargeText<FlowNode> stepLog(FlowNode node, boolean complete) {
64+
return new BrokenAnnotatedLargeText<>();
65+
}
66+
67+
private class BrokenAnnotatedLargeText<T> extends AnnotatedLargeText<T> {
68+
69+
BrokenAnnotatedLargeText() {
70+
super(makeByteBuffer(), StandardCharsets.UTF_8, true, null);
71+
}
72+
73+
}
74+
75+
private ByteBuffer makeByteBuffer() {
76+
ByteBuffer buf = new ByteBuffer();
77+
byte[] stack = Functions.printThrowable(x).getBytes(StandardCharsets.UTF_8);
78+
try {
79+
buf.write(stack, 0, stack.length);
80+
} catch (IOException x2) {
81+
assert false : x2;
82+
}
83+
return buf;
84+
}
85+
86+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2018 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package org.jenkinsci.plugins.workflow.log;
26+
27+
import com.jcraft.jzlib.GZIPInputStream;
28+
import com.jcraft.jzlib.GZIPOutputStream;
29+
import com.trilead.ssh2.crypto.Base64;
30+
import hudson.console.AnnotatedLargeText;
31+
import hudson.console.ConsoleAnnotationOutputStream;
32+
import hudson.console.ConsoleAnnotator;
33+
import hudson.remoting.ClassFilter;
34+
import hudson.remoting.ObjectInputStreamEx;
35+
import java.io.ByteArrayInputStream;
36+
import java.io.IOException;
37+
import java.io.ObjectInputStream;
38+
import java.io.ObjectOutputStream;
39+
import static java.lang.Math.abs;
40+
import java.util.concurrent.TimeUnit;
41+
import javax.crypto.Cipher;
42+
import javax.crypto.CipherInputStream;
43+
import javax.crypto.CipherOutputStream;
44+
import jenkins.model.Jenkins;
45+
import jenkins.security.CryptoConfidentialKey;
46+
import org.apache.commons.io.output.ByteArrayOutputStream;
47+
import org.kohsuke.accmod.Restricted;
48+
import org.kohsuke.accmod.restrictions.Beta;
49+
import org.kohsuke.stapler.Stapler;
50+
import org.kohsuke.stapler.StaplerRequest;
51+
import org.kohsuke.stapler.StaplerResponse;
52+
53+
/**
54+
* Some utility code extracted from {@link AnnotatedLargeText} which probably belongs in {@link ConsoleAnnotator} or {@link ConsoleAnnotationOutputStream}.
55+
*/
56+
@Restricted(Beta.class)
57+
public class ConsoleAnnotators {
58+
59+
private static final CryptoConfidentialKey PASSING_ANNOTATOR = new CryptoConfidentialKey(ConsoleAnnotators.class, "consoleAnnotator");
60+
61+
/**
62+
* What to pass to {@link ConsoleAnnotationOutputStream#ConsoleAnnotationOutputStream} when overriding {@link AnnotatedLargeText#writeHtmlTo}.
63+
*/
64+
public static <T> ConsoleAnnotator<T> createAnnotator(T context) throws IOException {
65+
StaplerRequest req = Stapler.getCurrentRequest();
66+
try {
67+
String base64 = req != null ? req.getHeader("X-ConsoleAnnotator") : null;
68+
if (base64 != null) {
69+
@SuppressWarnings("deprecation") // TODO still used in the AnnotatedLargeText version
70+
Cipher sym = PASSING_ANNOTATOR.decrypt();
71+
try (ObjectInputStream ois = new ObjectInputStreamEx(new GZIPInputStream(
72+
new CipherInputStream(new ByteArrayInputStream(Base64.decode(base64.toCharArray())), sym)),
73+
Jenkins.get().pluginManager.uberClassLoader,
74+
ClassFilter.DEFAULT)) {
75+
long timestamp = ois.readLong();
76+
if (TimeUnit.HOURS.toMillis(1) > abs(System.currentTimeMillis() - timestamp)) {
77+
@SuppressWarnings("unchecked") ConsoleAnnotator<T> annotator = (ConsoleAnnotator) ois.readObject();
78+
return annotator;
79+
}
80+
}
81+
}
82+
} catch (ClassNotFoundException e) {
83+
throw new IOException(e);
84+
}
85+
return ConsoleAnnotator.initial(context);
86+
}
87+
88+
/**
89+
* What to call at the end of an override of {@link AnnotatedLargeText#writeHtmlTo}.
90+
*/
91+
public static void setAnnotator(ConsoleAnnotator<?> annotator) throws IOException {
92+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
93+
@SuppressWarnings("deprecation") // TODO still used in the AnnotatedLargeText version
94+
Cipher sym = PASSING_ANNOTATOR.encrypt();
95+
try (ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(new CipherOutputStream(baos, sym)))) {
96+
oos.writeLong(System.currentTimeMillis());
97+
oos.writeObject(annotator);
98+
}
99+
StaplerResponse rsp = Stapler.getCurrentResponse();
100+
if (rsp != null) {
101+
rsp.setHeader("X-ConsoleAnnotator", new String(Base64.encode(baos.toByteArray())));
102+
}
103+
}
104+
105+
private ConsoleAnnotators() {}
106+
107+
}

0 commit comments

Comments
 (0)