diff --git a/pom.xml b/pom.xml
index 9ce46c5..f39b2ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,16 @@
workflow-step-api
+
+ org.jenkins-ci.plugins.workflow
+ workflow-api
+
+
+
+ org.jenkins-ci.plugins.workflow
+ workflow-job
+
+
io.jenkins.plugins
@@ -99,12 +109,6 @@
test
-
- org.jenkins-ci.plugins.workflow
- workflow-job
- test
-
-
org.jenkins-ci.plugins.workflow
workflow-durable-task-step
diff --git a/src/main/java/io/jenkins/plugins/explain_error/ConsoleExplainErrorAction.java b/src/main/java/io/jenkins/plugins/explain_error/ConsoleExplainErrorAction.java
index 8c53156..f4b2249 100644
--- a/src/main/java/io/jenkins/plugins/explain_error/ConsoleExplainErrorAction.java
+++ b/src/main/java/io/jenkins/plugins/explain_error/ConsoleExplainErrorAction.java
@@ -6,9 +6,12 @@
import jenkins.model.RunAction2;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.List;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
+
+import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -86,7 +89,7 @@ public void doExplainConsoleError(StaplerRequest2 req, StaplerResponse2 rsp) thr
}
// Fetch the last N lines of the log
- java.util.List logLines = run.getLog(maxLines);
+ List logLines = PipelineLogExtractor.getFailedStepLog(run, maxLines);
String errorText = String.join("\n", logLines);
ErrorExplainer explainer = new ErrorExplainer();
diff --git a/src/main/java/io/jenkins/plugins/explain_error/ErrorExplainer.java b/src/main/java/io/jenkins/plugins/explain_error/ErrorExplainer.java
index 476fe11..370614f 100644
--- a/src/main/java/io/jenkins/plugins/explain_error/ErrorExplainer.java
+++ b/src/main/java/io/jenkins/plugins/explain_error/ErrorExplainer.java
@@ -11,6 +11,7 @@
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
+import org.jenkinsci.plugins.workflow.job.WorkflowRun;
/**
* Service class responsible for explaining errors using AI.
@@ -60,7 +61,7 @@ public void explainError(Run, ?> run, TaskListener listener, String logPattern
}
private String extractErrorLogs(Run, ?> run, String logPattern, int maxLines) throws IOException {
- List logLines = run.getLog(maxLines);
+ List logLines = PipelineLogExtractor.getFailedStepLog(run, maxLines);
if (StringUtils.isBlank(logPattern)) {
// Return last few lines if no pattern specified
diff --git a/src/main/java/io/jenkins/plugins/explain_error/PipelineLogExtractor.java b/src/main/java/io/jenkins/plugins/explain_error/PipelineLogExtractor.java
new file mode 100644
index 0000000..ad1c76b
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/explain_error/PipelineLogExtractor.java
@@ -0,0 +1,88 @@
+package io.jenkins.plugins.explain_error;
+
+import org.jenkinsci.plugins.workflow.job.WorkflowRun;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import org.jenkinsci.plugins.workflow.flow.FlowExecution;
+import org.jenkinsci.plugins.workflow.graph.FlowNode;
+import org.jenkinsci.plugins.workflow.graph.FlowGraphWalker;
+import org.jenkinsci.plugins.workflow.actions.ErrorAction;
+import org.jenkinsci.plugins.workflow.actions.LogAction;
+import hudson.console.AnnotatedLargeText;
+import hudson.console.ConsoleNote;
+import hudson.model.Run;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+public class PipelineLogExtractor {
+
+ private static List readLimitedLog(AnnotatedLargeText extends FlowNode> logText,
+ int maxLines) {
+ StringWriter writer = new StringWriter();
+ try {
+ long offset = logText.writeLogTo(0, writer);
+ if (offset <= 0) {
+ return Collections.emptyList();
+ }
+ String cleanLog = ConsoleNote.removeNotes(writer.toString());
+ BufferedReader reader = new BufferedReader(new StringReader(cleanLog));
+ LinkedList queue = new LinkedList<>();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (queue.size() >= maxLines) {
+ queue.removeFirst();
+ }
+ line = line.replace("\n", "").replace("\r", "");
+ queue.add(line);
+ }
+ return new ArrayList<>(queue);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Extracts the log output of the specific step that caused the pipeline failure.
+ * @param run The pipeline build
+ * @return The log text of the failed step, or null if no failed step with a log is found.
+ * @throws IOException
+ */
+ public static List getFailedStepLog(@NonNull Run, ?> run, int maxLines) throws IOException {
+
+ if (run instanceof WorkflowRun) {
+ FlowExecution execution = ((WorkflowRun) run).getExecution();
+
+ FlowGraphWalker walker = new FlowGraphWalker(execution);
+ for (FlowNode node : walker) {
+ ErrorAction errorAction = node.getAction(ErrorAction.class);
+ if (errorAction != null) {
+ FlowNode nodeThatThrewException = ErrorAction.findOrigin(errorAction.getError(), execution);
+ if (nodeThatThrewException == null) {
+ continue;
+ }
+ LogAction logAction = nodeThatThrewException.getAction(LogAction.class);
+ if (logAction != null) {
+ AnnotatedLargeText extends FlowNode> logText = logAction.getLogText();
+ List result = readLimitedLog(logText, maxLines);
+ if (result == null || result.isEmpty())
+ {
+ continue;
+ }
+ return result;
+ }
+ }
+ }
+ }
+
+ return run.getLog(maxLines);
+ }
+}