Skip to content

Commit

Permalink
Trigger tailed log output colorization also after Jenkins restart (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
tszmytka authored May 16, 2021
1 parent 0d69f83 commit c4984f1
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 214 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* [#219](https://github.com/jenkinsci/ansicolor-plugin/pull/219): Set env var TERM also if a default color map is used - [@tszmytka](https://github.com/tszmytka).
* [#226](https://github.com/jenkinsci/ansicolor-plugin/pull/226): Work around withMaven step not passing on log line metadata - [@tszmytka](https://github.com/tszmytka).
* [#225](https://github.com/jenkinsci/ansicolor-plugin/pull/225): Work around logstash-plugin hiding some log lines from ansicolor - [@tszmytka](https://github.com/tszmytka).
* [#227](https://github.com/jenkinsci/ansicolor-plugin/pull/227): Trigger tailed log output colorization also after Jenkins restart - [@tszmytka](https://github.com/tszmytka).
* Your contribution here.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import hudson.Extension;
import hudson.console.ConsoleNote;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import hudson.util.VersionNumber;
import jenkins.model.Jenkins;

import javax.annotation.Nonnull;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -33,8 +35,8 @@ public ShortlogActionCreator(LineIdentifier lineIdentifier, String eol) {
this.eol = eol.getBytes(UTF_8);
}

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

private ActionContext findLastActionBefore(File logFile, Collection<String> serializedActions, int shortlogLimit, boolean keepLinesWhole) {
private ActionContext findLastActionBefore(File logFile, Collection<String> serializedActions, int shortlogLimit, boolean keepLinesWhole, long reservedBytes) {
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(logFile))) {
final long shortlogStart = logFile.length() - shortlogLimit * 1024L;
final long shortlogStart = logFile.length() + reservedBytes - shortlogLimit * 1024L;
if (shortlogStart > 0) {
final byte[] buf = new byte[BUFFER_SIZE];
int read;
Expand Down Expand Up @@ -110,9 +112,10 @@ private int[] calculateBeginLength(byte[] buf, int startInBuff, int eolPos, bool

@Extension
public static class Listener extends RunListener<Run<?, ?>> {

@Override
public void onFinalized(Run<?, ?> run) {
super.onFinalized(run);
public void onCompleted(Run<?, ?> run, @Nonnull TaskListener listener) {
super.onCompleted(run, listener);
final List<ColorizedAction.Command> commands = Arrays.asList(ColorizedAction.Command.START, ColorizedAction.Command.STOP);
final Map<String, ColorizedAction> actions = run.getActions(ColorizedAction.class).stream()
.filter(a -> commands.contains(a.getCommand()))
Expand All @@ -127,16 +130,20 @@ public void onFinalized(Run<?, ?> run) {
if (!actions.isEmpty()) {
final File logFile = new File(run.getRootDir(), "log");
if (logFile.isFile()) {
final ShortlogActionCreator shortlogActionCreator = new ShortlogActionCreator(new LineIdentifier(), System.lineSeparator());
final String nl = System.lineSeparator();
final ShortlogActionCreator shortlogActionCreator = new ShortlogActionCreator(new LineIdentifier(), nl);
final String consoleTail = System.getProperty("hudson.consoleTailKB");
final boolean keepLinesWhole = Optional.ofNullable(System.getProperty(PROP_LINES_WHOLE))
.map(Boolean::parseBoolean)
.orElseGet(() -> Optional.ofNullable(Jenkins.getVersion()).orElse(LINES_WHOLE_SINCE_VERSION).isNewerThan(LINES_WHOLE_SINCE_VERSION));
// ensure all log entries are in log file
listener.getLogger().flush();
final ColorizedAction action = shortlogActionCreator.createActionForShortlog(
logFile,
actions,
consoleTail != null ? Integer.parseInt(consoleTail) : CONSOLE_TAIL_DEFAULT,
keepLinesWhole
keepLinesWhole,
Optional.ofNullable(run.getResult()).map(r -> 10 + r.toString().length() + nl.getBytes(UTF_8).length).orElse(0) // "Finished: " + result + new line
);
if (action != null) {
run.addAction(action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.junit.*;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.*;
import org.kohsuke.stapler.StaplerRequest;
import org.mockito.Mock;
Expand All @@ -35,6 +34,9 @@ public class AnsiColorBuildWrapperTest {
private static final String ESC = "\033";
private static final String CLR = ESC + "[2K";

@ClassRule
public static BuildWatcher buildWatcher = new BuildWatcher();

@SuppressWarnings("unused")
private enum CSI {
CUU("A", 1),
Expand Down Expand Up @@ -65,6 +67,12 @@ private enum CSI {
}
}

@Rule
public RestartableJenkinsRule jenkinsRule = new RestartableJenkinsRule();

@Rule
public LoggerRule logging = new LoggerRule().recordPackage(ConsoleNote.class, Level.FINE).record(ColorConsoleAnnotator.class, Level.FINER);

@Test
public void testGetColorMapNameNull() {
AnsiColorBuildWrapper instance = new AnsiColorBuildWrapper(null);
Expand All @@ -83,16 +91,9 @@ public void testDecorateLogger() {
assertThat(ansiColorBuildWrapper, instanceOf(AnsiColorBuildWrapper.class));
}

@ClassRule
public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule
public RestartableJenkinsRule story = new RestartableJenkinsRule();
@Rule
public LoggerRule logging = new LoggerRule().recordPackage(ConsoleNote.class, Level.FINE).record(ColorConsoleAnnotator.class, Level.FINER);

@Test
public void maven() {
story.then(r -> {
jenkinsRule.then(r -> {
FreeStyleProject p = r.createFreeStyleProject();
p.getBuildWrappersList().add(new AnsiColorBuildWrapper(null));
p.getBuildersList().add(new TestBuilder() {
Expand Down Expand Up @@ -130,7 +131,7 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen

@Test
public void testMultilineEscapeSequence() {
story.then(r -> {
jenkinsRule.then(r -> {
FreeStyleProject p = r.createFreeStyleProject();
p.getBuildWrappersList().add(new AnsiColorBuildWrapper(null));
p.getBuildersList().add(new TestBuilder() {
Expand Down Expand Up @@ -161,7 +162,7 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen

@Test
public void testDefaultForegroundBackground() {
story.then(r -> {
jenkinsRule.then(r -> {
FreeStyleProject p = r.createFreeStyleProject();
// The VGA ColorMap sets default foreground and background colors.
p.getBuildWrappersList().add(new AnsiColorBuildWrapper("vga"));
Expand Down Expand Up @@ -196,38 +197,34 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
@Issue("JENKINS-54133")
@Test
public void testWorkflowWrap() {
story.addStep(new Statement() {

@Override
public void evaluate() throws Throwable {
Assume.assumeTrue(!Functions.isWindows());
story.j.createSlave();
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node('!master') {\n"
+ " wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm']) {\n"
+ " sh(\"\"\"#!/bin/bash\n"
+ " printf 'The following word is supposed to be \\\\e[31mred\\\\e[0m\\\\n'\"\"\"\n"
+ " )\n"
+ " }\n"
+ "}"
, false
));
story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
StringWriter writer = new StringWriter();
assertTrue(p.getLastBuild().getLogText().writeHtmlTo(0L, writer) > 0);
String html = writer.toString();
assertTrue(
"Failed to match color attribute in following HTML log output:\n" + html,
html.replaceAll("<!--.+?-->", "").matches("(?s).*<span style=\"color: #CD0000;\">red</span>.*")
);
}
jenkinsRule.then(r -> {
Assume.assumeTrue(!Functions.isWindows());
jenkinsRule.j.createSlave();
WorkflowJob p = jenkinsRule.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node('!master') {\n"
+ " wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm']) {\n"
+ " sh(\"\"\"#!/bin/bash\n"
+ " printf 'The following word is supposed to be \\\\e[31mred\\\\e[0m\\\\n'\"\"\"\n"
+ " )\n"
+ " }\n"
+ "}"
, false
));
jenkinsRule.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
StringWriter writer = new StringWriter();
assertTrue(p.getLastBuild().getLogText().writeHtmlTo(0L, writer) > 0);
String html = writer.toString();
assertTrue(
"Failed to match color attribute in following HTML log output:\n" + html,
html.replaceAll("<!--.+?-->", "").matches("(?s).*<span style=\"color: #CD0000;\">red</span>.*")
);
});
}

@Test
public void testNonAscii() {
story.then(r -> {
jenkinsRule.then(r -> {
FreeStyleProject p = r.createFreeStyleProject();
p.getBuildWrappersList().add(new AnsiColorBuildWrapper(null));
p.getBuildersList().add(new TestBuilder() {
Expand Down Expand Up @@ -258,7 +255,7 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
@Issue("JENKINS-55139")
@Test
public void testTerraform() {
story.then(r -> {
jenkinsRule.then(r -> {
FreeStyleProject p = r.createFreeStyleProject();
p.getBuildWrappersList().add(new AnsiColorBuildWrapper(null));
p.getBuildersList().add(new TestBuilder() {
Expand Down Expand Up @@ -291,7 +288,7 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
@Issue("JENKINS-55139")
@Test
public void testRedundantResets() {
story.then(r -> {
jenkinsRule.then(r -> {
FreeStyleProject p = r.createFreeStyleProject();
p.getBuildWrappersList().add(new AnsiColorBuildWrapper(null));
p.getBuildersList().add(new TestBuilder() {
Expand Down Expand Up @@ -480,7 +477,7 @@ private static String sgr(int... sgrParam) {
}

private void assertCorrectOutput(Collection<String> expectedOutput, Collection<String> notExpectedOutput, Consumer<PrintStream> inputProvider) {
story.then(r -> {
jenkinsRule.then(r -> {
final String html = runBuildWithPlugin(r, inputProvider).replaceAll("<!--.+?-->", "");
expectedOutput.forEach(s -> assertThat(html, containsString(s)));
notExpectedOutput.forEach(s -> assertThat("Test failed for sequence: " + s.replace(ESC, "ESC"), html, not(containsString(s))));
Expand Down
Loading

0 comments on commit c4984f1

Please sign in to comment.