org.tinylog.writers.RollingFileWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tinylog-impl Show documentation
Show all versions of tinylog-impl Show documentation
tinylog native logging implementation
The newest version!
/*
* Copyright 2018 Martin Winandy
*
* 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.tinylog.writers;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.tinylog.Level;
import org.tinylog.configuration.ServiceLoader;
import org.tinylog.converters.FileConverter;
import org.tinylog.converters.NopFileConverter;
import org.tinylog.core.LogEntry;
import org.tinylog.path.DynamicPath;
import org.tinylog.path.FileTuple;
import org.tinylog.policies.Policy;
import org.tinylog.policies.StartupPolicy;
import org.tinylog.provider.InternalLogger;
import org.tinylog.runtime.RuntimeProvider;
import org.tinylog.writers.raw.ByteArrayWriter;
/**
* Writer for outputting log entries to rolling log files. Rollover strategies can be defined via {@link Policy
* policies} and the output can be buffered for improving performance. The path to the log file can contain one or more
* patterns that will be resolved at runtime.
*/
public final class RollingFileWriter extends AbstractFormatPatternWriter {
private final DynamicPath path;
private final List policies;
private final FileConverter converter;
private final int backups;
private final boolean buffered;
private final boolean writingThread;
private final DynamicPath linkToLatest;
private final Charset charset;
private ByteArrayWriter writer;
/**
* @throws IOException
* Log file cannot be opened for write access
* @throws IllegalArgumentException
* A property has an invalid value or is missing in configuration
*/
public RollingFileWriter() throws IOException {
this(Collections.emptyMap());
}
/**
* @param properties
* Configuration for writer
*
* @throws IOException
* Log file cannot be opened for write access
* @throws IllegalArgumentException
* A property has an invalid value or is missing in configuration
*/
public RollingFileWriter(final Map properties) throws IOException {
super(properties);
path = new DynamicPath(getFileName());
policies = createPolicies(getStringValue("policies"));
converter = createConverter(getStringValue("convert"));
backups = properties.containsKey("backups") ? Integer.parseInt(getStringValue("backups")) : -1;
linkToLatest = properties.containsKey("latest") ? new DynamicPath(getStringValue("latest")) : null;
List files = getAllFileTuplesWithoutLinks(converter.getBackupSuffix());
File latestFile = findLatestValidLogFile(path, files);
if (backups >= 0) {
deleteBackups(files, backups);
}
String fileName;
boolean append;
if (latestFile != null) {
fileName = latestFile.getAbsolutePath();
if (canBeContinued(fileName, policies)) {
append = true;
} else {
fileName = path.resolve();
append = false;
}
} else {
fileName = path.resolve();
append = false;
}
charset = getCharset();
buffered = getBooleanValue("buffered");
writingThread = getBooleanValue("writingthread");
writer = createByteArrayWriterAndLinkLatest(fileName, append, buffered, charset);
}
@Override
public void write(final LogEntry logEntry) throws IOException {
byte[] data = render(logEntry).getBytes(charset);
if (writingThread) {
internalWrite(data);
} else {
synchronized (writer) {
internalWrite(data);
}
}
}
@Override
public void flush() throws IOException {
if (writingThread) {
internalFlush();
} else {
synchronized (writer) {
internalFlush();
}
}
}
@Override
public void close() throws IOException, InterruptedException {
if (writingThread) {
internalClose();
} else {
synchronized (writer) {
internalClose();
}
}
}
/**
* Outputs a passed byte array unsynchronized.
*
* @param data
* Byte array to output
* @throws IOException
* Writing failed
*/
private void internalWrite(final byte[] data) throws IOException {
if (!canBeContinued(data, policies)) {
writer.close();
converter.close();
String fileName = path.resolve();
writer = createByteArrayWriterAndLinkLatest(fileName, false, buffered, charset);
for (Policy policy : policies) {
policy.reset();
}
if (backups >= 0) {
deleteBackups(getAllFileTuplesWithoutLinks(converter.getBackupSuffix()), backups);
}
}
byte[] convertedData = converter.write(data);
writer.write(convertedData, 0, convertedData.length);
}
/**
* Outputs buffered log entries immediately unsynchronized.
*
* @throws IOException
* Flushing failed
*/
private void internalFlush() throws IOException {
writer.flush();
}
/**
* Closes the writer unsynchronized.
*
* @throws IOException
* Closing failed
* @throws InterruptedException
* Interrupted while waiting for the converter
*/
private void internalClose() throws IOException, InterruptedException {
writer.close();
converter.close();
converter.shutdown();
}
/**
* Gets all log files including backups from {@link DynamicPath} but without links.
*
* @param backupSuffix
* File extension for backup files
* @return Found file tuples without links
*/
@IgnoreJRERequirement
private List getAllFileTuplesWithoutLinks(final String backupSuffix) {
List files = path.getAllFiles(backupSuffix);
if (linkToLatest != null && !RuntimeProvider.isAndroid()) {
File fileLink = new File(linkToLatest.resolve()).getAbsoluteFile();
Iterator iterator = files.iterator();
while (iterator.hasNext()) {
if (fileLink.equals(iterator.next().getOriginal())) {
iterator.remove();
break;
}
}
}
return files;
}
/**
* Creates a {@link ByteArrayWriter} for a file and creates a link to it if linking is enabled.
*
* @param fileName
* Name of file to open for writing
* @param append
* An already existing file should be continued
* @param buffered
* Output should be buffered
* @param charset
* Charset used by the writer
* @return Writer for writing to passed file
* @throws IOException
* Log file cannot be opened for write access
*/
@IgnoreJRERequirement
private ByteArrayWriter createByteArrayWriterAndLinkLatest(final String fileName, final boolean append,
final boolean buffered, final Charset charset) throws IOException {
converter.open(fileName);
ByteArrayWriter writer = createByteArrayWriter(fileName, append, buffered, false, false, charset);
if (linkToLatest != null) {
File logFile = new File(fileName);
File linkFile = new File(linkToLatest.resolve());
if (!RuntimeProvider.isAndroid()) {
try {
Path logPath = logFile.toPath();
Path linkPath = linkFile.toPath();
Files.deleteIfExists(linkPath);
Files.createLink(linkPath, logPath);
} catch (IOException ex) {
InternalLogger.log(Level.ERROR, ex, "Failed to create link '" + linkFile + "'");
}
} else {
InternalLogger.log(Level.WARN, "Cannot create link to latest log file on Android");
}
}
return writer;
}
/**
* Finds the latest valid existing original log file.
*
* @param path
* Dynamic path instance fo validation
* @param files
* All original and backup files
* @return Found original log file or {@code null} if there are no original log files
*/
private static File findLatestValidLogFile(final DynamicPath path, final List files) {
for (FileTuple file : files) {
if (file.getOriginal().isFile() && (file.getOriginal().equals(file.getBackup()) || !file.getBackup().isFile())) {
File original = file.getOriginal();
if (path.isValid(original)) {
return original;
}
}
}
return null;
}
/**
* Creates policies from a nullable string.
*
* @param property
* Nullable string with policies to create
* @return Created policies
*/
private static List createPolicies(final String property) {
if (property == null || property.isEmpty()) {
return Collections.singletonList(new StartupPolicy(null));
} else {
if (RuntimeProvider.getProcessId() == Long.MIN_VALUE) {
java.util.ServiceLoader.load(Policy.class); // Workaround for ProGuard (see issue #126)
}
return new ServiceLoader(Policy.class, String.class).createList(property);
}
}
/**
* Creates the file converter from a nullable string.
*
* @param property
* Nullable string with converter to create
* @return Created file converter
*/
private static FileConverter createConverter(final String property) {
if (property == null || property.isEmpty()) {
return new NopFileConverter();
} else {
if (RuntimeProvider.getProcessId() == Long.MIN_VALUE) {
java.util.ServiceLoader.load(FileConverter.class); // Workaround for ProGuard (see issue #126)
}
FileConverter converter = new ServiceLoader(FileConverter.class).create(property);
return converter == null ? new NopFileConverter() : converter;
}
}
/**
* Checks if an already existing log file can be continued.
*
* @param fileName
* Log file
* @param policies
* Policies that should be applied
* @return {@code true} if the passed log file can be continued, {@code false} if a new log file should be started
*/
private static boolean canBeContinued(final String fileName, final List policies) {
boolean result = true;
for (Policy policy : policies) {
result &= policy.continueExistingFile(fileName);
}
return result;
}
/**
* Checks if a new log entry can be still written to the current log file.
*
* @param data
* Log entry
* @param policies
* Policies that should be applied
* @return {@code true} if the current log file can be continued, {@code false} if a new log file should be started
*/
private static boolean canBeContinued(final byte[] data, final List policies) {
boolean result = true;
for (Policy policy : policies) {
result &= policy.continueCurrentFile(data);
}
return result;
}
/**
* Deletes old log files.
*
* @param files
* All original and backup files
* @param count
* Number of log files to keep
*/
private static void deleteBackups(final List files, final int count) {
for (int i = count; i < files.size(); ++i) {
files.get(i).delete();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy