Skip to content

Commit

Permalink
Merge pull request #214 from tszmytka/212-partial-log-output
Browse files Browse the repository at this point in the history
212 partial log output
  • Loading branch information
dblock authored Jan 11, 2021
2 parents 90868ca + e0b438b commit 9b3952a
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
0.7.4 (Next)
============

* [#213](https://github.com/jenkinsci/ansicolor-plugin/pull/213): Remove tabular markup on new Jenkins core - [@timja](https://github.com/timja).
* [#214](https://github.com/jenkinsci/ansicolor-plugin/pull/214): Render logs correctly also in Jenkins versions greater than 2.260 - [@tszmytka](https://github.com/tszmytka).
* Your contribution here.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import hudson.console.ConsoleNote;
import hudson.model.Run;
import hudson.model.listeners.RunListener;
import hudson.util.VersionNumber;
import jenkins.model.Jenkins;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
Expand All @@ -32,8 +31,8 @@ public ShortlogActionCreator(LineIdentifier lineIdentifier, String eol) {
this.eol = eol.getBytes(UTF_8);
}

public ColorizedAction createActionForShortlog(File logFile, Map<String, ColorizedAction> actions, int shortlogLimit) {
final ActionContext lastAction = findLastActionBefore(logFile, actions.keySet(), shortlogLimit);
public ColorizedAction createActionForShortlog(File logFile, Map<String, ColorizedAction> actions, int shortlogLimit, boolean keepLinesWhole) {
final ActionContext lastAction = findLastActionBefore(logFile, actions.keySet(), shortlogLimit, keepLinesWhole);
if (!lastAction.isEmpty()) {
final ColorizedAction colorizedAction = actions.get(lastAction.serializedAction);
if (ColorizedAction.Command.START.equals(colorizedAction.getCommand())) {
Expand All @@ -43,7 +42,7 @@ public ColorizedAction createActionForShortlog(File logFile, Map<String, Coloriz
return null;
}

private ActionContext findLastActionBefore(File logFile, Collection<String> serializedActions, int shortlogLimit) {
private ActionContext findLastActionBefore(File logFile, Collection<String> serializedActions, int shortlogLimit, boolean keepLinesWhole) {
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(logFile))) {
final long shortlogStart = logFile.length() - shortlogLimit * 1024L;
if (shortlogStart > 0) {
Expand All @@ -60,11 +59,14 @@ private ActionContext findLastActionBefore(File logFile, Collection<String> seri
}
if (totalRead + read >= shortlogStart) {
final int eolPos = indexOfEol(buf, startInBuff);
if (eolPos != -1 && !lastAction.isEmpty()) {
return new ActionContext(lastAction, partialLine + new String(buf, startInBuff, eolPos - startInBuff + eol.length, UTF_8));
final int[] beginLength = calculateBeginLength(buf, startInBuff, eolPos, partialLine.isEmpty() && keepLinesWhole);
final int begin = beginLength[0];
final int length = beginLength[1];
if (length != -1 && !lastAction.isEmpty()) {
return new ActionContext(lastAction, partialLine + new String(buf, begin, length, UTF_8));
} else {
// line extends to the next buffer
partialLine = new String(Arrays.copyOfRange(buf, startInBuff, buf.length), UTF_8);
partialLine = new String(Arrays.copyOfRange(buf, begin, buf.length), UTF_8);
}
}
totalRead += read;
Expand All @@ -88,14 +90,22 @@ private String findLastAction(Collection<String> serializedActions, byte[] buf,
}

private int indexOfEol(byte[] buf, int after) {
for (int i = after; i < buf.length; i++) {
for (int i = after + 1; i < buf.length; i++) {
if (Arrays.equals(Arrays.copyOfRange(buf, i, i + eol.length), eol)) {
return i;
}
}
return -1;
}

private int[] calculateBeginLength(byte[] buf, int startInBuff, int eolPos, boolean keepLinesWhole) {
if (keepLinesWhole) {
final int begin = eolPos != -1? eolPos + eol.length: startInBuff;
return new int[]{begin, eolPos != -1 ? indexOfEol(buf, eolPos) - begin + eol.length : -1};
}
return new int[]{startInBuff, eolPos != -1 ? eolPos - startInBuff + eol.length : -1};
}

@Extension
public static class Listener extends RunListener<Run<?, ?>> {
@Override
Expand All @@ -116,8 +126,14 @@ public void onFinalized(Run<?, ?> run) {
final File logFile = new File(run.getRootDir(), "log");
if (logFile.isFile()) {
final ShortlogActionCreator shortlogActionCreator = new ShortlogActionCreator(new LineIdentifier(), System.lineSeparator());
final VersionNumber keepLinesWholeVersion = new VersionNumber("2.260");
final String consoleTail = System.getProperty("hudson.consoleTailKB");
final ColorizedAction action = shortlogActionCreator.createActionForShortlog(logFile, actions, consoleTail != null ? Integer.parseInt(consoleTail) : CONSOLE_TAIL_DEFAULT);
final ColorizedAction action = shortlogActionCreator.createActionForShortlog(
logFile,
actions,
consoleTail != null ? Integer.parseInt(consoleTail) : CONSOLE_TAIL_DEFAULT,
Optional.ofNullable(Jenkins.getVersion()).orElse(keepLinesWholeVersion).isNewerThan(keepLinesWholeVersion)
);
if (action != null) {
run.addAction(action);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,39 @@ public void setUp() throws Exception {

@Test
public void canCreateActionForShortlog() {
canCreateActionForShortlog(shortlogActionCreator, "[Pipeline] echo\n", "testlog.log");
final String shortlogLine = "\u001B[3B\u001B[2A\u001B[2K \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold\n";
canCreateActionForShortlog(shortlogActionCreator, shortlogLine, "testlog.log", true);
}

@Test
public void canCreateActionForShortlogBreakLines() {
canCreateActionForShortlog(shortlogActionCreator, "[Pipeline] echo\n", "testlog.log", false);
}

@Test
public void canCreateActionForShortlogForWindows() {
final String shortlogLine = "\u001B[3B\u001B[2A\u001B[2K \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold \u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mlightgreen bold " +
"\u001B[92m\u001B[22mlightgreen normal\u001B[0m \u001B[92m\u001B[1mligh";
final String eol = "\r\n";
canCreateActionForShortlog(new ShortlogActionCreator(lineIdentifier, eol), shortlogLine + eol, "testlog-crlf.log", true);
}

@Test
public void canCreateActionForShortlogForWindowsBreakLines() {
final String eol = "\r\n";
canCreateActionForShortlog(new ShortlogActionCreator(lineIdentifier, eol), "[Pipeline] echo" + eol, "testlog-crlf.log");
canCreateActionForShortlog(new ShortlogActionCreator(lineIdentifier, eol), "[Pipeline] echo" + eol, "testlog-crlf.log", false);
}

private void canCreateActionForShortlog(ShortlogActionCreator shortlogActionCreator, String shortlogLine, String logFile) {
private void canCreateActionForShortlog(ShortlogActionCreator shortlogActionCreator, String shortlogLine, String logFile, boolean keepLinesWhole) {
final String lineHash = "mock-line-hash";
final ColorizedAction colorizedAction = new ColorizedAction("xterm", ColorizedAction.Command.START);
final String serializedNote = "<mock-serialized-note-start>";
Expand All @@ -50,7 +73,7 @@ private void canCreateActionForShortlog(ShortlogActionCreator shortlogActionCrea
startActions.put(ConsoleNote.PREAMBLE_STR + serializedNote, colorizedAction);
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-stop>", new ColorizedAction("xterm", ColorizedAction.Command.STOP));
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start1>", new ColorizedAction("gnome-terminal", ColorizedAction.Command.START));
final ColorizedAction shortlogAction = shortlogActionCreator.createActionForShortlog(file, startActions, 3);
final ColorizedAction shortlogAction = shortlogActionCreator.createActionForShortlog(file, startActions, 3, keepLinesWhole);
assertEquals(colorizedAction.getColorMapName(), shortlogAction.getColorMapName());
assertEquals(colorizedAction.getCommand(), shortlogAction.getCommand());
assertNotEquals(colorizedAction.getId(), shortlogAction.getId());
Expand All @@ -59,56 +82,77 @@ private void canCreateActionForShortlog(ShortlogActionCreator shortlogActionCrea

@Test
public void wontCreateActionForLogFileShorterThanShortlogLimit() {
final String serializedNote = "<mock-serialized-note-start>";
final ColorizedAction colorizedAction = new ColorizedAction("xterm", ColorizedAction.Command.START);
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog.log")).getFile());
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start0>", new ColorizedAction("css", ColorizedAction.Command.START));
startActions.put(ConsoleNote.PREAMBLE_STR + serializedNote, colorizedAction);
assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 256));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
final boolean[] keepLinesWholeOptions = {true, false};
for (boolean keepLinesWhole : keepLinesWholeOptions) {
final String serializedNote = "<mock-serialized-note-start>";
final ColorizedAction colorizedAction = new ColorizedAction("xterm", ColorizedAction.Command.START);
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog.log")).getFile());
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start0>", new ColorizedAction("css", ColorizedAction.Command.START));
startActions.put(ConsoleNote.PREAMBLE_STR + serializedNote, colorizedAction);
assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 256, keepLinesWhole));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
}
}

@Test
public void wontCreateActionIfBuildHasNoStartActions() {
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog.log")).getFile());
assertNull(shortlogActionCreator.createActionForShortlog(file, new HashMap<>(), 3));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
final boolean[] keepLinesWholeOptions = {true, false};
for (boolean keepLinesWhole : keepLinesWholeOptions) {
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog.log")).getFile());
assertNull(shortlogActionCreator.createActionForShortlog(file, new HashMap<>(), 3, keepLinesWhole));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
}
}

@Test
public void wontCreateActionIfNoCorrespondingNotesArePresent() {
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start0>", new ColorizedAction("css", ColorizedAction.Command.START));
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-stop0>", new ColorizedAction("css", ColorizedAction.Command.STOP));
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog-no-notes.log")).getFile());
assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 3));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
final boolean[] keepLinesWholeOptions = {true, false};
for (boolean keepLinesWhole : keepLinesWholeOptions) {
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start0>", new ColorizedAction("css", ColorizedAction.Command.START));
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-stop0>", new ColorizedAction("css", ColorizedAction.Command.STOP));
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog-no-notes.log")).getFile());
assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 3, keepLinesWhole));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
}
}

@Test
public void wontCreateActionIfNoLogFileIsPresent() {
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start0>", new ColorizedAction("css", ColorizedAction.Command.START));
final File file = new File("non-existing.log");
assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 3));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
final boolean[] keepLinesWholeOptions = {true, false};
for (boolean keepLinesWhole : keepLinesWholeOptions) {
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start0>", new ColorizedAction("css", ColorizedAction.Command.START));
final File file = new File("non-existing.log");
assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 3, keepLinesWhole));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
}
}

@Test
public void wontCreateActionIfActionIsNotActiveAtShortlogLimit() {
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog-action-not-active.log")).getFile());
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start>", new ColorizedAction("css", ColorizedAction.Command.START));
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-stop>", new ColorizedAction("css", ColorizedAction.Command.STOP));

assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 3));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
final boolean[] keepLinesWholeOptions = {true, false};
for (boolean keepLinesWhole : keepLinesWholeOptions) {
final File file = new File(getClass().getResource(String.join("/", "", getClass().getName().replace('.', '/'), "testlog-action-not-active.log")).getFile());
final HashMap<String, ColorizedAction> startActions = new HashMap<>();
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-start>", new ColorizedAction("css", ColorizedAction.Command.START));
startActions.put(ConsoleNote.PREAMBLE_STR + "<mock-serialized-note-stop>", new ColorizedAction("css", ColorizedAction.Command.STOP));

assertNull(shortlogActionCreator.createActionForShortlog(file, startActions, 3, keepLinesWhole));
verify(lineIdentifier, never()).hash(anyString(), anyLong());
}
}

@Test
public void canCreateActionForShortlogOnLogLineExceedingBufferSize() {
final String s = "[Pipeline] echo a very very very long line,a very very very long line,a very very very long line,a very very very long line,a very very very long line";
canCreateActionForShortlog(shortlogActionCreator, s + "\n", "testlog-long.log");
canCreateActionForShortlog(shortlogActionCreator, s + "\n", "testlog-long.log", true);
}

@Test
public void canCreateActionForShortlogOnLogLineExceedingBufferSizeBreakLines() {
final String s = "[Pipeline] echo a very very very long line,a very very very long line,a very very very long line,a very very very long line,a very very very long line";
canCreateActionForShortlog(shortlogActionCreator, s + "\n", "testlog-long.log", false);
}
}

0 comments on commit 9b3952a

Please sign in to comment.