日志文件自動刪除功能必不可少,當(dāng)然你可以讓運維去做這事,只是這不地道。而日志組件是一個必備組件,讓其多做一件刪除的工作,無可厚非。本文就來探討下 log4j 的日志文件自動刪除實現(xiàn)吧。
0.自動刪除配置參考樣例: (log4j2.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<?xml version= "1.0" encoding= "UTF-8" ?> <Configuration status= "warn" monitorInterval= "30" strict= "true" schema= "Log4J-V2.2.xsd" > <Properties> <Property name= "log_level" >info</Property> </Properties> <Appenders> <!-- 輸出到控制臺 --> <Console name= "Console" target= "SYSTEM_OUT" > <ThresholdFilter level= "${log_level}" onMatch= "ACCEPT" onMismatch= "DENY" /> <PatternLayout pattern= "%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%t] %p - %c - %m%n" /> </Console> <!-- 與properties文件中位置存在沖突,如有問題,請注意調(diào)整 --> <RollingFile name= "logFile" fileName= "logs/app/test.log" filePattern= "logs/app/history/test-%d{MM-dd-yyyy}-%i.log.gz" > <ThresholdFilter level= "${log_level}" onMatch= "ACCEPT" onMismatch= "DENY" /> <PatternLayout pattern= "%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%p] [%c:%L] -- %m%n" /> <Policies> <!-- 按天遞計算頻率 --> <TimeBasedTriggeringPolicy interval= "1" /> <SizeBasedTriggeringPolicy size= "500 MB" /> <OnStartupTriggeringPolicy /> </Policies> <!-- 刪除策略配置 --> <DefaultRolloverStrategy max= "5" > <Delete basePath= "logs/app/history" maxDepth= "1" > <IfFileName glob= "*.log.gz" /> <IfLastModified age= "7d" /> </Delete> <Delete basePath= "logs/app/history" maxDepth= "1" > <IfFileName glob= "*.docx" /> </Delete> <Delete basePath= "logs/app/history" maxDepth= "1" > <IfFileName glob= "*.vsdx" /> </Delete> </DefaultRolloverStrategy> </RollingFile> <Async name= "Async" bufferSize= "2000" blocking= "false" > <AppenderRef ref= "logFile" /> </Async> </Appenders> <Loggers> <Root level= "${log_level}" > <AppenderRef ref= "Console" /> <AppenderRef ref= "Async" /> </Root> <!-- 配置個例 --> <Logger name= "com.xx.filter" level= "info" /> </Loggers> </Configuration> |
如果僅想停留在使用層面,如上log4j2.xml配置文件足矣!
不過,至少得注意一點,以上配置需要基于log4j2, 而如果你是 log4j1.x,則需要做下無縫升級:主要就是換下jar包版本,換個橋接包之類的,比如下參考配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version> 1.2 </version> </dependency> <!-- 橋接:告訴commons logging使用Log4j2 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version> 1.7 . 26 </version> </dependency> <!-- 此處老版本,需注釋掉 --> <!--<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version> 1.2 . 17 </version> </dependency>--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version> 1.10 </version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version> 2.8 . 2 </version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version> 2.8 . 2 </version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version> 2.8 . 2 </version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-web</artifactId> <version> 2.8 . 2 </version> </dependency> |
如果還想多了解一點其運行原理,就跟隨本文的腳步吧:
1.自動清理大體運行流程
自動刪除工作的運行原理大體流程如下。(大抵都是如此)
1. 加載log4j2.xml配置文件;
2. 讀取appenders,并添加到log4j上下文中;
3. 加載 policy, 加載 rollover 配置;
4. 寫入日志時判斷是否滿足rollover配置, 默認(rèn)是一天運行一次, 可自行添加各種運行測試, 比如大小、啟動時;
所以,刪除策略的核心是每一次添加日志時。代碼驗證如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
// 在每次添加日志時判定 // org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender#append /** * Write the log entry rolling over the file when required. * * @param event The LogEvent. */ @Override public void append( final LogEvent event) { final RollingRandomAccessFileManager manager = getManager(); // 重點:直接檢查是否需要 rollover, 如需要直接進行 manager.checkRollover(event); // Leverage the nice batching behaviour of async Loggers/Appenders: // we can signal the file manager that it needs to flush the buffer // to disk at the end of a batch. // From a user's point of view, this means that all log events are // _always_ available in the log file, without incurring the overhead // of immediateFlush=true. manager.setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted // LOG4J2-1292 utilize gc-free Layout.encode() method: taken care of in superclass super .append(event); } // org.apache.logging.log4j.core.appender.rolling.RollingFileManager#checkRollover /** * Determines if a rollover should occur. * @param event The LogEvent. */ public synchronized void checkRollover( final LogEvent event) { // 由各觸發(fā)策略判定是否需要進行 rolling // 如需要, 則調(diào)用 rollover() if (triggeringPolicy.isTriggeringEvent(event)) { rollover(); } } |
所以,何時進行刪除?答案是在適當(dāng)?shù)臅r機,這個時機可以是任意時候。
2. log4j 日志滾動
日志滾動,可以是重命名,也可以是刪除文件。但總體判斷是否可觸發(fā)滾動的前提是一致的。我們這里主要關(guān)注文件刪除。我們以時間作為依據(jù)看下判斷過程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
// 1. 判斷是否是 觸發(fā)事件時機 // org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy#isTriggeringEvent /** * Determines whether a rollover should occur. * @param event A reference to the currently event. * @return true if a rollover should occur. */ @Override public boolean isTriggeringEvent( final LogEvent event) { if (manager.getFileSize() == 0 ) { return false ; } final long nowMillis = event.getTimeMillis(); // TimeBasedTriggeringPolicy, 是基于時間判斷的, 此處為每天一次 if (nowMillis >= nextRolloverMillis) { nextRolloverMillis = manager.getPatternProcessor().getNextTime(nowMillis, interval, modulate); return true ; } return false ; } // org.apache.logging.log4j.core.appender.rolling.RollingFileManager#rollover() public synchronized void rollover() { if (!hasOutputStream()) { return ; } // strategy 是xml配置的策略 if (rollover(rolloverStrategy)) { try { size = 0 ; initialTime = System.currentTimeMillis(); createFileAfterRollover(); } catch ( final IOException e) { logError( "Failed to create file after rollover" , e); } } } // RollingFileManager 統(tǒng)一管理觸發(fā)器 // org.apache.logging.log4j.core.appender.rolling.RollingFileManager#rollover private boolean rollover( final RolloverStrategy strategy) { boolean releaseRequired = false ; try { // Block until the asynchronous operation is completed. // 上鎖保證線程安全 semaphore.acquire(); releaseRequired = true ; } catch ( final InterruptedException e) { logError( "Thread interrupted while attempting to check rollover" , e); return false ; } boolean success = true ; try { // 由各觸發(fā)器運行 rollover 邏輯 final RolloverDescription descriptor = strategy.rollover( this ); if (descriptor != null ) { writeFooter(); closeOutputStream(); if (descriptor.getSynchronous() != null ) { LOGGER.debug( "RollingFileManager executing synchronous {}" , descriptor.getSynchronous()); try { // 先使用同步方法,改名,然后再使用異步方法操作更多 success = descriptor.getSynchronous().execute(); } catch ( final Exception ex) { success = false ; logError( "Caught error in synchronous task" , ex); } } // 如果配置了異步器, 則使用異步進行 rollover if (success && descriptor.getAsynchronous() != null ) { LOGGER.debug( "RollingFileManager executing async {}" , descriptor.getAsynchronous()); // CompositeAction, 使用異步線程池運行用戶的 action asyncExecutor.execute( new AsyncAction(descriptor.getAsynchronous(), this )); // 在異步運行action期間,鎖是不會被釋放的,以避免線程安全問題 // 直到異步任務(wù)完成,再主動釋放鎖 releaseRequired = false ; } return true ; } return false ; } finally { if (releaseRequired) { semaphore.release(); } } } |
此處滾動有兩個處理點,1. 每個滾動策略可以自行處理業(yè)務(wù); 2. RollingFileManager 統(tǒng)一管理觸發(fā)同步和異步的滾動action;
3. DefaultRolloverStrategy 默認(rèn)滾動策略驅(qū)動
DefaultRolloverStrategy 作為一個默認(rèn)的滾動策略實現(xiàn),可以配置多個 Action, 然后處理刪除操作。
刪除有兩種方式: 1. 當(dāng)次滾動的文件數(shù)過多,會立即進行刪除; 2. 配置單獨的 DeleteAction, 根據(jù)配置的具體策略進行刪除。(但該Action只會被返回給外部調(diào)用,自身則不會執(zhí)行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
// org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy#rollover /** * Performs the rollover. * * @param manager The RollingFileManager name for current active log file. * @return A RolloverDescription. * @throws SecurityException if an error occurs. */ @Override public RolloverDescription rollover( final RollingFileManager manager) throws SecurityException { int fileIndex; // 默認(rèn) minIndex=1 if (minIndex == Integer.MIN_VALUE) { final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager); fileIndex = eligibleFiles.size() > 0 ? eligibleFiles.lastKey() + 1 : 1 ; } else { if (maxIndex < 0 ) { return null ; } final long startNanos = System.nanoTime(); // 刪除case1: 獲取符合條件的文件數(shù),同時清理掉大于 max 配置的日志文件 // 如配置 max=5, 當(dāng)前只有4個滿足時, 不會立即清理文件, 但也不會阻塞后續(xù)流程 // 只要沒有出現(xiàn)錯誤, fileIndex 不會小于0 fileIndex = purge(minIndex, maxIndex, manager); if (fileIndex < 0 ) { return null ; } if (LOGGER.isTraceEnabled()) { final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); LOGGER.trace( "DefaultRolloverStrategy.purge() took {} milliseconds" , durationMillis); } } // 進入此區(qū)域即意味著,必然有文件需要滾動,重新命名了 final StringBuilder buf = new StringBuilder( 255 ); manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex); final String currentFileName = manager.getFileName(); String renameTo = buf.toString(); final String compressedName = renameTo; Action compressAction = null ; FileExtension fileExtension = manager.getFileExtension(); if (fileExtension != null ) { renameTo = renameTo.substring( 0 , renameTo.length() - fileExtension.length()); compressAction = fileExtension.createCompressAction(renameTo, compressedName, true , compressionLevel); } // 未發(fā)生文件重命名情況,即文件未被重命名未被滾動 // 該種情況應(yīng)該不太會發(fā)生 if (currentFileName.equals(renameTo)) { LOGGER.warn( "Attempt to rename file {} to itself will be ignored" , currentFileName); return new RolloverDescriptionImpl(currentFileName, false , null , null ); } // 新建一個重命令的 action, 返回待用 final FileRenameAction renameAction = new FileRenameAction( new File(currentFileName), new File(renameTo), manager.isRenameEmptyFiles()); // 異步處理器,會處理用戶配置的異步action,如本文配置的 DeleteAction // 它將會在稍后被提交到異步線程池中運行 final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError); // 封裝Rollover返回, renameAction 是同步方法, 其他用戶配置的動態(tài)action 則是異步方法 // 刪除case2: 封裝異步返回action return new RolloverDescriptionImpl(currentFileName, false , renameAction, asyncAction); } private int purge( final int lowIndex, final int highIndex, final RollingFileManager manager) { // 默認(rèn)使用 accending 的方式進行清理文件 return useMax ? purgeAscending(lowIndex, highIndex, manager) : purgeDescending(lowIndex, highIndex, manager); } // org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy#purgeAscending /** * Purges and renames old log files in preparation for rollover. The oldest file will have the smallest index, the * newest the highest. * * @param lowIndex low index. Log file associated with low index will be deleted if needed. * @param highIndex high index. * @param manager The RollingFileManager * @return true if purge was successful and rollover should be attempted. */ private int purgeAscending( final int lowIndex, final int highIndex, final RollingFileManager manager) { final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager); final int maxFiles = highIndex - lowIndex + 1 ; boolean renameFiles = false ; // 依次迭代 eligibleFiles, 刪除 while (eligibleFiles.size() >= maxFiles) { try { LOGGER.debug( "Eligible files: {}" , eligibleFiles); Integer key = eligibleFiles.firstKey(); LOGGER.debug( "Deleting {}" , eligibleFiles.get(key).toFile().getAbsolutePath()); // 調(diào)用nio的接口刪除文件 Files.delete(eligibleFiles.get(key)); eligibleFiles.remove(key); renameFiles = true ; } catch (IOException ioe) { LOGGER.error( "Unable to delete {}, {}" , eligibleFiles.firstKey(), ioe.getMessage(), ioe); break ; } } final StringBuilder buf = new StringBuilder(); if (renameFiles) { // 針對未完成刪除的文件,繼續(xù)處理 // 比如使用 匹配的方式匹配文件, 則不能被正常刪除 // 還有些未超過maxFiles的文件 for (Map.Entry<Integer, Path> entry : eligibleFiles.entrySet()) { buf.setLength( 0 ); // LOG4J2-531: directory scan & rollover must use same format manager.getPatternProcessor().formatFileName(strSubstitutor, buf, entry.getKey() - 1 ); String currentName = entry.getValue().toFile().getName(); String renameTo = buf.toString(); int suffixLength = suffixLength(renameTo); if (suffixLength > 0 && suffixLength(currentName) == 0 ) { renameTo = renameTo.substring( 0 , renameTo.length() - suffixLength); } Action action = new FileRenameAction(entry.getValue().toFile(), new File(renameTo), true ); try { LOGGER.debug( "DefaultRolloverStrategy.purgeAscending executing {}" , action); if (!action.execute()) { return - 1 ; } } catch ( final Exception ex) { LOGGER.warn( "Exception during purge in RollingFileAppender" , ex); return - 1 ; } } } // 此處返回的 findIndex 一定是 >=0 的 return eligibleFiles.size() > 0 ? (eligibleFiles.lastKey() < highIndex ? eligibleFiles.lastKey() + 1 : highIndex) : lowIndex; } |
4. 符合過濾條件的文件查找
當(dāng)配置了 max 參數(shù),這個參數(shù)是如何匹配的呢?比如我某個文件夾下有很歷史文件,是否都會匹配呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
// 文件查找規(guī)則 // org.apache.logging.log4j.core.appender.rolling.AbstractRolloverStrategy#getEligibleFiles protected SortedMap<Integer, Path> getEligibleFiles( final RollingFileManager manager) { return getEligibleFiles(manager, true ); } protected SortedMap<Integer, Path> getEligibleFiles( final RollingFileManager manager, final boolean isAscending) { final StringBuilder buf = new StringBuilder(); // 此處的pattern 即是在appender上配置的 filePattern, 一般會受限于 MM-dd-yyyy-$i.log.gz String pattern = manager.getPatternProcessor().getPattern(); // 此處會將時間替換為當(dāng)前, 然后按照此規(guī)則進行匹配要處理的文件 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, NotANumber.NAN); return getEligibleFiles(buf.toString(), pattern, isAscending); } // 細節(jié)匹配要處理的文件 protected SortedMap<Integer, Path> getEligibleFiles(String path, String logfilePattern, boolean isAscending) { TreeMap<Integer, Path> eligibleFiles = new TreeMap<>(); File file = new File(path); File parent = file.getParentFile(); if (parent == null ) { parent = new File( "." ); } else { parent.mkdirs(); } if (!logfilePattern.contains( "%i" )) { return eligibleFiles; } Path dir = parent.toPath(); String fileName = file.getName(); int suffixLength = suffixLength(fileName); if (suffixLength > 0 ) { fileName = fileName.substring( 0 , fileName.length() - suffixLength) + ".*" ; } String filePattern = fileName.replace(NotANumber.VALUE, "(\\d+)" ); Pattern pattern = Pattern.compile(filePattern); try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { for (Path entry: stream) { // 該匹配相當(dāng)精確 // 只會刪除當(dāng)天或者在時間交替的時候刪除上一天的數(shù)據(jù)咯 // 如果在這個時候進行了重啟操作,就再也不會刪除此文件了 Matcher matcher = pattern.matcher(entry.toFile().getName()); if (matcher.matches()) { Integer index = Integer.parseInt(matcher.group( 1 )); eligibleFiles.put(index, entry); } } } catch (IOException ioe) { throw new LoggingException( "Error reading folder " + dir + " " + ioe.getMessage(), ioe); } return isAscending? eligibleFiles : eligibleFiles.descendingMap(); } // 此處會將 各種格式的文件名,替換為當(dāng)前時間或者最后一次滾動的文件的時間。所以匹配的時候,并不會匹配超時當(dāng)前認(rèn)知范圍的文件 /** * Formats file name. * @param subst The StrSubstitutor. * @param buf string buffer to which formatted file name is appended, may not be null. * @param obj object to be evaluated in formatting, may not be null. */ public final void formatFileName( final StrSubstitutor subst, final StringBuilder buf, final boolean useCurrentTime, final Object obj) { // LOG4J2-628: we deliberately use System time, not the log4j.Clock time // for creating the file name of rolled-over files. final long time = useCurrentTime && currentFileTime != 0 ? currentFileTime : prevFileTime != 0 ? prevFileTime : System.currentTimeMillis(); formatFileName(buf, new Date(time), obj); final LogEvent event = new Log4jLogEvent.Builder().setTimeMillis(time).build(); final String fileName = subst.replace(event, buf); buf.setLength( 0 ); buf.append(fileName); } |
AsyncAction 是一個 Runnable 的實現(xiàn), 被直接提交到線程池運行. AsyncAction -> AbstractAction -> Action -> Runnable
它是一個統(tǒng)一管理異步Action的包裝,主要是管理鎖和異常類操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
// org.apache.logging.log4j.core.appender.rolling.RollingFileManager.AsyncAction /** * Performs actions asynchronously. */ private static class AsyncAction extends AbstractAction { private final Action action; private final RollingFileManager manager; /** * Constructor. * @param act The action to perform. * @param manager The manager. */ public AsyncAction( final Action act, final RollingFileManager manager) { this .action = act; this .manager = manager; } /** * Executes an action. * * @return true if action was successful. A return value of false will cause * the rollover to be aborted if possible. * @throws java.io.IOException if IO error, a thrown exception will cause the rollover * to be aborted if possible. */ @Override public boolean execute() throws IOException { try { // 門面調(diào)用 action.execute(), 一般是調(diào)用 CompositeAction, 里面封裝了多個 action return action.execute(); } finally { // 任務(wù)執(zhí)行完成,才會釋放外部的鎖 // 雖然不是很優(yōu)雅,但是很準(zhǔn)確很安全 manager.semaphore.release(); } } ... } // CompositeAction 封裝了多個 action 處理 // org.apache.logging.log4j.core.appender.rolling.action.CompositeAction#run /** * Execute sequence of actions. * * @return true if all actions were successful. * @throws IOException on IO error. */ @Override public boolean execute() throws IOException { if (stopOnError) { // 依次調(diào)用action for ( final Action action : actions) { if (!action.execute()) { return false ; } } return true ; } boolean status = true ; IOException exception = null ; for ( final Action action : actions) { try { status &= action.execute(); } catch ( final IOException ex) { status = false ; if (exception == null ) { exception = ex; } } } if (exception != null ) { throw exception; } return status; } |
DeleteAction是我們真正關(guān)心的動作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
// CompositeAction 封裝了多個 action 處理 // org.apache.logging.log4j.core.appender.rolling.action.CompositeAction#run /** * Execute sequence of actions. * * @return true if all actions were successful. * @throws IOException on IO error. */ @Override public boolean execute() throws IOException { if (stopOnError) { // 依次調(diào)用action for ( final Action action : actions) { if (!action.execute()) { return false ; } } return true ; } boolean status = true ; IOException exception = null ; for ( final Action action : actions) { try { status &= action.execute(); } catch ( final IOException ex) { status = false ; if (exception == null ) { exception = ex; } } } if (exception != null ) { throw exception; } return status; } // DeleteAction 做真正的刪除動作 // org.apache.logging.log4j.core.appender.rolling.action.DeleteAction#execute() @Override public boolean execute() throws IOException { // 如果沒有script配置,則直接委托父類處理 return scriptCondition != null ? executeScript() : super .execute(); } org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute() @Override public boolean execute() throws IOException { // 根據(jù)指定的basePath, 和過濾條件,選擇相關(guān)文件 // 調(diào)用 DeleteAction 的 createFileVisitor(), 返回 DeletingVisitor return execute(createFileVisitor(getBasePath(), pathConditions)); } // org.apache.logging.log4j.core.appender.rolling.action.DeleteAction#execute(java.nio.file.FileVisitor<java.nio.file.Path>) @Override public boolean execute( final FileVisitor<Path> visitor) throws IOException { // 根據(jù)maxDepth設(shè)置,遍歷所有可能的文件路徑 // 使用 Files.walkFileTree() 實現(xiàn), 添加到 collected 中 final List<PathWithAttributes> sortedPaths = getSortedPaths(); trace( "Sorted paths:" , sortedPaths); for ( final PathWithAttributes element : sortedPaths) { try { // 依次調(diào)用 visitFile, 依次判斷是否需要刪除 visitor.visitFile(element.getPath(), element.getAttributes()); } catch ( final IOException ioex) { LOGGER.error( "Error in post-rollover Delete when visiting {}" , element.getPath(), ioex); visitor.visitFileFailed(element.getPath(), ioex); } } // TODO return (visitor.success || ignoreProcessingFailure) return true ; // do not abort rollover even if processing failed } |
最終,即和想像的一樣:找到要查找的文件夾,遍歷各文件,用多個條件判斷是否滿足。刪除符合條件的文件。
只是這其中注意的點:如何刪除文件的線程安全性;如何保證刪除工作不影響業(yè)務(wù)線程;很常見的鎖和多線程的應(yīng)用。
5.真正的刪除
真正的刪除動作就是在DeleteAction中配置的,但上面可以看它是調(diào)用visitor的visitFile方法,所以有必要看看是如何真正處理刪除的。(實際上前面在purge時已經(jīng)做過一次刪除操作了,所以別被兩個點迷惑了,建議盡量只依賴于Delete配置,可以將外部max設(shè)置很大以避免兩處生效)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// org.apache.logging.log4j.core.appender.rolling.action.DeletingVisitor#visitFile @Override public FileVisitResult visitFile( final Path file, final BasicFileAttributes attrs) throws IOException { for ( final PathCondition pathFilter : pathConditions) { final Path relative = basePath.relativize(file); // 遍歷所有條件,只要有一個不符合,即不進行刪除。 // 所以,所以條件是 AND 關(guān)系, 沒有 OR 關(guān)系 // 如果想配置 OR 關(guān)系,只能配置多個DELETE if (!pathFilter.accept(basePath, relative, attrs)) { LOGGER.trace( "Not deleting base={}, relative={}" , basePath, relative); return FileVisitResult.CONTINUE; } } // 直接刪除文件 if (isTestMode()) { LOGGER.info( "Deleting {} (TEST MODE: file not actually deleted)" , file); } else { delete(file); } return FileVisitResult.CONTINUE; } |
刪除策略配置比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<RollingFile name= "logFile" fileName= "logs/app/test.log" filePattern= "logs/app/history/test-%d{MM-dd-yyyy}-%i.log.gz" > <ThresholdFilter level= "${log_level}" onMatch= "ACCEPT" onMismatch= "DENY" /> <PatternLayout pattern= "%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%p] [%c:%L] -- %m%n" /> <Policies> <!-- 按天遞計算頻率 --> <TimeBasedTriggeringPolicy interval= "1" /> <SizeBasedTriggeringPolicy size= "500 MB" /> <OnStartupTriggeringPolicy /> </Policies> <!-- 刪除策略配置 --> <DefaultRolloverStrategy max= "5000" > <Delete basePath= "logs/app/history" maxDepth= "1" > <!-- 配置且關(guān)系 --> <IfFileName glob= "*.log.gz" /> <IfLastModified age= "7d" /> </Delete> <!-- 配置或關(guān)系 --> <Delete basePath= "logs/app/history" maxDepth= "1" > <IfFileName glob= "*.docx" /> </Delete> <Delete basePath= "logs/app/history" maxDepth= "1" > <IfFileName glob= "*.vsdx" /> </Delete> </DefaultRolloverStrategy> </RollingFile> |
另外說明,之所以能夠無縫替換,是因為利用了不同實現(xiàn)版本的 org/slf4j/impl/StaticLoggerBinder.class, 而外部都使用 slf4j 接口定義實現(xiàn)的,比如 org.apache.logging.log4j:log4j-slf4j-impl 包的實現(xiàn)。
總結(jié)
到此這篇關(guān)于log4j2 自動刪除過期日志文件的配置及實現(xiàn)原理解析的文章就介紹到這了,更多相關(guān)log4j2自動刪除過期日志文件內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://www.cnblogs.com/yougewe/p/13407812.html