com.microsoft.applicationinsights.internal.logger.FileLoggerOutput Maven / Gradle / Ivy
/*
* ApplicationInsights-Java
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the ""Software""), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package com.microsoft.applicationinsights.internal.logger;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.Date;
import java.util.Calendar;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import com.microsoft.applicationinsights.internal.util.LocalFileSystemUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.FileUtils;
import com.google.common.collect.Lists;
import com.google.common.base.Strings;
/**
* The class is responsible for write log messages into log files.
* The class will write to a file until the file max size is reached.
*
* When that happens it will create close that file and open a new file.
*
* When the maximum amount of log files is reached, the oldest one will be deleted.
*
* The class does a 'best effort' to work with files, if there is a problem, the class will
* try to write into a {@link com.microsoft.applicationinsights.internal.logger.ConsoleLoggerOutput}
* but will not fail the process under any scenario.
*/
public final class FileLoggerOutput implements LoggerOutput {
private final static int MIN_SIZE_PER_LOG_FILE_IN_MB = 5;
private final static int MAX_SIZE_PER_LOG_FILE_IN_MB = 500;
private final static int MIN_NUMBER_OF_LOG_FILES = 2;
private static String SDK_LOGS_DEFAULT_FOLDER = "javasdklogs";
private final static String LOG_FILE_SUFFIX_FOR_LISTING = "jsl";
private final static String NUMBER_OF_FILES_ATTRIBUTE = "NumberOfFiles";
private final static String TOTAL_SIZE_OF_LOG_FILES_IN_MB_ATTRIBUTE = "NumberOfTotalSizeInMB";
private final static String LOG_FILES_BASE_FOLDER_ATTRIBUTE = "BaseFolder";
private final static String UNIQUE_LOG_FILE_PREFIX_ATTRIBUTE = "UniquePrefix";
private static final String DATE_FORMAT_NOW = "yyyy-MM-dd-HH-mm-ss";
private static class FileAndDate {
public final File file;
public final Date date;
private FileAndDate(File file, Date date) {
this.file = file;
this.date = date;
}
}
private String uniquePrefix;
private LogFileProxy[] files;
private int maxSizePerFileInMB;
private int currentLogFileIndex;
private File baseFolder;
private LogFileProxyFactory factory;
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT_NOW);
private ConsoleLoggerOutput fallbackLoggerOutput = new ConsoleLoggerOutput();
public FileLoggerOutput(Map loggerData) {
uniquePrefix = loggerData.get(UNIQUE_LOG_FILE_PREFIX_ATTRIBUTE);
if (Strings.isNullOrEmpty(uniquePrefix)) {
throw new IllegalArgumentException(String.format("Unique log file prefix is not defined"));
}
uniquePrefix += '-';
int numberOfFiles = getRequest(loggerData, NUMBER_OF_FILES_ATTRIBUTE, MIN_NUMBER_OF_LOG_FILES);
int numberOfTotalMB = getRequest(loggerData, TOTAL_SIZE_OF_LOG_FILES_IN_MB_ATTRIBUTE, MIN_SIZE_PER_LOG_FILE_IN_MB);
String baseFolderName = loggerData.get(LOG_FILES_BASE_FOLDER_ATTRIBUTE);
factory = new DefaultLogFileProxyFactory();
initialize(baseFolderName, numberOfFiles, numberOfTotalMB);
}
private int getRequest(Map loggerData, String requestName, int defaultValue) {
int requestValue = defaultValue;
String requestValueAsString = loggerData.get(requestName);
if (!Strings.isNullOrEmpty(requestValueAsString)) {
try {
requestValue = Integer.valueOf(loggerData.get(requestName));
} catch (Exception e) {
fallbackLoggerOutput.log(String.format("Error: invalid value '%s' for '%s', using default: %d", requestValueAsString, requestName, defaultValue));
}
}
return requestValue;
}
private void initialize(String baseFolderName, int numberOfFiles, int numberOfTotalMB) {
currentLogFileIndex = 0;
if (Strings.isNullOrEmpty(baseFolderName)) {
baseFolderName = SDK_LOGS_DEFAULT_FOLDER;
}
if (numberOfFiles < MIN_NUMBER_OF_LOG_FILES) {
numberOfFiles = MIN_NUMBER_OF_LOG_FILES;
}
files = new LogFileProxy[numberOfFiles];
int tempSizePerFileInMB = numberOfTotalMB / numberOfFiles;
if (tempSizePerFileInMB < MIN_SIZE_PER_LOG_FILE_IN_MB) {
tempSizePerFileInMB = MIN_SIZE_PER_LOG_FILE_IN_MB;
} else if (tempSizePerFileInMB > MAX_SIZE_PER_LOG_FILE_IN_MB) {
tempSizePerFileInMB = MAX_SIZE_PER_LOG_FILE_IN_MB;
}
this.maxSizePerFileInMB = tempSizePerFileInMB;
baseFolder = new File(LocalFileSystemUtils.getTempDir(), baseFolderName);
if (!baseFolder.exists()) {
baseFolder.mkdirs();
} else {
attachToExisting();
}
}
@Override
public synchronized void log(String message) {
try {
LogFileProxy logFileProxy = getCurrentLogFileProxy();
if (logFileProxy != null) {
logFileProxy.writeLine(message);
}
} catch (IOException e) {
fallbackLoggerOutput.log(String.format("Failed to write to log to file exception: %s. Message '%s'", e.getMessage(), message));
}
}
/**
* After this method is called the instance should not be called again for logging messages
*/
@Override
public void close() {
LogFileProxy currentLogger = files[currentLogFileIndex];
if (currentLogger != null) {
try {
files[currentLogFileIndex] = null;
currentLogger.close();
} catch (IOException e) {
}
}
}
void setLogProxyFactory(LogFileProxyFactory factory) {
this.factory = factory;
}
private LogFileProxy getCurrentLogFileProxy() throws IOException {
LogFileProxy currentProxy = files[currentLogFileIndex];
if (currentProxy != null && !currentProxy.isFull()) {
return currentProxy;
}
return createNewFileProxy();
}
private LogFileProxy createNewFileProxy() throws IOException {
LogFileProxy currentLogger = files[currentLogFileIndex];
if (currentLogger != null) {
try {
currentLogger.close();
} catch (IOException e) {
// Failed to close but that should not stop us
fallbackLoggerOutput.log(String.format("Failed to close log file, exception: %s", e.getMessage()));
}
}
++currentLogFileIndex;
if (currentLogFileIndex == files.length) {
currentLogFileIndex = 0;
}
currentLogger = files[currentLogFileIndex];
if (currentLogger != null) {
files[currentLogFileIndex] = null;
// Best effort
try {
currentLogger.delete();
} catch (Exception e) {
// Failed to delete but that should not stop us
fallbackLoggerOutput.log(String.format("Failed to delete log file, exception: %s", e.getMessage()));
}
}
Calendar cal = Calendar.getInstance();
String filePrefix = uniquePrefix + simpleDateFormat.format(cal.getTime());
LogFileProxy logFileProxy = factory.create(baseFolder, filePrefix, maxSizePerFileInMB);
files[currentLogFileIndex] = logFileProxy;
return logFileProxy;
}
private void attachToExisting() {
try {
List oldLogs = getExistingLogsFromNewToOld();
attachToExisting(oldLogs);
} catch (Exception e) {
// Failed to delete but that should not stop us
fallbackLoggerOutput.log(String.format("Failed to delete old log file, exception: %s", e.getMessage()));
}
}
private void attachToExisting(List oldLogs) {
if (oldLogs.isEmpty()) {
return;
}
int filesIndex = currentLogFileIndex;
int numberOfFilesFound = 0;
for (FileAndDate oldLog : oldLogs) {
try {
if (numberOfFilesFound < files.length) {
LogFileProxy logFileProxy = factory.attach(oldLog.file, maxSizePerFileInMB);
if (logFileProxy == null) {
continue;
}
++numberOfFilesFound;
files[filesIndex] = logFileProxy;
++filesIndex;
} else {
oldLog.file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private List getExistingLogsFromNewToOld() {
try {
Collection oldLogs = FileUtils.listFiles(baseFolder, new String[]{LOG_FILE_SUFFIX_FOR_LISTING}, false);
List asList;
if (!(oldLogs instanceof List)) {
asList = Lists.newArrayList(oldLogs);
} else {
asList = (List)oldLogs;
}
ArrayList filesByDate = new ArrayList();
for (File file : asList) {
Date fileDate = getFileDate(file);
if (fileDate == null) {
continue;
}
filesByDate.add(new FileAndDate(file, fileDate));
}
Collections.sort(filesByDate, new Comparator() {
@Override
public int compare(FileAndDate file1, FileAndDate file2) {
if (file1.date.before(file2.date)) {
return 1;
} else if (file2.date.before(file1.date)) {
return -1;
}
return 0;
}
});
return filesByDate;
} catch (Exception e) {
return Collections.emptyList();
}
}
private Date getFileDate(File file) {
try {
String fileName = FilenameUtils.getBaseName(file.getName());
int index = fileName.indexOf(uniquePrefix);
if (index != -1) {
String dateString = fileName.substring(index + uniquePrefix.length(), index + uniquePrefix.length() + DATE_FORMAT_NOW.length());
Date date = simpleDateFormat.parse(dateString);
return date;
}
} catch (Exception e) {
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy