From 174cb50c9b9992e53976c1ec17b79eb5825c601c Mon Sep 17 00:00:00 2001 From: aysha-afrah26 Date: Tue, 21 Jul 2026 13:57:45 +0530 Subject: [PATCH] LOGBACK-992: do not delete the original file when compression fails Signed-off-by: aysha-afrah26 --- .../helper/CompressionStrategyBase.java | 6 ++ .../rolling/helper/GZCompressionStrategy.java | 2 + .../rolling/helper/XZCompressionStrategy.java | 2 + .../helper/ZipCompressionStrategy.java | 2 + .../CompressionStrategyFailureTest.java | 86 +++++++++++++++++++ 5 files changed, 98 insertions(+) create mode 100644 logback-core/src/test/java/ch/qos/logback/core/rolling/helper/CompressionStrategyFailureTest.java diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java index cd5affa92c..6de266eaf9 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java +++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java @@ -29,4 +29,10 @@ void createMissingTargetDirsIfNecessary(File file) { addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]"); } } + + void discardPartialCompressedFile(File file) { + if (file.exists() && !file.delete()) { + addWarn("Could not delete partially written compressed file [" + file.getAbsolutePath() + "]"); + } + } } diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java index f32fc03441..e978d42342 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java +++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java @@ -62,6 +62,8 @@ public void compress(String originalFileName, String compressedFileName, String addInfo("Done GZ compressing [" + file2gz + "] as [" + gzedFile + "]"); } catch (Exception e) { addStatus(new ErrorStatus("Error occurred while compressing [" + originalFileName + "] into [" + compressedFileName + "].", this, e)); + discardPartialCompressedFile(gzedFile); + return; } if (!file2gz.delete()) { diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java index f4fe6210b1..b9e38ae705 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java +++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java @@ -64,6 +64,8 @@ public void compress(String nameOfFile2xz, String nameOfxzedFile, String innerEn } } catch (Exception e) { addError("Error occurred while compressing [" + nameOfFile2xz + "] into [" + nameOfxzedFile + "].", e); + discardPartialCompressedFile(xzedFile); + return; } if (!file2xz.delete()) { diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java index d6b1c5ef5d..f96b967395 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java +++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java @@ -78,6 +78,8 @@ public void compress(String originalFileName, String compressedFileName, String addInfo("Done ZIP compressing [" + file2zip + "] as [" + zippedFile + "]"); } catch (Exception e) { addStatus(new ErrorStatus("Error occurred while compressing [" + originalFileName + "] into [" + compressedFileName + "].", this, e)); + discardPartialCompressedFile(zippedFile); + return; } if (!file2zip.delete()) { addStatus(new WarnStatus("Could not delete [" + originalFileName + "].", this)); diff --git a/logback-core/src/test/java/ch/qos/logback/core/rolling/helper/CompressionStrategyFailureTest.java b/logback-core/src/test/java/ch/qos/logback/core/rolling/helper/CompressionStrategyFailureTest.java new file mode 100644 index 0000000000..8ff3671c68 --- /dev/null +++ b/logback-core/src/test/java/ch/qos/logback/core/rolling/helper/CompressionStrategyFailureTest.java @@ -0,0 +1,86 @@ +/* + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2026, QOS.ch. All rights reserved. + * + * This program and the accompanying materials are dual-licensed under + * either the terms of the Eclipse Public License v2.0 as published by + * the Eclipse Foundation + * + * or (per the licensee's choosing) + * + * under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation. + */ +package ch.qos.logback.core.rolling.helper; + +import ch.qos.logback.core.Context; +import ch.qos.logback.core.ContextBase; +import ch.qos.logback.core.status.Status; +import ch.qos.logback.core.status.testUtil.StatusChecker; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import static ch.qos.logback.core.testUtil.CoreTestConstants.OUTPUT_DIR_PREFIX; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * When compression fails, the original (uncompressed) file must NOT be + * deleted, otherwise the rolled log data is lost with only a corrupt + * archive left behind. See LOGBACK-992. + */ +public class CompressionStrategyFailureTest { + + Context context = new ContextBase(); + File outputDir; + File originalFile; + File blockingFile; + + @BeforeEach + public void setUp() throws IOException { + outputDir = new File(OUTPUT_DIR_PREFIX + "compressFailure-" + System.nanoTime()); + assertTrue(outputDir.mkdirs()); + originalFile = new File(outputDir, "app.log"); + try (FileWriter fw = new FileWriter(originalFile)) { + fw.write("hello world\n"); + } + // plain file later used as the parent "directory" of the compression + // target, guaranteeing that opening the target stream fails + blockingFile = new File(outputDir, "blocking"); + try (FileWriter fw = new FileWriter(blockingFile)) { + fw.write("x"); + } + } + + private String failingTarget(String suffix) { + return new File(blockingFile, "app.log" + suffix).getPath(); + } + + @Test + public void originalFileSurvivesFailedGZCompression() { + GZCompressionStrategy gz = new GZCompressionStrategy(); + gz.setContext(context); + gz.compress(originalFile.getPath(), failingTarget(".gz"), null); + + StatusChecker checker = new StatusChecker(context); + checker.assertContainsMatch(Status.ERROR, "Error occurred while compressing"); + assertTrue(originalFile.exists(), "original file should survive a failed GZ compression"); + assertFalse(new File(failingTarget(".gz")).exists()); + } + + @Test + public void originalFileSurvivesFailedZipCompression() { + ZipCompressionStrategy zip = new ZipCompressionStrategy(); + zip.setContext(context); + zip.compress(originalFile.getPath(), failingTarget(".zip"), "app.log"); + + StatusChecker checker = new StatusChecker(context); + checker.assertContainsMatch(Status.ERROR, "Error occurred while compressing"); + assertTrue(originalFile.exists(), "original file should survive a failed ZIP compression"); + assertFalse(new File(failingTarget(".zip")).exists()); + } +}