Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add Executor Service To Delete Logs When log_daily Is Enabled #1754

Merged
merged 30 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7bc55a6
updated buildRollingAppenderTriggeringPolicy to include a
deleonenriqueta Feb 2, 2024
f34da2c
updated FileName Pattern
deleonenriqueta Feb 2, 2024
5f7c4ae
updated tests to reflect rolloverStrategy changes
deleonenriqueta Feb 8, 2024
aa02940
updated buildDailyRollingAppender with a new filePattern and updated
deleonenriqueta Feb 8, 2024
5841bdd
started work on creating Runnable for file deletion
deleonenriqueta Feb 8, 2024
982db8d
updates to logging and dateFormat
deleonenriqueta Feb 12, 2024
94948c6
update to fileNamePrefix and filePath
deleonenriqueta Feb 12, 2024
731c051
update tests to reflect FileAppenderFactory changes
deleonenriqueta Feb 12, 2024
009ac3b
added test to check for file deletion
deleonenriqueta Feb 12, 2024
375854d
TODOs and passing path from higher up the stream
jtduffy Feb 12, 2024
c5a5b69
update tests to reflect change in FileAppenderFactory constructor
deleonenriqueta Feb 12, 2024
f6a6f38
updated FileAppenderFactory constructor to require log file directory
deleonenriqueta Feb 12, 2024
deeed1f
updated extractDateString method and exception logging
deleonenriqueta Feb 12, 2024
49b8950
update ParseException handling in run method
deleonenriqueta Feb 13, 2024
f068a5e
add setUp and tearDown methods
deleonenriqueta Feb 13, 2024
6557bd6
Merge branch 'main' into log-daily
deleonenriqueta Feb 14, 2024
38db397
updated where path value is obtained
deleonenriqueta Feb 16, 2024
3d82a6a
added some edge case tests
deleonenriqueta Feb 20, 2024
ef8568d
update to how repeat interval is calculated for the executor service in
deleonenriqueta Feb 20, 2024
e31a123
added notes, helper method for extracting file name prefix and updated
deleonenriqueta Feb 20, 2024
18cccb9
moved delay and interval values for buildDailyRollingAppender
deleonenriqueta Feb 21, 2024
ae36a3c
updated imports
deleonenriqueta Feb 22, 2024
b730f2d
updated the thresholdDate
deleonenriqueta Feb 22, 2024
0a7bb41
Merge branch 'main' into log-daily
deleonenriqueta Feb 22, 2024
4c00ca5
updated runnable class name and added some additional test
deleonenriqueta Feb 22, 2024
696f7f4
update to class fields and field notes
deleonenriqueta Feb 23, 2024
6e470e3
removed thread naming
deleonenriqueta Feb 23, 2024
4b5625c
added threadFactory to generate daemon threads for executorService and
deleonenriqueta Feb 24, 2024
ba65447
cleaned up some tests and update logging
deleonenriqueta Feb 24, 2024
cdec94a
updated threadFactory to a lambda in buildDailyRollingAppender
deleonenriqueta Feb 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.newrelic.agent.logging;

import com.newrelic.api.agent.NewRelic;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
* Runnable class to clean up expired log files based on a specified threshold.
* This class iterates over agent log files identifying those older than the specified
* threshold, and deletes them.
*/
public class ClearExpiredLogsRunnable implements Runnable {

/**
* Regular expression pattern for matching the date format in log file names.
* The date format follows the pattern yyyy-MM-dd.
*/
private static final String DATE_REGEX = "\\.(\\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))(\\.\\d)?$";

/**
* The compiled pattern for matching the date format in log file names.
*/
private final Pattern pattern;
private final Path logDirectoryPath;
private final int daysToKeepFiles;

/**
* Constructs a ClearExpiredLogFilesRunnable object.
*
* @param logDirectoryPath the directory path where log files are located
* @param fileCount the number of days to keep log files before deleting them
* @param filePrefixPath the file prefix used to filter log files
*/
public ClearExpiredLogsRunnable(Path logDirectoryPath, int fileCount, String filePrefixPath) {
this.logDirectoryPath = logDirectoryPath;
this.daysToKeepFiles = fileCount;

String fileNamePrefix = extractFileNamePrefix(filePrefixPath);
pattern = Pattern.compile(fileNamePrefix.replace(".", "\\.")
+ DATE_REGEX);
}

@Override
public void run() {
Path logDirectory = Paths.get(logDirectoryPath.toString());
LocalDate thresholdDate = LocalDate.now().minusDays(daysToKeepFiles);

try (Stream<Path> paths = Files.list(logDirectory)) {
paths.forEach(path -> {
String fileName = path.getFileName().toString();
String dateString = extractDateString(fileName);
deleteIfOlderThanThreshold(path, dateString, thresholdDate);
});
} catch (IOException e) {
// Logging failure to list log files
NewRelic.getAgent().getLogger().log(Level.FINEST, "Error listing log files in: " + logDirectoryPath, e);
}
}

private void deleteIfOlderThanThreshold(Path filePath, String dateString, LocalDate thresholdDate) {
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate fileDate;

if (dateString != null) {
try {
fileDate = LocalDate.parse(dateString, dateFormat);
if (fileDate.isBefore(thresholdDate)) {
Files.delete(filePath);
}
} catch (IOException e) {
// Logging failure to parse log file date or error deleting expired log
NewRelic.getAgent().getLogger().log(Level.FINEST, "Error deleting expired log", e);
}
}
}

private String extractDateString(String fileName) {
// Extract the date string from the file name using the pattern
Matcher matcher = pattern.matcher(fileName);
return matcher.find() ? matcher.group(1) : null;
}

private String extractFileNamePrefix(String fileName) {
// Extract the file name prefix from the given file path
String[] parts = fileName.split("/");
return parts[parts.length - 1];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@
import org.apache.logging.log4j.core.appender.FileManager;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.CronTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.NoOpTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.core.layout.PatternLayout;

import java.io.File;
import java.nio.file.Path;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.newrelic.agent.logging.Log4jLogger.CONVERSION_PATTERN;

public class FileAppenderFactory {
Expand All @@ -39,22 +44,36 @@ public class FileAppenderFactory {
*/
static final String FILE_APPENDER_NAME = "File";

/**
* Initial delay for scheduling log files cleanup task (in seconds).
*/
private static final long INITIAL_DELAY_SECONDS = 60;

/**
* Repeat interval for scheduling log files cleanup task (in seconds).
*/
private static final int REPEAT_INTERVAL_SECONDS = 24 * 60 * 60;


private final int fileCount;
private final long logLimitBytes;
private final String fileName;
private final boolean isDaily;
private final String path;

/**
* @param fileCount maximum number of log files
* @param logLimitBytes maximum size of a given log file
* @param fileName prefix for log file names
* @param isDaily if the logs are to be rolled over daily
* @param path directory path for log files
*/
public FileAppenderFactory(int fileCount, long logLimitBytes, String fileName, boolean isDaily) {
public FileAppenderFactory(int fileCount, long logLimitBytes, String fileName, boolean isDaily, String path) {
this.fileCount = fileCount;
this.logLimitBytes = logLimitBytes;
this.fileName = fileName;
this.isDaily = isDaily;
this.path = path;
}

/**
Expand Down Expand Up @@ -99,14 +118,30 @@ private AbstractOutputStreamAppender<? extends FileManager> buildDefaultFileAppe
private RollingFileAppender buildDailyRollingAppender() {

TriggeringPolicy policy = buildRollingAppenderTriggeringPolicy();
DefaultRolloverStrategy rolloverStrategy = DefaultRolloverStrategy.newBuilder().withMax(String.valueOf(fileCount)).build();
DefaultRolloverStrategy rolloverStrategy = DefaultRolloverStrategy.newBuilder()
.withMax(String.valueOf(fileCount))
.build();

String filePattern = fileName + ".%d{yyyy-MM-dd}";
if (logLimitBytes > 0) {
// If we might roll within a day, use a number ordering suffix
filePattern = fileName + ".%d{yyyy-MM-dd}.%i";
}

Path directory = new File(this.path).toPath();
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable);
thread.setName("New Relic Expiring Log File Cleanup");
thread.setDaemon(true);
return thread;
});
executorService.scheduleWithFixedDelay(
new ClearExpiredLogsRunnable(directory, fileCount, fileName),
INITIAL_DELAY_SECONDS,
REPEAT_INTERVAL_SECONDS,
TimeUnit.SECONDS
);

return initializeRollingFileAppender()
.withPolicy(policy)
.withFilePattern(filePattern)
Expand All @@ -115,7 +150,7 @@ private RollingFileAppender buildDailyRollingAppender() {
}

private TriggeringPolicy buildRollingAppenderTriggeringPolicy() {
TriggeringPolicy timeBasedTriggeringPolicy = CronTriggeringPolicy.createPolicy(new DefaultConfiguration(), "true", DAILY_CRON);
TimeBasedTriggeringPolicy timeBasedTriggeringPolicy = TimeBasedTriggeringPolicy.newBuilder().withInterval(1).withModulate(true).build();
TriggeringPolicy sizeBasedTriggeringPolicy = sizeBasedPolicy();
return CompositeTriggeringPolicy.createPolicy(timeBasedTriggeringPolicy, sizeBasedTriggeringPolicy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,10 @@ private void configureFileHandler(String logFileName, AgentConfig agentConfig) {
rootLogger.removeConsoleAppender();
int limit = agentConfig.getLogLimit() * 1024;
int fileCount = Math.max(1, agentConfig.getLogFileCount());
String path = LogFileHelper.getLogFile(agentConfig).getParent();
boolean isDaily = agentConfig.isLogDaily();

rootLogger.addFileAppender(logFileName, limit, fileCount, isDaily);
rootLogger.addFileAppender(logFileName, limit, fileCount, isDaily, path);
logFilePath = logFileName;
String msg = MessageFormat.format("Writing to New Relic log file: {0}", logFileName);
rootLogger.info(msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ public void addConsoleAppender() {
* @param logLimitBytes Log limit
* @param fileCount The number of files.
*/
public void addFileAppender(String fileName, long logLimitBytes, int fileCount, boolean isDaily) {
public void addFileAppender(String fileName, long logLimitBytes, int fileCount, boolean isDaily, String path) {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
Expand All @@ -267,7 +267,7 @@ public void addFileAppender(String fileName, long logLimitBytes, int fileCount,
return;
}

FileAppenderFactory fileAppenderFactory = new FileAppenderFactory(fileCount, logLimitBytes, fileName, isDaily);
FileAppenderFactory fileAppenderFactory = new FileAppenderFactory(fileCount, logLimitBytes, fileName, isDaily, path);
AbstractOutputStreamAppender<? extends FileManager> fileAppender = fileAppenderFactory.build();
if (fileAppender == null) {
return;
Expand Down
Loading
Loading