diff --git a/pom.xml b/pom.xml
index 1f76f7c..e870c9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,9 @@
1.0-alpha-1
-SNAPSHOT
- 2.135-rc15088.42aa6febbbed
+
+ 2.137-20180806.095555-1
+ 2.137-20180806.095623-1
8
true
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/Event.java b/src/main/java/io/jenkins/plugins/extlogging/api/Event.java
index c6e8aac..9ba1c5d 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/Event.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/Event.java
@@ -15,6 +15,7 @@ public class Event {
final long timestamp;
final long id;
+ //TODO:Consider so that Key-Value storages could be quickly implemented
Map data = new HashMap<>();
public Event(long id, String message, long timestamp) {
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowser.java b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowser.java
index c09e3dc..bece115 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowser.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowser.java
@@ -1,18 +1,121 @@
package io.jenkins.plugins.extlogging.api;
-import jenkins.model.logging.LogBrowser;
+import hudson.console.AnnotatedLargeText;
+import hudson.console.ConsoleNote;
+import hudson.model.Run;
import jenkins.model.logging.Loggable;
+import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.util.LinkedList;
+import java.util.List;
/**
* Base abstract class for External Log Browsers.
+ * This implementation also implements a convenience method for caching of files.
* @author Oleg Nenashev
* @since TODO
*/
-public abstract class ExternalLogBrowser extends LogBrowser {
+public abstract class ExternalLogBrowser extends LoggableHandler {
public ExternalLogBrowser(@Nonnull Loggable loggable) {
super(loggable);
}
+
+ //TODO: Push warnings to Telemetry API
+ //Pipeline: LOGGER.log(Level.WARNING, "Avoid calling getLogFile on " + this,
+ // new UnsupportedOperationException());
+ /**
+ * Gets log as a file.
+ * This is a compatibility method, which is used in {@link Run#getLogFile()}.
+ * {@link ExternalLogBrowser} implementations may provide it, e.g. by creating temporary files if needed.
+ * @return Log file. If it does not exist, {@link IOException} should be thrown
+ * @throws IOException Log file cannot be retrieved
+ * @deprecated The method is available for compatibility purposes only
+ */
+ public File getLogFile() throws IOException {
+ File f = File.createTempFile("deprecated", ".log", getOwner().getTmpDir());
+ f.deleteOnExit();
+ try (OutputStream os = new FileOutputStream(f)) {
+ overallLog().writeRawLogTo(0, os);
+ }
+ return f;
+ }
+
+ /**
+ * Gets log for an object.
+ * @return Created log or {@link jenkins.model.logging.impl.BrokenAnnotatedLargeText} if it cannot be retrieved
+ */
+ @Nonnull
+ public abstract AnnotatedLargeText overallLog();
+
+ //TODO: jglick requests justification of why it needs to be in the core
+ /**
+ * Gets log for a part of the object.
+ * @param stepId Identifier of the step to be displayed.
+ * It may be Pipeline step or other similar abstraction
+ * @param completed indicates that the step is completed
+ * @return Created log or {@link jenkins.model.logging.impl.BrokenAnnotatedLargeText} if it cannot be retrieved
+ */
+ @Nonnull
+ public abstract AnnotatedLargeText stepLog(@CheckForNull String stepId, boolean completed);
+
+ public InputStream getLogInputStream() throws IOException {
+ // Inefficient but probably rarely used anyway.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ overallLog().writeRawLogTo(0, baos);
+ return new ByteArrayInputStream(baos.toByteArray());
+ }
+
+ public Reader getLogReader() throws IOException {
+ // As above.
+ return overallLog().readAll();
+ }
+
+ @SuppressWarnings("deprecation")
+ public String getLog() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ overallLog().writeRawLogTo(0, baos);
+ return baos.toString(loggable.getCharset().name());
+ }
+
+ public List getLog(int maxLines) throws IOException {
+ int lineCount = 0;
+ List logLines = new LinkedList<>();
+ if (maxLines == 0) {
+ return logLines;
+ }
+ try (BufferedReader reader = new BufferedReader(getLogReader())) {
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ logLines.add(line);
+ ++lineCount;
+ if (lineCount > maxLines) {
+ logLines.remove(0);
+ }
+ }
+ }
+ if (lineCount > maxLines) {
+ logLines.set(0, "[...truncated " + (lineCount - (maxLines - 1)) + " lines...]");
+ }
+ return ConsoleNote.removeNotes(logLines);
+ }
+
+ /**
+ * Deletes the log in the storage.
+ * @return {@code true} if the log was deleted.
+ * {@code false} if Log deletion is not supported.
+ * @throws IOException Failed to delete the log.
+ */
+ public abstract boolean deleteLog() throws IOException;
+
+
}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowserFactory.java b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowserFactory.java
index 6ded090..f93fb06 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowserFactory.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLogBrowserFactory.java
@@ -4,12 +4,12 @@
import hudson.model.Describable;
import hudson.model.Descriptor;
import jenkins.model.Jenkins;
-import jenkins.model.logging.LogBrowser;
import jenkins.model.logging.Loggable;
import javax.annotation.CheckForNull;
/**
+ * Produces {@link ExternalLogBrowser}s.
* @author Oleg Nenashev
* @since TODO
*/
@@ -17,7 +17,7 @@ public abstract class ExternalLogBrowserFactory
implements Describable, ExtensionPoint {
@CheckForNull
- public abstract LogBrowser create(Loggable loggable);
+ public abstract ExternalLogBrowser create(Loggable loggable);
@Override
public Descriptor getDescriptor() {
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingEventWriter.java b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingEventWriter.java
index 307baa3..3f2d815 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingEventWriter.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingEventWriter.java
@@ -1,12 +1,21 @@
package io.jenkins.plugins.extlogging.api;
+import io.jenkins.plugins.extlogging.api.impl.ExternalLoggingOutputStream;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
import java.io.IOException;
+import java.io.PrintStream;
import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
+//TODO: jglick is concerned about the event-based logic, maybe we should add a lower-level implementation
+//TODO: jglick: "Why do we need events?" in JEP
/**
* Implements logging of events
* @author Oleg Nenashev
@@ -14,9 +23,17 @@
*/
public abstract class ExternalLoggingEventWriter extends Writer implements Serializable {
+ String charset;
Map metadata = new HashMap<>();
AtomicLong messageCounter = new AtomicLong();
+ @CheckForNull
+ private transient PrintStream printStream;
+
+ public ExternalLoggingEventWriter(@Nonnull Charset charset) {
+ this.charset = charset.name();
+ }
+
public abstract void writeEvent(Event event) throws IOException;
public void writeMessage(String message) throws IOException {
@@ -25,6 +42,7 @@ public void writeMessage(String message) throws IOException {
writeEvent(event);
}
+ //TODO(oleg-nenashev): jglick requests example of complex event
public void addMetadataEntry(String key, Serializable value) {
metadata.put(key, value);
}
@@ -37,11 +55,24 @@ public void write(char[] cbuf, int off, int len) throws IOException {
@Override
public void close() throws IOException {
- // noop
+ // noop by default
}
@Override
public void flush() throws IOException {
- // noop
+ if (printStream != null) {
+ printStream.flush();
+ }
+ }
+
+ public Charset getCharset() {
+ return Charset.forName(charset);
+ }
+
+ public PrintStream getLogger() throws UnsupportedEncodingException {
+ if (printStream == null) {
+ printStream = new PrintStream(new ExternalLoggingOutputStream(this), true, charset);
+ }
+ return printStream;
}
}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingMethod.java b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingMethod.java
index be82b04..d4bbff2 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingMethod.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/ExternalLoggingMethod.java
@@ -18,7 +18,6 @@
import io.jenkins.plugins.extlogging.api.util.UniqueIdHelper;
import jenkins.model.Jenkins;
import jenkins.model.logging.Loggable;
-import jenkins.model.logging.LoggingMethod;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -28,21 +27,29 @@
* @author Oleg Nenashev
* @since TODO
*/
-public abstract class ExternalLoggingMethod extends LoggingMethod {
+public abstract class ExternalLoggingMethod extends LoggableHandler {
public ExternalLoggingMethod(@Nonnull Loggable loggable) {
super(loggable);
}
+ /**
+ * Gets default Log browser which should be used with this Logging method.
+ * It allows setting a custom default LogBrowser if needed.
+ * @return Log browser or {@code null} if not defined.
+ */
+ @CheckForNull
+ public ExternalLogBrowser getDefaultLogBrowser() {
+ return null;
+ }
+
//TODO: implement
@CheckForNull
- @Override
public TaskListener createTaskListener() {
return null;
}
// TODO: Implement event-based logic instead of the
- @Override
public BuildListener createBuildListener() throws IOException, InterruptedException {
return new ExternalLoggingBuildListener(createWriter());
}
@@ -98,7 +105,6 @@ public final ExternalLoggingEventWriter createWriter() throws IOException, Inter
protected abstract ExternalLoggingEventWriter _createWriter() throws IOException, InterruptedException;
@Nonnull
- @Override
public Launcher decorateLauncher(@Nonnull Launcher original,
@Nonnull Run,?> run, @Nonnull Node node) {
if (node instanceof Jenkins) {
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/LoggableHandler.java b/src/main/java/io/jenkins/plugins/extlogging/api/LoggableHandler.java
new file mode 100644
index 0000000..3e27e90
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/LoggableHandler.java
@@ -0,0 +1,79 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2018 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.jenkins.plugins.extlogging.api;
+
+import jenkins.model.logging.Loggable;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.Beta;
+import org.kohsuke.stapler.export.Exported;
+import org.kohsuke.stapler.export.ExportedBean;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Object which operates with {@link Loggable} items.
+ * @param Loggable type
+ * @author Oleg Nenashev
+ * @since TODO
+ */
+@ExportedBean
+@Restricted(Beta.class)
+public abstract class LoggableHandler {
+
+ protected transient TLoggable loggable;
+
+ public LoggableHandler(@Nonnull TLoggable loggable) {
+ this.loggable = loggable;
+ }
+
+ @Exported
+ public String getId() {
+ return getClass().getName();
+ }
+
+ /**
+ * Called when the owner is loaded from disk.
+ * The owner may be persisted on the disk, so the build reference should be {@code transient} (quasi-{@code final}) and restored here.
+ * @param loggable an owner to which this component is associated.
+ */
+ public void onLoad(@Nonnull TLoggable loggable) {
+ this.loggable = loggable;
+ }
+
+ public static void onLoad(@Nonnull T loggable, @CheckForNull LoggableHandler logHandler) {
+ if (logHandler != null) {
+ logHandler.onLoad(loggable);
+ }
+ }
+
+ @Nonnull
+ protected TLoggable getOwner() {
+ if (loggable == null) {
+ throw new IllegalStateException("Owner has not been assigned to the object yet");
+ }
+ return loggable;
+
+ }
+}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/impl/DisabledExternalLogBrowserFactory.java b/src/main/java/io/jenkins/plugins/extlogging/api/impl/DisabledExternalLogBrowserFactory.java
index 626d679..042ee34 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/impl/DisabledExternalLogBrowserFactory.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/impl/DisabledExternalLogBrowserFactory.java
@@ -1,9 +1,9 @@
package io.jenkins.plugins.extlogging.api.impl;
import hudson.Extension;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowser;
import io.jenkins.plugins.extlogging.api.ExternalLogBrowserFactory;
import io.jenkins.plugins.extlogging.api.ExternalLogBrowserFactoryDescriptor;
-import jenkins.model.logging.LogBrowser;
import jenkins.model.logging.Loggable;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -21,7 +21,7 @@ public DisabledExternalLogBrowserFactory() {
}
@Override
- public LogBrowser create(Loggable loggable) {
+ public ExternalLogBrowser create(Loggable loggable) {
return null;
}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLogStorage.java b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLogStorage.java
new file mode 100644
index 0000000..b39ba44
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLogStorage.java
@@ -0,0 +1,108 @@
+package io.jenkins.plugins.extlogging.api.impl;
+
+import hudson.console.AnnotatedLargeText;
+import hudson.model.BuildListener;
+import hudson.model.TaskListener;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowser;
+import io.jenkins.plugins.extlogging.api.ExternalLoggingMethod;
+import jenkins.model.logging.LogStorage;
+import jenkins.model.logging.Loggable;
+import org.kohsuke.stapler.export.Exported;
+import org.kohsuke.stapler.export.ExportedBean;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * External Log Storage implementation.
+ * @author Oleg Nenashev
+ * @since TODO
+ */
+@ExportedBean
+public class ExternalLogStorage extends LogStorage {
+
+ @Nonnull
+ private final ExternalLoggingMethod reporter;
+ @Nonnull
+ private final ExternalLogBrowser browser;
+
+ public ExternalLogStorage(TLoggable loggable,
+ @Nonnull ExternalLoggingMethod reporter,
+ @Nonnull ExternalLogBrowser browser) {
+ super(loggable);
+ this.reporter = reporter;
+ this.browser = browser;
+
+ }
+
+ @Override
+ public void onLoad(@Nonnull TLoggable loggable) {
+ super.onLoad(loggable);
+ reporter.onLoad(loggable);
+ browser.onLoad(loggable);
+ }
+
+ @CheckForNull
+ @Override
+ public TaskListener createTaskListener() throws IOException, InterruptedException {
+ return getReporter().createTaskListener();
+ }
+
+ @Nonnull
+ @Override
+ public BuildListener createBuildListener() throws IOException, InterruptedException {
+ return getReporter().createBuildListener();
+ }
+
+ @Nonnull
+ @Override
+ public AnnotatedLargeText overallLog() {
+ return getBrowser().overallLog();
+ }
+
+ @Nonnull
+ @Override
+ public AnnotatedLargeText stepLog(@CheckForNull String s, boolean completed) {
+ return getBrowser().stepLog(s, completed);
+ }
+
+ @Override
+ public InputStream getLogInputStream() throws IOException {
+ return getBrowser().getLogInputStream();
+ }
+
+ @Override
+ public List getLog(int i) throws IOException {
+ return getBrowser().getLog(i);
+ }
+
+ @Nonnull
+ @Override
+ public File getLogFile() throws IOException {
+ return getBrowser().getLogFile();
+ }
+
+ @Override
+ public boolean deleteLog() throws IOException {
+ return getBrowser().deleteLog();
+ }
+
+ @Nonnull
+ @Exported
+ public ExternalLogBrowser getBrowser() {
+ return browser;
+ }
+
+ @Nonnull
+ @Exported
+ public ExternalLoggingMethod getReporter() {
+ return reporter;
+ }
+
+
+
+}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLogStorageFactory.java b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLogStorageFactory.java
new file mode 100644
index 0000000..4c4052c
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLogStorageFactory.java
@@ -0,0 +1,69 @@
+package io.jenkins.plugins.extlogging.api.impl;
+
+import hudson.Extension;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowser;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowserFactory;
+import io.jenkins.plugins.extlogging.api.ExternalLoggingMethod;
+import io.jenkins.plugins.extlogging.api.ExternalLoggingMethodFactory;
+import jenkins.model.logging.LogStorage;
+import jenkins.model.logging.LogStorageFactory;
+import jenkins.model.logging.Loggable;
+
+import javax.annotation.CheckForNull;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Locator which provides logging methods and browsers from {@link ExternalLoggingGlobalConfiguration}.
+ * @author Oleg Nenashev
+ * @see ExternalLoggingGlobalConfiguration
+ */
+@Extension
+public class ExternalLogStorageFactory extends LogStorageFactory {
+
+ private static final Logger LOGGER =
+ Logger.getLogger(ExternalLogStorageFactory.class.getName());
+
+ @CheckForNull
+ @Override
+ protected LogStorage getLogStorage(Loggable loggable) {
+ ExternalLoggingMethod method = getLoggingMethod(loggable);
+ ExternalLogBrowser browser = getLogBrowser(loggable);
+ if (method == null || browser == null) {
+ LOGGER.log(Level.FINE, "Cannot find external log reporter or browser for {0}. " +
+ "Reporter: {1}, browser: {2}",
+ new Object[] {loggable, method, browser});
+ return null;
+ }
+
+ return new ExternalLogStorage(loggable, method, browser);
+ }
+
+ @CheckForNull
+ protected ExternalLoggingMethod getLoggingMethod(Loggable loggable) {
+ ExternalLoggingMethodFactory factory = ExternalLoggingGlobalConfiguration.getInstance().getLoggingMethod();
+ if (factory != null) {
+ return factory.create(loggable);
+ }
+ return null;
+ }
+
+ @CheckForNull
+ protected ExternalLogBrowser getLogBrowser(Loggable loggable) {
+ ExternalLogBrowser browser = null;
+ ExternalLogBrowserFactory factory = ExternalLoggingGlobalConfiguration.getInstance().getLogBrowser();
+ if (factory != null) {
+ browser = factory.create(loggable);
+ }
+
+ // If factories cannot provide the implementation, try the default
+ if (browser == null) {
+ ExternalLoggingMethod method = getLoggingMethod(loggable);
+ if (method != null) {
+ browser = method.getDefaultLogBrowser();
+ }
+ }
+
+ return browser;
+ }
+}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingBuildListener.java b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingBuildListener.java
index bf024e5..a4af101 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingBuildListener.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingBuildListener.java
@@ -4,6 +4,7 @@
import io.jenkins.plugins.extlogging.api.ExternalLoggingEventWriter;
import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
/**
* @author Oleg Nenashev
@@ -11,14 +12,19 @@
*/
public class ExternalLoggingBuildListener implements BuildListener {
- private final ExternalLoggingEventWriter writer;
+ protected final ExternalLoggingEventWriter writer;
public ExternalLoggingBuildListener(ExternalLoggingEventWriter writer) {
this.writer = writer;
}
+ //TODO: do something better about it
@Override
public PrintStream getLogger() {
- return new PrintStream(new ExternalLoggingOutputStream(writer));
+ try {
+ return writer.getLogger();
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException(e);
+ }
}
}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingGlobalConfiguration.java b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingGlobalConfiguration.java
index 411354f..415aa93 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingGlobalConfiguration.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingGlobalConfiguration.java
@@ -1,5 +1,6 @@
package io.jenkins.plugins.extlogging.api.impl;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import io.jenkins.plugins.extlogging.api.ExternalLogBrowserFactory;
import io.jenkins.plugins.extlogging.api.ExternalLoggingMethodFactory;
@@ -28,6 +29,8 @@ public class ExternalLoggingGlobalConfiguration extends GlobalConfiguration {
private ExternalLogBrowserFactory logBrowser;
@Nonnull
+ @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE",
+ justification = "Findbugs does not handle asserts right")
public static ExternalLoggingGlobalConfiguration getInstance() {
ExternalLoggingGlobalConfiguration cfg = GlobalConfiguration.all().get(ExternalLoggingGlobalConfiguration.class);
assert cfg != null : "Global configuration should be present";
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingLauncher.java b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingLauncher.java
index 7995edb..126e1a5 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingLauncher.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingLauncher.java
@@ -23,14 +23,15 @@
*/
package io.jenkins.plugins.extlogging.api.impl;
+import hudson.FilePath;
import hudson.Launcher;
import hudson.Proc;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.remoting.RemoteInputStream;
+import hudson.remoting.VirtualChannel;
import io.jenkins.plugins.extlogging.api.ExternalLoggingMethod;
import io.jenkins.plugins.extlogging.api.OutputStreamWrapper;
-import jenkins.model.logging.LoggingMethod;
import jenkins.security.MasterToSlaveCallable;
import org.apache.commons.io.input.NullInputStream;
import org.kohsuke.accmod.Restricted;
@@ -43,6 +44,7 @@
import java.util.ArrayList;
import java.util.List;
+//TODO: Likely YAGNI in the current implementation
/**
* Provides {@link Launcher} implementations for External Logging.
* These implementations plug-in {@link ExternalLoggingMethod} into the on-agent executions.
@@ -61,7 +63,7 @@ public DefaultLocalLauncher(Launcher inner) {
}
/**
- * Default remote launcher which redirects all the output and error to the stream {@link LoggingMethod} provides.
+ * Default remote launcher which redirects all the output and error to the stream {@link ExternalLoggingMethod} provides.
*/
public static class DefaultRemoteLauncher extends Launcher.DecoratedLauncher {
private static final NullInputStream NULL_INPUT_STREAM = new NullInputStream(0);
@@ -85,16 +87,22 @@ public Proc launch(Launcher.ProcStarter ps) throws IOException {
// RemoteLogstashReporterStream(new CloseProofOutputStream(ps.stdout()
final OutputStream out = ps.stdout() == null ? null : (streamOut == null ? ps.stdout() : streamOut.toSerializableOutputStream());
final OutputStream err = ps.stdout() == null ? null : (streamErr == null ? ps.stdout() : streamErr.toSerializableOutputStream());
- final InputStream in = (ps.stdin() == null || ps.stdin() == NULL_INPUT_STREAM) ? null : new RemoteInputStream(ps.stdin(), false);
- final String workDir = ps.pwd() == null ? null : ps.pwd().getRemote();
+ final InputStream in = (ps.stdin() == null || ps.stdin() == NULL_INPUT_STREAM)
+ ? null : new RemoteInputStream(ps.stdin(), false);
+
+ final FilePath pwd = ps.pwd();
+ final String workDir = pwd == null ? null : pwd.getRemote();
// TODO: we do not reverse streams => the parameters
try {
final RemoteLaunchCallable callable = new RemoteLaunchCallable(
ps.cmds(), ps.masks(), ps.envs(), in,
out, err, ps.quiet(), workDir, listener);
-
- return new Launcher.RemoteLauncher.ProcImpl(getChannel().call(callable));
+ final VirtualChannel channel = getChannel();
+ if (channel == null) {
+ throw new IOException("Channel is null");
+ }
+ return new Launcher.RemoteLauncher.ProcImpl(channel.call(callable));
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e);
}
@@ -134,14 +142,14 @@ public Launcher.RemoteProcess call() throws IOException {
final Proc p = ps.start();
- return Channel.current().export(Launcher.RemoteProcess.class, new Launcher.RemoteProcess() {
+ return Channel.currentOrFail().export(Launcher.RemoteProcess.class, new Launcher.RemoteProcess() {
public int join() throws InterruptedException, IOException {
try {
return p.join();
} finally {
// make sure I/O is delivered to the remote before we return
try {
- Channel.current().syncIO();
+ Channel.currentOrFail().syncIO();
} catch (Throwable th) {
// this includes a failure to sync, slave.jar too old, etc
}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingMethodLocator.java b/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingMethodLocator.java
deleted file mode 100644
index f8f6bce..0000000
--- a/src/main/java/io/jenkins/plugins/extlogging/api/impl/ExternalLoggingMethodLocator.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.jenkins.plugins.extlogging.api.impl;
-
-import hudson.Extension;
-import io.jenkins.plugins.extlogging.api.ExternalLogBrowserFactory;
-import io.jenkins.plugins.extlogging.api.ExternalLoggingMethodFactory;
-import jenkins.model.logging.LogBrowser;
-import jenkins.model.logging.Loggable;
-import jenkins.model.logging.LoggingMethod;
-import jenkins.model.logging.LoggingMethodLocator;
-
-import javax.annotation.CheckForNull;
-
-/**
- * Locator which provides logging nethods and browsers from {@link ExternalLoggingGlobalConfiguration}.
- * @author Oleg Nenashev
- * @see ExternalLoggingGlobalConfiguration
- */
-@Extension
-public class ExternalLoggingMethodLocator extends LoggingMethodLocator {
-
- @CheckForNull
- @Override
- protected LoggingMethod getLoggingMethod(Loggable loggable) {
- ExternalLoggingMethodFactory factory = ExternalLoggingGlobalConfiguration.getInstance().getLoggingMethod();
- if (factory != null) {
- return factory.create(loggable);
- }
- return null;
- }
-
- @CheckForNull
- @Override
- protected LogBrowser getLogBrowser(Loggable loggable) {
- ExternalLogBrowserFactory factory = ExternalLoggingGlobalConfiguration.getInstance().getLogBrowser();
- if (factory != null) {
- return factory.create(loggable);
- }
- return null;
- }
-}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorage.java b/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorage.java
index f4875bc..5edd8d8 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorage.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorage.java
@@ -28,11 +28,10 @@
import hudson.console.AnnotatedLargeText;
import hudson.model.BuildListener;
import hudson.model.TaskListener;
-import io.jenkins.plugins.extlogging.api.ExternalLoggingEventWriter;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowser;
import io.jenkins.plugins.extlogging.api.ExternalLoggingMethod;
-import io.jenkins.plugins.extlogging.api.impl.ExternalLoggingOutputStream;
+import io.jenkins.plugins.extlogging.api.impl.ExternalLoggingBuildListener;
import io.jenkins.plugins.extlogging.api.SensitiveStringsProvider;
-import jenkins.model.logging.LogBrowser;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
@@ -40,7 +39,6 @@
import javax.annotation.Nonnull;
import java.io.IOException;
-import java.io.PrintStream;
import java.util.Collection;
import java.util.logging.Logger;
@@ -54,10 +52,11 @@ public class ExternalPipelineLogStorage implements LogStorage {
Logger.getLogger(ExternalPipelineLogStorage.class.getName());
private final ExternalLoggingMethod lm;
- private final LogBrowser logBrowser;
+ private final ExternalLogBrowser logBrowser;
private final WorkflowRun run;
- ExternalPipelineLogStorage(@Nonnull WorkflowRun run, @Nonnull ExternalLoggingMethod lm, @Nonnull LogBrowser browser) {
+ ExternalPipelineLogStorage(@Nonnull WorkflowRun run, @Nonnull ExternalLoggingMethod lm,
+ @Nonnull ExternalLogBrowser browser) {
this.run = run;
this.lm = lm;
this.logBrowser = browser;
@@ -88,16 +87,15 @@ public AnnotatedLargeText stepLog(@Nonnull FlowNode flowNode, boolean
return logBrowser.stepLog(flowNode.getId(), completed);
}
- private static class PipelineListener implements BuildListener {
+ private static class PipelineListener extends ExternalLoggingBuildListener {
private static final long serialVersionUID = 1;
+ //TODO: unused, remove?
private final Collection sensitiveStrings;
- private final ExternalLoggingEventWriter writer;
- private transient PrintStream logger;
PipelineListener(WorkflowRun run, ExternalLoggingMethod method) throws IOException, InterruptedException {
- this.writer = method.createWriter();
+ super(method.createWriter());
this.sensitiveStrings = SensitiveStringsProvider.getAllSensitiveStrings(run);
}
@@ -106,14 +104,6 @@ private static class PipelineListener implements BuildListener {
writer.addMetadataEntry("stepId", node.getId());
}
- @Override
- public PrintStream getLogger() {
- if (logger == null) {
- logger = new PrintStream(ExternalLoggingOutputStream.createOutputStream(writer, sensitiveStrings));
- }
- return logger;
- }
-
}
}
diff --git a/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorageFactory.java b/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorageFactory.java
index 5005715..b9c0e0b 100644
--- a/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorageFactory.java
+++ b/src/main/java/io/jenkins/plugins/extlogging/api/integrations/pipeline/ExternalPipelineLogStorageFactory.java
@@ -3,11 +3,8 @@
import hudson.Extension;
import hudson.model.Queue;
import hudson.model.Run;
-import io.jenkins.plugins.extlogging.api.ExternalLoggingMethod;
+import io.jenkins.plugins.extlogging.api.impl.ExternalLogStorage;
import io.jenkins.plugins.extlogging.api.integrations.MaskPasswordsSensitiveStringsProvider.MaskSensitiveStringsProvider;
-import jenkins.model.logging.LogBrowser;
-import jenkins.model.logging.LoggingMethod;
-import jenkins.model.logging.LoggingMethodLocator;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.log.LogStorage;
@@ -53,13 +50,12 @@ public LogStorage forBuild(@Nonnull FlowExecutionOwner flowExecutionOwner) {
return null;
}
- final LoggingMethod loggingMethod = LoggingMethodLocator.locate(run);
- final LogBrowser browser = LoggingMethodLocator.locateBrowser(run);
-
- if (loggingMethod instanceof ExternalLoggingMethod) {
+ jenkins.model.logging.LogStorage logStorage = run.getLogStorage();
+ if (logStorage instanceof ExternalLogStorage) {
+ ExternalLogStorage extLogStorage = (ExternalLogStorage)logStorage;
return new ExternalPipelineLogStorage(run,
- (ExternalLoggingMethod) loggingMethod,
- browser);
+ extLogStorage.getReporter(),
+ extLogStorage.getBrowser());
}
return null;
diff --git a/src/test/java/io/jenkins/plugins/extlogging/api/FreestyleJobTest.java b/src/test/java/io/jenkins/plugins/extlogging/api/FreestyleJobTest.java
index dcf2fea..e62baa8 100644
--- a/src/test/java/io/jenkins/plugins/extlogging/api/FreestyleJobTest.java
+++ b/src/test/java/io/jenkins/plugins/extlogging/api/FreestyleJobTest.java
@@ -3,6 +3,7 @@
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.tasks.Shell;
+import io.jenkins.plugins.extlogging.api.impl.ExternalLogStorage;
import io.jenkins.plugins.extlogging.api.impl.ExternalLoggingGlobalConfiguration;
import io.jenkins.plugins.extlogging.api.util.MockExternalLoggingEventWriter;
import io.jenkins.plugins.extlogging.api.util.MockLogBrowserFactory;
@@ -19,6 +20,9 @@
import java.io.File;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
/**
* @author Oleg Nenashev
* @since TODO
@@ -47,7 +51,9 @@ public void spotcheck_Mock() throws Exception {
project.getBuildersList().add(new Shell("echo hello"));
FreeStyleBuild build = j.buildAndAssertSuccess(project);
- MockLoggingMethod lm = (MockLoggingMethod)build.getLoggingMethod();
+ assertThat(build.getLogStorage(), instanceOf(ExternalLogStorage.class));
+ ExternalLogStorage storage = (ExternalLogStorage) build.getLogStorage();
+ MockLoggingMethod lm = (MockLoggingMethod)storage.getReporter();
MockExternalLoggingEventWriter writer = lm.getWriter();
Assert.assertTrue(writer.isEventWritten());
j.assertLogContains("hello", build);
diff --git a/src/test/java/io/jenkins/plugins/extlogging/api/PipelineSmokeTest.java b/src/test/java/io/jenkins/plugins/extlogging/api/PipelineSmokeTest.java
index 9fe0898..c808fa5 100644
--- a/src/test/java/io/jenkins/plugins/extlogging/api/PipelineSmokeTest.java
+++ b/src/test/java/io/jenkins/plugins/extlogging/api/PipelineSmokeTest.java
@@ -1,6 +1,7 @@
package io.jenkins.plugins.extlogging.api;
import hudson.model.Run;
+import io.jenkins.plugins.extlogging.api.impl.ExternalLogStorage;
import io.jenkins.plugins.extlogging.api.impl.ExternalLoggingGlobalConfiguration;
import io.jenkins.plugins.extlogging.api.util.MockExternalLoggingEventWriter;
import io.jenkins.plugins.extlogging.api.util.MockLogBrowser;
@@ -50,8 +51,10 @@ public void spotcheck_Mock() throws Exception {
project.setDefinition(new CpsFlowDefinition("echo 'Hello'", true));
Run build = j.buildAndAssertSuccess(project);
- assertThat(build.getLoggingMethod(), instanceOf(MockLoggingMethod.class));
- MockLoggingMethod loggingMethod = (MockLoggingMethod)build.getLoggingMethod();
+ assertThat(build.getLogStorage(), instanceOf(ExternalLogStorage.class));
+ ExternalLogStorage storage = (ExternalLogStorage) build.getLogStorage();
+ assertThat(storage.getReporter(), instanceOf(MockLoggingMethod.class));
+ MockLoggingMethod loggingMethod = (MockLoggingMethod)storage.getReporter();
MockExternalLoggingEventWriter writer = loggingMethod.getWriter();
// Do not try to add it. Pipeline creates separate PipelineLogListeners for each call
// Assert.assertTrue(writer.isEventWritten());
diff --git a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockExternalLoggingEventWriter.java b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockExternalLoggingEventWriter.java
index c4233f7..dc0a15a 100644
--- a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockExternalLoggingEventWriter.java
+++ b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockExternalLoggingEventWriter.java
@@ -6,6 +6,7 @@
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.nio.charset.Charset;
import java.util.logging.Logger;
/**
@@ -23,7 +24,8 @@ public class MockExternalLoggingEventWriter extends ExternalLoggingEventWriter {
// Debug flags
private boolean eventWritten;
- public MockExternalLoggingEventWriter(File dest) {
+ public MockExternalLoggingEventWriter(File dest, Charset charset) {
+ super(charset);
this.dest = dest;
}
diff --git a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowser.java b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowser.java
index 789c972..670e8b7 100644
--- a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowser.java
+++ b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowser.java
@@ -1,12 +1,16 @@
package io.jenkins.plugins.extlogging.api.util;
+import hudson.console.AnnotatedLargeText;
import hudson.model.Run;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowser;
import jenkins.model.logging.Loggable;
-import jenkins.model.logging.impl.FileLogBrowser;
+import jenkins.model.logging.impl.BrokenAnnotatedLargeText;
+import javax.annotation.CheckForNull;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -14,7 +18,7 @@
* @author Oleg Nenashev
* @since TODO
*/
-public class MockLogBrowser extends FileLogBrowser {
+public class MockLogBrowser extends ExternalLogBrowser {
private static final Logger LOGGER =
Logger.getLogger(MockLogBrowser.class.getName());
@@ -31,8 +35,45 @@ public MockLogBrowser(Run, ?> run, File baseDir) {
return (Run, ?>)super.getOwner();
}
- @Override
public File getLogFileOrFail(Loggable loggable) throws IOException {
return new File(baseDir, getOwner().getFullDisplayName() + ".txt");
}
+
+ @Override
+ public AnnotatedLargeText overallLog() {
+ final File logFile;
+ try {
+ logFile = getLogFileOrFail(getOwner());
+ } catch (IOException ex) {
+ return new BrokenAnnotatedLargeText(ex, getOwner().getCharset());
+ }
+
+ return new AnnotatedLargeText
+ (logFile, getOwner().getCharset(), getOwner().isLoggingFinished(), getOwner());
+ }
+
+ @Override
+ public AnnotatedLargeText stepLog(@CheckForNull String stepId, boolean b) {
+ // Not supported, there is no default implementation for "step"
+ return new BrokenAnnotatedLargeText(
+ new UnsupportedOperationException(MockLogBrowser.class.getName() + " does not support partial logs"),
+ getOwner().getCharset()
+ );
+ }
+
+ @Override
+ public boolean deleteLog() throws IOException {
+ File logFile = getLogFileOrFail(loggable);
+ if (logFile.exists()) {
+ try {
+ Files.delete(logFile.toPath());
+ } catch (Exception ex) {
+ throw new IOException("Failed to delete " + logFile, ex);
+ }
+ } else {
+ LOGGER.log(Level.FINE, "Trying to delete Log File of {0} which does not exist: {1}",
+ new Object[] {loggable, logFile});
+ }
+ return true;
+ }
}
diff --git a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowserFactory.java b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowserFactory.java
index bb939ff..8ae676e 100644
--- a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowserFactory.java
+++ b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLogBrowserFactory.java
@@ -1,13 +1,14 @@
package io.jenkins.plugins.extlogging.api.util;
import hudson.model.Run;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowser;
import io.jenkins.plugins.extlogging.api.ExternalLogBrowserFactory;
-import jenkins.model.logging.LogBrowser;
import jenkins.model.logging.Loggable;
import java.io.File;
/**
+ * Producer for {@link MockLogBrowser}.
* @author Oleg Nenashev
* @since TODO
*/
@@ -20,7 +21,7 @@ public MockLogBrowserFactory(File baseDir) {
}
@Override
- public LogBrowser create(Loggable loggable) {
+ public ExternalLogBrowser create(Loggable loggable) {
return new MockLogBrowser((Run, ?>)loggable, baseDir);
}
}
diff --git a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLoggingMethod.java b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLoggingMethod.java
index 01e8dc2..c872c5b 100644
--- a/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLoggingMethod.java
+++ b/src/test/java/io/jenkins/plugins/extlogging/api/util/MockLoggingMethod.java
@@ -1,9 +1,9 @@
package io.jenkins.plugins.extlogging.api.util;
import hudson.model.Run;
+import io.jenkins.plugins.extlogging.api.ExternalLogBrowser;
import io.jenkins.plugins.extlogging.api.ExternalLoggingEventWriter;
import io.jenkins.plugins.extlogging.api.ExternalLoggingMethod;
-import jenkins.model.logging.LogBrowser;
import javax.annotation.CheckForNull;
import java.io.File;
@@ -30,7 +30,9 @@ public MockLoggingMethod(Run,?> run, File baseDir) {
@Override
public ExternalLoggingEventWriter _createWriter() {
- writer = new MockExternalLoggingEventWriter(new File(baseDir, getOwner().getFullDisplayName() + ".txt"));
+ writer = new MockExternalLoggingEventWriter(new File(baseDir,
+ getOwner().getFullDisplayName() + ".txt"),
+ loggable.getCharset());
return writer;
}
@@ -40,7 +42,7 @@ public MockExternalLoggingEventWriter getWriter() {
}
@Override
- public LogBrowser getDefaultLogBrowser() {
+ public ExternalLogBrowser getDefaultLogBrowser() {
return new MockLogBrowser(getOwner(), baseDir);
}
}