com.swirlds.logging.io.RolloverFileOutputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swirlds-logging Show documentation
Show all versions of swirlds-logging Show documentation
Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* 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 com.swirlds.logging.io;
import static com.swirlds.logging.utils.StringUtils.toPaddedDigitsString;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
/**
* {@link RolloverFileOutputStream} is an {@link OutputStream} implementation that supports rollover size functionality
* for the underlying file. Note: It's important to manage the lifecycle of the output stream properly by calling
* {@link #flush()} and {@link #close()} methods when finished using it to ensure proper resource cleanup.
*/
public class RolloverFileOutputStream extends OutputStream {
protected final Path logPath;
private final int maxFiles;
private final long maxFileSize;
private final boolean append;
private long remainingSize;
private FileOutputStream outputStream;
private final String baseName;
private final String extension;
private final int indexLength;
private int index;
/**
* Creates an {@link RolloverFileOutputStream}. Supporting size based rollover. Usage:
*
* // Example usage with size-based rollover
* Path logPath = Paths.get("logs", "example.log");
* long maxFileSize = 1024 * 1024; // 1 MB
* boolean append = true;
* int maxFiles = 5; // Maximum number of rolling files
* RolloverFileOutputStream outputStream = new RolloverFileOutputStream(logPath, maxFileSize, append, maxFiles);
*
*
* @param logPath path where the logging file is located. Should contain the base dir + the name of the logging
* file.
* @param maxFileSize maximum size for the file. The limit is checked with best effort
* @param append if true and the file exists, appends the content to the file. if not, the file is rolled.
* @param maxFiles Within a rolling period, how many rolling files are allowed.
*/
protected RolloverFileOutputStream(
final @NonNull Path logPath, final long maxFileSize, final boolean append, final int maxFiles) {
this.logPath = logPath.toAbsolutePath().getParent();
this.append = append;
this.maxFiles = maxFiles;
this.maxFileSize = maxFileSize;
final String baseFile = logPath.getFileName().toString();
final int lastDotIndex = baseFile.lastIndexOf(".");
if (lastDotIndex > 0) {
this.baseName = baseFile.substring(0, lastDotIndex);
this.extension = baseFile.substring(lastDotIndex + 1);
} else {
this.baseName = baseFile;
this.extension = "";
}
this.index = 0;
this.indexLength = String.valueOf(maxFiles).length();
try {
this.outputStream = new FileOutputStream(logPath.toString(), this.append);
} catch (FileNotFoundException e) {
throw new IllegalStateException("Log file cannot be created", e);
}
if (Files.exists(logPath) && append) {
this.remainingSize = maxFileSize - logPath.toFile().length();
} else {
this.remainingSize = maxFileSize;
this.roll();
}
}
/**
* Writes {@code length} bytes from the specified {@code bytes} array starting at {@code offset} off to this file
* output stream. Rolls over the file if necessary.
*/
@Override
public synchronized void write(@NonNull final byte[] bytes, final int offset, final int length) throws IOException {
((OutputStream) outputStream).write(bytes, offset, length);
this.remainingSize -= length;
this.rollIfNeeded();
}
/**
* Writes a byte array into the stream. Rolls over the file if necessary
*/
@Override
public synchronized void write(@NonNull final byte[] bytes) throws IOException {
((OutputStream) outputStream).write(bytes);
this.remainingSize -= (bytes.length);
this.rollIfNeeded();
}
/**
* Writes a single byte to the stream. Rolls over the file if necessary
*/
@Override
public synchronized void write(final int b) throws IOException {
((OutputStream) outputStream).write(b);
this.remainingSize--;
this.rollIfNeeded();
}
@Override
public void flush() throws IOException {
outputStream.flush();
}
@Override
public void close() throws IOException {
outputStream.flush();
((OutputStream) outputStream).close();
}
private String dotExtension() {
if (extension.isEmpty()) {
return "";
}
return "." + extension;
}
private void rollIfNeeded() {
if (remainingSize <= 0) {
this.roll();
}
}
private String logFileName() {
return baseName + this.dotExtension();
}
protected Path logFilePath() {
return logPath.resolve(logFileName());
}
/**
* Rolls the file
*/
private void roll() {
final long maxIndex = maxFiles - 1;
int currentIndex = index;
Path newPath = getPathFor(currentIndex);
// Start by searching a new index if current index is in use
while (Files.exists(newPath) && currentIndex <= maxIndex) {
currentIndex++;
newPath = getPathFor(currentIndex % maxFiles);
}
if (currentIndex > maxIndex) {
currentIndex = index;
newPath = getPathFor(currentIndex % maxFiles);
}
final File file = logFilePath().toFile();
try {
outputStream.close();
Files.move(file.toPath(), newPath, StandardCopyOption.REPLACE_EXISTING);
outputStream = new FileOutputStream(file, append);
} catch (IOException e) {
throw new IllegalStateException("Something happened while rolling over", e);
}
index = currentIndex;
remainingSize = maxFileSize;
}
private Path getPathFor(int index) {
return logPath.resolve(baseName + "." + toPaddedDigitsString(index, indexLength) + dotExtension());
}
}