Skip to content

Commit

Permalink
Changed the truncate summary to cut build logs from top (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
sarahdeitke authored Feb 16, 2025
1 parent 6194699 commit bfdc7df
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,7 @@ public Optional<String> getText() {
* @return Text, truncated to maxSize with truncation message if appropriate.
*/
public Optional<String> getText(final int maxSize) {
return Optional.ofNullable(text)
.map(s -> new TruncatedString.Builder()
.setChunkOnNewlines()
.setTruncateStart()
.addText(s.toString())
.build()
.buildByChars(maxSize));
return Optional.ofNullable(text).map(s -> s.build(maxSize));
}

public List<ChecksAnnotation> getChecksAnnotations() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
class FlowExecutionAnalyzer {
private static final Logger LOGGER = Logger.getLogger(FlowExecutionAnalyzer.class.getName());
private static final String TRUNCATED_MESSAGE = "\n\nOutput truncated.";
private static final String TRUNCATED_MESSAGE_BUILD_LOG = "Build log truncated.\n\n";
private static final int MAX_MESSAGE_SIZE_TO_CHECKS_API = 65_535;

private final Run<?, ?> run;
private final FlowExecution execution;
Expand Down Expand Up @@ -132,9 +134,12 @@ private Pair<String, String> processErrorOrWarningRow(final FlowGraphTable.Row r
nodeTextBuilder.append(String.format("**Error**: *%s*", displayName));
nodeSummaryBuilder.append(String.format("```%n%s%n```%n", displayName));
if (!suppressLogs) {
String log = getLog(flowNode);
// -2 for "\n\n" at the end of the summary and -30 for buffer
String logTemplate = "<details>%n<summary>Build log</summary>%n%n```%n%s%n```%n</details>";
int maxMessageSize = MAX_MESSAGE_SIZE_TO_CHECKS_API - nodeSummaryBuilder.length() - logTemplate.length() - 32;
String log = getLog(flowNode, maxMessageSize);
if (StringUtils.isNotBlank(log)) {
nodeSummaryBuilder.append(String.format("<details>%n<summary>Build log</summary>%n%n```%n%s%n```%n</details>", log));
nodeSummaryBuilder.append(String.format(logTemplate, log));
}
}
}
Expand Down Expand Up @@ -197,7 +202,7 @@ private String getPotentialTitle(final FlowNode flowNode, final ErrorAction erro
}

@CheckForNull
private static String getLog(final FlowNode flowNode) {
private static String getLog(final FlowNode flowNode, final int maxMessageSize) {
LogAction logAction = flowNode.getAction(LogAction.class);
if (logAction == null) {
return null;
Expand All @@ -209,7 +214,15 @@ private static String getLog(final FlowNode flowNode) {

String outputString = out.toString(StandardCharsets.UTF_8);
// strip ansi color codes
return outputString.replaceAll("\u001B\\[[;\\d]*m", "");
String log = outputString.replaceAll("\u001B\\[[;\\d]*m", "");

return new TruncatedString.Builder()
.setChunkOnNewlines()
.setTruncateStart()
.withTruncationText(TRUNCATED_MESSAGE_BUILD_LOG)
.addText(log)
.build()
.build(maxMessageSize);
}
catch (IOException e) {
LOGGER.log(Level.WARNING, String.format("Failed to extract logs for step '%s'",
Expand Down
43 changes: 6 additions & 37 deletions src/test/java/io/jenkins/plugins/checks/api/ChecksOutputTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ void shouldBuildCorrectlyWithAllFields() {
.addImage(images.get(1))
.build();

ChecksOutputAssert.assertThat(checksOutput)
.hasTitle(Optional.of(TITLE))
.hasSummary(Optional.of(SUMMARY))
.hasText(Optional.of(TEXT));
assertThat(checksOutput.getTitle()).isEqualTo(Optional.of(TITLE));
assertThat(checksOutput.getSummary()).isEqualTo(Optional.of(SUMMARY));
assertThat(checksOutput.getText()).isEqualTo(Optional.of(TEXT));
assertThat(checksOutput.getChecksAnnotations())
.usingFieldByFieldElementComparator()
.containsExactlyInAnyOrderElementsOf(annotations);
Expand Down Expand Up @@ -85,10 +84,9 @@ void shouldCopyConstructCorrectly() {
.build();

ChecksOutput copied = new ChecksOutput(checksOutput);
ChecksOutputAssert.assertThat(copied)
.hasTitle(Optional.of(TITLE))
.hasSummary(Optional.of(SUMMARY))
.hasText(Optional.of(TEXT));
assertThat(copied.getTitle()).isEqualTo(Optional.of(TITLE));
assertThat(copied.getSummary()).isEqualTo(Optional.of(SUMMARY));
assertThat(copied.getText()).isEqualTo(Optional.of(TEXT));
assertThat(copied.getChecksAnnotations())
.usingFieldByFieldElementComparator()
.containsExactlyInAnyOrderElementsOf(annotations);
Expand All @@ -97,35 +95,6 @@ void shouldCopyConstructCorrectly() {
.containsExactlyInAnyOrderElementsOf(images);
}

@Test
void shouldTruncateTextFromStart() {
String longText = "This is the beginning.\n" + "Middle part.\n".repeat(10) + "This is the end.\n";
ChecksOutput checksOutput = new ChecksOutputBuilder()
.withText(longText)
.build();

String truncated = checksOutput.getText(75).orElse("");

assertThat(truncated)
.startsWith("Output truncated.")
.endsWith("This is the end.\n");
assertThat(truncated.length()).isLessThanOrEqualTo(75);
}

@Test
void shouldNotTruncateShortText() {
String shortText = "This is a short text that should not be truncated.";
ChecksOutput checksOutput = new ChecksOutputBuilder()
.withText(shortText)
.build();

String result = checksOutput.getText(100).orElse("");

assertThat(result)
.isEqualTo(shortText)
.doesNotContain("Output truncated.");
}

private List<ChecksAnnotation> createAnnotations() {
final ChecksAnnotationBuilder builder = new ChecksAnnotationBuilder()
.withPath("src/main/java/1.java")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,61 @@ public void shouldPublishStageDetailsWithoutLogsIfRequested() {
});
}

/**
* Test that log messages are properly truncated when they exceed the maximum size limit.
*/
@Test
public void shouldTruncateLogsWhenExceedingMaxSize() {
getProperties().setApplicable(true);
getProperties().setSkipped(false);
getProperties().setName("Test Status");
getProperties().setSuppressLogs(false);
WorkflowJob job = createPipeline();

// Create a pipeline that generates a large log output
job.setDefinition(new CpsFlowDefinition(""
+ "node {\n"
+ " stage('Large Log Stage') {\n"
+ " // Generate a large log using Jenkins' built-in commands\n"
+ " def logContent = (1..1000).collect { i ->\n"
+ " \"Line ${i}: This is a very long log line that will be repeated many times to test truncation. Adding some extra system information here.\"\n"
+ " }.join('\\n')\n"
+ " // Use writeFile and bat/sh based on platform\n"
+ " writeFile file: 'large_log.txt', text: logContent\n"
+ " if (isUnix()) {\n"
+ " sh 'cat large_log.txt && exit 1'\n"
+ " } else {\n"
+ " bat 'type large_log.txt && exit /b 1'\n"
+ " }\n"
+ " error('Pipeline failed with large logs')\n"
+ " }\n"
+ "}", true));

buildWithResult(job, Result.FAILURE);

List<ChecksDetails> checksDetails = getFactory().getPublishedChecks();

// Get the final check details which should contain the truncated logs
ChecksDetails details = checksDetails.get(checksDetails.size() - 1);
assertThat(details.getStatus()).isEqualTo(ChecksStatus.COMPLETED);
assertThat(details.getConclusion()).isEqualTo(ChecksConclusion.FAILURE);
assertThat(details.getOutput()).isPresent().get().satisfies(output -> {
assertThat(output.getSummary()).isPresent().get().satisfies(summary -> {
// Verify the log section exists and is truncated
assertThat(summary).contains("<details>");
assertThat(summary).contains("</details>");
assertThat(summary).contains("Build log");
assertThat(summary).contains("Build log truncated.");
assertThat(summary).doesNotContain("Line 1:"); // Should be truncated from the start
assertThat(summary).contains("exit"); // Should see the exit command at the end
// Verify the truncation message appears at the start of the log section
assertThat(summary).matches(Pattern.compile(".*<summary>Build log</summary>\\s+\\n```\\s*\\nBuild log truncated.\\n\\n.*", Pattern.DOTALL));
// Verify the total size is within limits
assertThat(summary.length()).isLessThanOrEqualTo(65_535);
});
});
}

/**
* Validates that a simple successful pipeline works.
*/
Expand Down

0 comments on commit bfdc7df

Please sign in to comment.