From 042fe8d8cf9c7c867583121085b9d17dc78dd423 Mon Sep 17 00:00:00 2001 From: James Perkins Date: Thu, 27 Apr 2017 15:13:39 -0700 Subject: [PATCH] [LOGMGR-30] Add ability to compress files when rotated if the suffix ends with .zip or .gz. --- .../handlers/PeriodicRotatingFileHandler.java | 42 +-- .../PeriodicSizeRotatingFileHandler.java | 25 +- .../handlers/SizeRotatingFileHandler.java | 57 +--- .../logmanager/handlers/SuffixRotator.java | 252 ++++++++++++++++++ .../handlers/AbstractHandlerTest.java | 53 ++++ .../PeriodicRotatingFileHandlerTests.java | 62 +++++ .../PeriodicSizeRotatingFileHandlerTests.java | 72 ++++- .../SizeRotatingFileHandlerTests.java | 50 ++++ 8 files changed, 530 insertions(+), 83 deletions(-) create mode 100644 src/main/java/org/jboss/logmanager/handlers/SuffixRotator.java diff --git a/src/main/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandler.java b/src/main/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandler.java index 5d112344..02d45e9e 100644 --- a/src/main/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandler.java +++ b/src/main/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandler.java @@ -19,22 +19,17 @@ package org.jboss.logmanager.handlers; -import org.jboss.logmanager.ExtLogRecord; - +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Calendar; +import java.util.Date; import java.util.TimeZone; -import java.io.File; -import java.io.FileNotFoundException; - import java.util.logging.ErrorManager; +import org.jboss.logmanager.ExtLogRecord; + /** * A file handler which rotates the log at a preset time interval. The interval is determined by the content of the * suffix string which is passed in to {@link #setSuffix(String)}. @@ -46,6 +41,7 @@ public class PeriodicRotatingFileHandler extends FileHandler { private Period period = Period.NEVER; private long nextRollover = Long.MAX_VALUE; private TimeZone timeZone = TimeZone.getDefault(); + private SuffixRotator suffixRotator = SuffixRotator.EMPTY; /** * Construct a new instance with no formatter and no output file. @@ -124,17 +120,22 @@ protected void preWrite(final ExtLogRecord record) { /** * Set the suffix string. The string is in a format which can be understood by {@link java.text.SimpleDateFormat}. * The period of the rotation is automatically calculated based on the suffix. + *

+ * If the suffix ends with {@code .gz} or {@code .zip} the file will be compressed on rotation. + *

* * @param suffix the suffix * @throws IllegalArgumentException if the suffix is not valid */ public void setSuffix(String suffix) throws IllegalArgumentException { - final SimpleDateFormat format = new SimpleDateFormat(suffix); + final SuffixRotator suffixRotator = SuffixRotator.parse(suffix); + final String dateSuffix = suffixRotator.getDatePattern(); + final SimpleDateFormat format = new SimpleDateFormat(dateSuffix); format.setTimeZone(timeZone); - final int len = suffix.length(); + final int len = dateSuffix.length(); Period period = Period.NEVER; for (int i = 0; i < len; i ++) { - switch (suffix.charAt(i)) { + switch (dateSuffix.charAt(i)) { case 'y': period = min(period, Period.YEAR); break; case 'M': period = min(period, Period.MONTH); break; case 'w': @@ -149,7 +150,7 @@ public void setSuffix(String suffix) throws IllegalArgumentException { case 'K': case 'h': period = min(period, Period.HOUR); break; case 'm': period = min(period, Period.MINUTE); break; - case '\'': while (suffix.charAt(++i) != '\''); break; + case '\'': while (dateSuffix.charAt(++i) != '\''); break; case 's': case 'S': throw new IllegalArgumentException("Rotating by second or millisecond is not supported"); } @@ -157,6 +158,7 @@ public void setSuffix(String suffix) throws IllegalArgumentException { synchronized (outputLock) { this.format = format; this.period = period; + this.suffixRotator = suffixRotator; final long now; final File file = getFile(); if (file != null && file.lastModified() > 0) { @@ -177,14 +179,22 @@ protected final String getNextSuffix() { return nextSuffix; } + /** + * Returns the file rotator for this handler. + * + * @return the file rotator + */ + SuffixRotator getSuffixRotator() { + return suffixRotator; + } + private void rollOver() { try { final File file = getFile(); // first, close the original file (some OSes won't let you move/rename a file that is open) setFile(null); // next, rotate it - final Path target = Paths.get(file.getAbsolutePath() + nextSuffix); - Files.move(file.toPath(), target, StandardCopyOption.REPLACE_EXISTING); + suffixRotator.rotate(file.toPath(), nextSuffix); // start new file setFile(file); } catch (IOException e) { diff --git a/src/main/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandler.java b/src/main/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandler.java index 02fd5e69..10e93bcf 100644 --- a/src/main/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandler.java +++ b/src/main/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandler.java @@ -23,10 +23,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.util.logging.ErrorManager; import org.jboss.logmanager.ExtLogRecord; @@ -156,7 +152,11 @@ public void setFile(final File file) throws FileNotFoundException { // Check for a rotate if (rotateOnBoot && maxBackupIndex > 0 && file != null && file.exists() && file.length() > 0L) { try { - rotate(file); + final String suffix = getNextSuffix(); + final SuffixRotator suffixRotator = getSuffixRotator(); + if (suffixRotator != SuffixRotator.EMPTY && suffix != null) { + suffixRotator.rotate(file.toPath(), suffix, maxBackupIndex); + } } catch (IOException e) { throw new RuntimeException(e); } @@ -229,7 +229,7 @@ protected void preWrite(final ExtLogRecord record) { } // close the old file. setFile(null); - rotate(file); + getSuffixRotator().rotate(file.toPath(), getNextSuffix(), maxBackupIndex); // start with new file. setFile(file); } catch (IOException e) { @@ -237,17 +237,4 @@ protected void preWrite(final ExtLogRecord record) { } } } - - private void rotate(final File file) throws IOException { - final Path fileWithSuffix = Paths.get(file.getAbsolutePath() + getNextSuffix()); - Files.deleteIfExists(Paths.get(fileWithSuffix + "." + maxBackupIndex)); - for (int i = maxBackupIndex - 1; i >= 1; i--) { - final Path src = Paths.get(fileWithSuffix + "." + i); - if (Files.exists(src)) { - final Path target = Paths.get(fileWithSuffix + "." + (i + 1)); - Files.move(src, target, StandardCopyOption.REPLACE_EXISTING); - } - } - Files.move(file.toPath(), Paths.get(fileWithSuffix + ".1"), StandardCopyOption.REPLACE_EXISTING); - } } diff --git a/src/main/java/org/jboss/logmanager/handlers/SizeRotatingFileHandler.java b/src/main/java/org/jboss/logmanager/handlers/SizeRotatingFileHandler.java index c70b6221..40fd56f2 100644 --- a/src/main/java/org/jboss/logmanager/handlers/SizeRotatingFileHandler.java +++ b/src/main/java/org/jboss/logmanager/handlers/SizeRotatingFileHandler.java @@ -25,12 +25,6 @@ import java.io.FileNotFoundException; import org.jboss.logmanager.ExtLogRecord; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.logging.ErrorManager; public class SizeRotatingFileHandler extends FileHandler { @@ -39,7 +33,7 @@ public class SizeRotatingFileHandler extends FileHandler { private int maxBackupIndex = 1; private CountingOutputStream outputStream; private boolean rotateOnBoot; - private String suffix; + private SuffixRotator suffixRotator = SuffixRotator.EMPTY; /** * Construct a new instance with no formatter and no output file. @@ -146,7 +140,7 @@ public void setFile(final File file) throws FileNotFoundException { // Check for a rotate if (rotateOnBoot && maxBackupIndex > 0 && file != null && file.exists() && file.length() > 0L) { try { - rotate(file); + suffixRotator.rotate(file.toPath(), maxBackupIndex); } catch (IOException e) { throw new RuntimeException(e); } @@ -211,15 +205,22 @@ public void setMaxBackupIndex(final int maxBackupIndex) { * @return the suffix or {@code null} if no suffix should be used */ public String getSuffix() { - return suffix; + if (suffixRotator == SuffixRotator.EMPTY) { + return null; + } + return suffixRotator.toString(); } /** * Sets the suffix to be appended to the file name during the file rotation. The suffix does not play a role in * determining when the file should be rotated. *

- * The suffix must be a string understood by the {@link java.text.SimpleDateFormat}. + * The suffix must be a string understood by the {@link java.text.SimpleDateFormat}. Optionally the suffix can end + * with {@code .gz} or {@code .zip} which will compress the file on rotation. *

+ *

+ * If the suffix ends with {@code .gz} or {@code .zip} the file will be compressed on rotation. + *

* Note: Any files rotated with the suffix appended will not be deleted. The {@link #setMaxBackupIndex(int) * maxBackupIndex} is not used for files with a suffix. * @@ -228,7 +229,7 @@ public String getSuffix() { public void setSuffix(final String suffix) { checkAccess(this); synchronized (outputLock) { - this.suffix = suffix; + this.suffixRotator = SuffixRotator.parse(suffix); } } @@ -245,7 +246,7 @@ protected void preWrite(final ExtLogRecord record) { } // close the old file. setFile(null); - rotate(file); + suffixRotator.rotate(file.toPath(), maxBackupIndex); // start with new file. setFile(file); } catch (IOException e) { @@ -253,36 +254,4 @@ protected void preWrite(final ExtLogRecord record) { } } } - - private void rotate(final File file) throws IOException { - if (suffix == null) { - // rotate. First, drop the max file (if any), then move each file to the next higher slot. - Files.deleteIfExists(Paths.get(file.getAbsolutePath() + "." + maxBackupIndex)); - for (int i = maxBackupIndex - 1; i >= 1; i--) { - final Path src = Paths.get(file.getAbsolutePath() + "." + i); - if (Files.exists(src)) { - final Path target = Paths.get(file.getAbsolutePath() + "." + (i + 1)); - Files.move(src, target, StandardCopyOption.REPLACE_EXISTING); - } - } - Files.move(file.toPath(), Paths.get(file.getAbsolutePath() + ".1"), StandardCopyOption.REPLACE_EXISTING); - } else { - // This is not efficient, but performance risks were noted on the setSuffix() method - final String suffix = new SimpleDateFormat(this.suffix).format(new Date()); - // Create the file name - final String newBaseFilename = file.getAbsolutePath() + suffix; - - // rotate. First, drop the max file (if any), then move each file to the next higher slot. - Files.deleteIfExists(Paths.get(newBaseFilename + "." + maxBackupIndex)); - for (int i = maxBackupIndex - 1; i >= 1; i--) { - final Path src = Paths.get(newBaseFilename + "." + i); - if (Files.exists(src)) { - final Path target = Paths.get(newBaseFilename + "." + (i + 1)); - Files.move(src, target, StandardCopyOption.REPLACE_EXISTING); - } - } - // Rename the current file - Files.move(file.toPath(), Paths.get(newBaseFilename + ".1"), StandardCopyOption.REPLACE_EXISTING); - } - } } diff --git a/src/main/java/org/jboss/logmanager/handlers/SuffixRotator.java b/src/main/java/org/jboss/logmanager/handlers/SuffixRotator.java new file mode 100644 index 00000000..5a4a7674 --- /dev/null +++ b/src/main/java/org/jboss/logmanager/handlers/SuffixRotator.java @@ -0,0 +1,252 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2017 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.logmanager.handlers; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.zip.GZIPOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * A utility for rotating files based on a files suffix. + * + * @author James R. Perkins + */ +class SuffixRotator { + + /** + * The compression type for the rotation + */ + public enum CompressionType { + NONE, + GZIP, + ZIP + } + + /** + * An empty rotation suffix. + */ + static final SuffixRotator EMPTY = new SuffixRotator("", "", "", CompressionType.NONE); + + private final String originalSuffix; + private final String datePattern; + private final SimpleDateFormat formatter; + private final String compressionSuffix; + private final CompressionType compressionType; + + private SuffixRotator(final String originalSuffix, final String datePattern, final String compressionSuffix, final CompressionType compressionType) { + this.originalSuffix = originalSuffix; + this.datePattern = datePattern; + this.compressionSuffix = compressionSuffix; + this.compressionType = compressionType; + if (datePattern.isEmpty()) { + formatter = null; + } else { + formatter = new SimpleDateFormat(datePattern); + } + } + + /** + * Parses a suffix into possible parts for rotation. + * + * @param suffix the suffix to parse + * + * @return the file rotator used to determine the suffix parts and rotate the file. + */ + static SuffixRotator parse(final String suffix) { + if (suffix == null || suffix.isEmpty()) { + return EMPTY; + } + // Check the if the suffix contains a compression suffix + String compressionSuffix = ""; + String datePattern = ""; + CompressionType compressionType = CompressionType.NONE; + final String lSuffix = suffix.toLowerCase(Locale.ROOT); + int compressionIndex = lSuffix.indexOf(".gz"); + if (compressionIndex != -1) { + compressionSuffix = suffix.substring(compressionIndex); + datePattern = suffix.substring(0, compressionIndex); + compressionType = SuffixRotator.CompressionType.GZIP; + } else { + compressionIndex = lSuffix.indexOf(".zip"); + if (compressionIndex != -1) { + compressionSuffix = suffix.substring(compressionIndex); + datePattern = suffix.substring(0, compressionIndex); + compressionType = SuffixRotator.CompressionType.ZIP; + } + } + if (compressionSuffix.isEmpty() && datePattern.isEmpty()) { + return new SuffixRotator(suffix, suffix, "", CompressionType.NONE); + } + return new SuffixRotator(suffix, datePattern, compressionSuffix, compressionType); + } + + /** + * The {@linkplain java.text.SimpleDateFormat date format pattern} for the suffix or an empty + * {@linkplain String string}. + * + * @return the date pattern or an empty string + */ + String getDatePattern() { + return datePattern; + } + + /** + * The compression suffix or an empty {@linkplain String string} + * + * @return the compression suffix or an empty string + */ + @SuppressWarnings("unused") + String getCompressionSuffix() { + return compressionSuffix; + } + + /** + * The compression type. + * + * @return the compression type + */ + @SuppressWarnings("unused") + CompressionType getCompressionType() { + return compressionType; + } + + /** + * Rotates the file to a new file appending the suffix to the target. + *

+ * The compression suffix will automatically be appended to target file if compression is being used. If compression + * is not being used the file is just moved replacing the target file if it already exists. + *

+ * + * @param source the file to be rotated + * @param suffix the suffix to append to the rotated file. + * + * @throws IOException if an error occurs rotating the file + */ + void rotate(final Path source, final String suffix) throws IOException { + final Path target = Paths.get(source + suffix + compressionSuffix); + if (compressionType == CompressionType.GZIP) { + archiveGzip(source, target); + } else if (compressionType == CompressionType.ZIP) { + archiveZip(source, target); + } else { + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); + } + } + + /** + * Rotates the file to a new file appending the suffix to the target. If a date suffix was specified the suffix + * will be added before the index or compression suffix. The current date will be used for the suffix. + *

+ * If the {@code maxBackupIndex} is greater than 0 previously rotated files will be moved to an numerically + * incremented target. The compression suffix, if required, will be appended to this indexed file name. + *

+ * + * @param source the file to be rotated + * @param maxBackupIndex the number of backups to keep + * + * @throws IOException if an error occurs rotating the file + */ + void rotate(final Path source, final int maxBackupIndex) throws IOException { + if (formatter == null) { + rotate(source, "", maxBackupIndex); + } else { + final String suffix; + synchronized (formatter) { + suffix = formatter.format(new Date()); + } + rotate(source, suffix, maxBackupIndex); + } + } + + /** + * Rotates the file to a new file appending the suffix to the target. + *

+ * If the {@code maxBackupIndex} is greater than 0 previously rotated files will be moved to an numerically + * incremented target. The compression suffix, if required, will be appended to this indexed file name. + *

+ * + * @param source the file to be rotated + * @param suffix the optional suffix to append to the file before the index and optional compression suffix + * @param maxBackupIndex the number of backups to keep + * + * @throws IOException if an error occurs rotating the file + */ + void rotate(final Path source, final String suffix, final int maxBackupIndex) throws IOException { + if (maxBackupIndex > 0) { + final String rotationSuffix = (suffix == null ? "" : suffix); + final String fileWithSuffix = source.toAbsolutePath() + rotationSuffix; + Files.deleteIfExists(Paths.get(fileWithSuffix + "." + maxBackupIndex + compressionSuffix)); + for (int i = maxBackupIndex - 1; i >= 1; i--) { + final Path src = Paths.get(fileWithSuffix + "." + i + compressionSuffix); + if (Files.exists(src)) { + final Path target = Paths.get(fileWithSuffix + "." + (i + 1) + compressionSuffix); + Files.move(src, target, StandardCopyOption.REPLACE_EXISTING); + } + } + rotate(source, rotationSuffix + ".1"); + } else if (suffix != null && !suffix.isEmpty()) { + rotate(source, suffix); + } + } + + @Override + public String toString() { + return originalSuffix; + } + + + private static void archiveGzip(final Path source, final Path target) throws IOException { + final byte[] buff = new byte[512]; + try (final GZIPOutputStream out = new GZIPOutputStream(Files.newOutputStream(target), true)) { + try (final InputStream in = Files.newInputStream(source)) { + int len; + while ((len = in.read(buff)) != -1) { + out.write(buff, 0, len); + } + } + out.finish(); + } + } + + private static void archiveZip(final Path source, final Path target) throws IOException { + final byte[] buff = new byte[512]; + try (final ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(target), StandardCharsets.UTF_8)) { + final ZipEntry entry = new ZipEntry(source.getFileName().toString()); + out.putNextEntry(entry); + try (final InputStream in = Files.newInputStream(source)) { + int len; + while ((len = in.read(buff)) != -1) { + out.write(buff, 0, len); + } + } + out.closeEntry(); + } + } +} diff --git a/src/test/java/org/jboss/logmanager/handlers/AbstractHandlerTest.java b/src/test/java/org/jboss/logmanager/handlers/AbstractHandlerTest.java index b46b9cd0..7e8a5222 100644 --- a/src/test/java/org/jboss/logmanager/handlers/AbstractHandlerTest.java +++ b/src/test/java/org/jboss/logmanager/handlers/AbstractHandlerTest.java @@ -19,12 +19,25 @@ package org.jboss.logmanager.handlers; +import java.io.BufferedReader; import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.zip.GZIPInputStream; import org.jboss.logmanager.ExtHandler; import org.jboss.logmanager.ExtLogRecord; import org.jboss.logmanager.formatters.PatternFormatter; import org.junit.After; +import org.junit.Assert; import org.junit.Before; /** @@ -107,4 +120,44 @@ protected ExtLogRecord createLogRecord(final org.jboss.logmanager.Level level, f protected ExtLogRecord createLogRecord(final org.jboss.logmanager.Level level, final String format, final Object... args) { return new ExtLogRecord(level, String.format(format, args), getClass().getName()); } + + /** + * Validates that at least one line of the GZIP'd file contains the expected text. + * + * @param path the path to the GZIP file + * @param expectedContains the expected text + * + * @throws IOException if an error occurs while reading the GZIP file + */ + static void validateGzipContents(final Path path, final String expectedContains) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(Files.newInputStream(path))))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(expectedContains)) { + return; + } + } + } + Assert.fail(String.format("GZIP file %s missing contents: %s", path, expectedContains)); + } + + /** + * Validates that the ZIP file contains the expected file, the expected file is not empty and that the first line + * contains the expected text. + * + * @param path the path to the zip file + * @param expectedFileName the name of the file inside the zip file + * @param expectedContains the expected text + * + * @throws IOException if an error occurs reading the zip file + */ + static void validateZipContents(final Path path, final String expectedFileName, final String expectedContains) throws IOException { + try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + path.toUri().toASCIIString()), Collections.singletonMap("create", "true"))) { + final Path file = zipFs.getPath(zipFs.getSeparator(), expectedFileName); + Assert.assertTrue(String.format("Expected file %s not found.", expectedFileName), Files.exists(file)); + final List lines = Files.readAllLines(file, StandardCharsets.UTF_8); + Assert.assertFalse(String.format("File %s appears to be empty in zip file %s.", expectedFileName, path), lines.isEmpty()); + Assert.assertTrue(String.format("ZIP file %s missing contents: %s", path, expectedContains), lines.get(0).contains(expectedContains)); + } + } } diff --git a/src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java b/src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java index 20c70693..8fa996f4 100644 --- a/src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java +++ b/src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java @@ -25,6 +25,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.text.SimpleDateFormat; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.List; @@ -79,6 +81,16 @@ public void testOverwriteRotate() throws Exception { testRotate(cal, rotatedFile); } + @Test + public void testArchiveRotateGzip() throws Exception { + testArchiveRotate(".gz"); + } + + @Test + public void testArchiveRotateZip() throws Exception { + testArchiveRotate(".zip"); + } + private void testRotate(final Calendar cal, final Path rotatedFile) throws Exception { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); @@ -117,4 +129,54 @@ record = createLogRecord(Level.INFO, "Date: %s", nextDate); Assert.assertEquals("More than 1 line found", 1, lines.size()); Assert.assertTrue("Expected the line to contain the date: " + currentDate, lines.get(0).contains(currentDate)); } + + private void testArchiveRotate(final String archiveSuffix) throws Exception { + final String rotationFormat = ".dd"; + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + final DateTimeFormatter rotateFormatter = DateTimeFormatter.ofPattern(rotationFormat); + ZonedDateTime date = ZonedDateTime.now(); + + handler.setSuffix(rotationFormat + archiveSuffix); + + final String currentDate = formatter.format(date); + final String firstDateSuffix = rotateFormatter.format(date); + + // Create a log message to be logged + ExtLogRecord record = createLogRecord(Level.INFO, "Date: %s", currentDate); + handler.publish(record); + + // Create a new record, increment the day by one and validate + date = date.plusDays(1); + final String secondDateSuffix = rotateFormatter.format(date); + final String nextDate = formatter.format(date); + record = createLogRecord(Level.INFO, "Date: %s", nextDate); + record.setMillis(date.toInstant().toEpochMilli()); + handler.publish(record); + + // Create a new record, increment the day by one and validate + date = date.plusDays(1); + final String thirdDay = formatter.format(date); + record = createLogRecord(Level.INFO, "Date: %s", thirdDay); + record.setMillis(date.toInstant().toEpochMilli()); + handler.publish(record); + + // There should be three files + final Path logDir = BASE_LOG_DIR.toPath(); + final Path rotated1 = logDir.resolve(FILENAME + firstDateSuffix + archiveSuffix); + final Path rotated2 = logDir.resolve(FILENAME + secondDateSuffix + archiveSuffix); + Assert.assertTrue("Missing file " + logFile, Files.exists(logFile)); + Assert.assertTrue("Missing rotated file " + rotated1, Files.exists(rotated1)); + Assert.assertTrue("Missing rotated file " + rotated2, Files.exists(rotated2)); + + // Validate the files are not empty and the compressed file contains at least one log record + if (archiveSuffix.endsWith(".gz")) { + validateGzipContents(rotated1, "Date: " + currentDate); + validateGzipContents(rotated2, "Date: " + nextDate); + } else if (archiveSuffix.endsWith(".zip")) { + validateZipContents(rotated1, logFile.getFileName().toString(), "Date: " + currentDate); + validateZipContents(rotated2, logFile.getFileName().toString(), "Date: " + nextDate); + } else { + Assert.fail("Unknown archive suffix: " + archiveSuffix); + } + } } diff --git a/src/test/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandlerTests.java b/src/test/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandlerTests.java index df8c7bbd..3f4a29f1 100644 --- a/src/test/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandlerTests.java +++ b/src/test/java/org/jboss/logmanager/handlers/PeriodicSizeRotatingFileHandlerTests.java @@ -20,17 +20,21 @@ package org.jboss.logmanager.handlers; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.jboss.logmanager.ExtLogRecord; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import java.util.List; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; /** * @author James R. Perkins @@ -174,6 +178,66 @@ public void testPeriodicAndSizeRotate() throws Exception { } } + @Test + public void testArchiveRotateGzip() throws Exception { + testArchiveRotate(".yyyy-MM-dd", ".gz"); + } + + @Test + public void testArchiveRotateZip() throws Exception { + testArchiveRotate(".yyyy-MM-dd", ".zip"); + } + + @Test + public void testArchiveRotateSizeOnlyGzip() throws Exception { + testArchiveRotate(null, ".gz"); + } + + @Test + public void testArchiveRotateSizeOnlyZip() throws Exception { + testArchiveRotate(null,".zip"); + } + + private void testArchiveRotate(final String dateSuffix, final String archiveSuffix) throws Exception { + final String currentDate = dateSuffix == null ? "" : LocalDate.now().format(DateTimeFormatter.ofPattern(dateSuffix)); + PeriodicSizeRotatingFileHandler handler = new PeriodicSizeRotatingFileHandler(); + configureHandlerDefaults(handler); + handler.setRotateSize(1024L); + handler.setMaxBackupIndex(2); + handler.setFile(logFile); + handler.setSuffix((dateSuffix == null ? "" : dateSuffix) + archiveSuffix); + + // Allow a few rotates + for (int i = 0; i < 100; i++) { + handler.publish(createLogRecord("Test message: %d", i)); + } + + handler.close(); + + // We should end up with 3 files, 2 rotated and the default log + final Path logDir = BASE_LOG_DIR.toPath(); + final Path path1 = logDir.resolve(FILENAME + currentDate + ".1" + archiveSuffix); + final Path path2 = logDir.resolve(FILENAME + currentDate + ".2" + archiveSuffix); + Assert.assertTrue(logFile.exists()); + Assert.assertTrue(Files.exists(path1)); + Assert.assertTrue(Files.exists(path2)); + + // Validate the files are not empty and the compressed file contains at least one log record + if (archiveSuffix.endsWith(".gz")) { + validateGzipContents(path1, "Test message:"); + validateGzipContents(path2, "Test message:"); + } else if (archiveSuffix.endsWith(".zip")) { + validateZipContents(path1, logFile.getName(), "Test message:"); + validateZipContents(path2, logFile.getName(), "Test message:"); + } else { + Assert.fail("Unknown archive suffix: " + archiveSuffix); + } + + // Clean up files + Files.deleteIfExists(path1); + Files.deleteIfExists(path2); + } + private void testPeriodicAndSizeRotate0(int handlerPeriod, int logMessagePeriod, boolean testSize) throws Exception { int logCount = 1; if (testSize) { diff --git a/src/test/java/org/jboss/logmanager/handlers/SizeRotatingFileHandlerTests.java b/src/test/java/org/jboss/logmanager/handlers/SizeRotatingFileHandlerTests.java index ee0debf5..035504bf 100644 --- a/src/test/java/org/jboss/logmanager/handlers/SizeRotatingFileHandlerTests.java +++ b/src/test/java/org/jboss/logmanager/handlers/SizeRotatingFileHandlerTests.java @@ -20,6 +20,8 @@ package org.jboss.logmanager.handlers; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; @@ -146,4 +148,52 @@ public void testBootRotate() throws Exception { // Clean up files rotatedFile.delete(); } + @Test + public void testArchiveRotateGzip() throws Exception { + testArchiveRotate(".gz"); + } + + @Test + public void testArchiveRotateZip() throws Exception { + testArchiveRotate(".zip"); + } + + private void testArchiveRotate(final String archiveSuffix) throws Exception { + final SizeRotatingFileHandler handler = new SizeRotatingFileHandler(); + configureHandlerDefaults(handler); + handler.setRotateSize(1024L); + handler.setMaxBackupIndex(2); + handler.setFile(logFile); + handler.setSuffix(archiveSuffix); + + // Allow a few rotates + for (int i = 0; i < 100; i++) { + handler.publish(createLogRecord("Test message: %d", i)); + } + + handler.close(); + + // We should end up with 3 files, 2 rotated and the default log + final Path logDir = BASE_LOG_DIR.toPath(); + final Path path1 = logDir.resolve(FILENAME + ".1" + archiveSuffix); + final Path path2 = logDir.resolve(FILENAME + ".2" + archiveSuffix); + Assert.assertTrue(logFile.exists()); + Assert.assertTrue(Files.exists(path1)); + Assert.assertTrue(Files.exists(path2)); + + // Validate the files are not empty and the compressed file contains at least one log record + if (archiveSuffix.endsWith(".gz")) { + validateGzipContents(path1, "Test message:"); + validateGzipContents(path2, "Test message:"); + } else if (archiveSuffix.endsWith(".zip")) { + validateZipContents(path1, logFile.getName(), "Test message:"); + validateZipContents(path2, logFile.getName(), "Test message:"); + } else { + Assert.fail("Unknown archive suffix: " + archiveSuffix); + } + + // Clean up files + Files.deleteIfExists(path1); + Files.deleteIfExists(path2); + } }