View Javadoc
1   package de.spiritscorp.datasync.io;
2   
3   /*-
4    * 		Data Sync
5    *
6    * 		Copyright ©   2022    The Spirit
7    * 		@email                        thespirit@spiritscorp.network
8    *
9    * 		This program is free software; you can redistribute it and/or modify
10   * 		it under the terms of the GNU General Public License as published by
11   * 		the Free Software Foundation; either version 3 of the License, or
12   * 		(at your option) any later version.
13   *
14   * 		This program is distributed in the hope that it will be useful,
15   * 		but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * 		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17   * 		See the GNU General Public License for more details.
18   *
19   * 		You should have received a copy of the GNU General Public License
20   * 		along with this program. If not, see <http://www.gnu.org/licenses/>.
21   */
22  
23  import java.io.IOException;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.StandardCopyOption;
27  
28  /**
29   * Orchestrates sequential log file rotation and archival retention policies.
30   * Enforces size-based bounds on active logging outputs and automatically manages the
31   * lifecycle of historical backup archives by shifting indices and purging expired segments.
32   */
33  class Logrotater {
34  
35  	/** The maximum threshold size in bytes before a file rotation is enforced. */
36  	private final long maxFileSize;
37  
38  	/** The capacity limit of historical backup archives to keep on disk. */
39  	private final int maxBackupIndex;
40  
41  	/**
42  	 * Initializes the log rotation engine with strict performance thresholds and archival limits.
43  	 *
44  	 * @param maxFileSize    The upper boundary capacity in bytes an active log file can reach before triggering a rotation cycle.
45  	 * @param maxBackupIndex The maximum index depth of historical backup files to retain on the file system before oldest logs are purged.
46  	 */
47  	Logrotater( final long maxFileSize, final int maxBackupIndex ) {
48  		this.maxFileSize = maxFileSize;
49  		this.maxBackupIndex = maxBackupIndex;
50  	}
51  
52  	/**
53  	 * Evaluates the size of the primary log file on startup and initiates a cascading shift
54  	 * of backup history files if the configured size threshold is exceeded.
55  	 *
56  	 * @param baseLogPath The full comfiguration file path
57  	 */
58  	void executeLogRotationIfNeeded( final Path baseLogPath ) {
59  		if( !Files.exists( baseLogPath ) ) { return; }
60  
61  		try {
62  			long actualSize = Files.size( baseLogPath );
63  			if( actualSize < maxFileSize ) { return; }
64  			Debug.printDebug( "[Logrotater] Rotation starting at file size: %.2f Mb", ( (float) actualSize ) / ( 1024 * 1024 ) );
65  
66  			// Cascade existing backups downwards (e.g., log.4 -> log.5)
67  			for( int i = maxBackupIndex - 1; i >= 1; i-- ) {
68  				final Path sourceBackup = resolveBackupPath( baseLogPath, i );
69  				if( Files.exists( sourceBackup ) ) {
70  					final Path targetBackup = resolveBackupPath( baseLogPath, i + 1 );
71  					Files.move( sourceBackup, targetBackup, StandardCopyOption.REPLACE_EXISTING );
72  					Debug.printDebug( "[Logrotater] Rotate file %s -> %s ", sourceBackup.toString(), targetBackup.toString() );
73  				}
74  			}
75  
76  			// Move the current active log file to index 1 (e.g., log -> log.1)
77  			final Path firstBackup = resolveBackupPath( baseLogPath, 1 );
78  			Files.move( baseLogPath, firstBackup, StandardCopyOption.REPLACE_EXISTING );
79  			Debug.printDebug( "[Logrotater] Rotate file %s -> %s ", baseLogPath.toString(), firstBackup.toString() );
80  			Debug.printDebug( "[Logrotater] Finished successfully " );
81  		}catch( final IOException exception ) {
82  			Debug.printDebug( "[Logrotater Error] Execution of log rotation failed at -> %s with message -> %s", baseLogPath.toString(), exception.getMessage() );
83  			Debug.printException( this.getClass(), exception );
84  		}
85  	}
86  
87  	/**
88  	 * Resolves the system path for an archived backup file based on its history index.
89  	 *
90  	 * @param baseLogPath The full comfiguration file path
91  	 * @param index       The history index marker
92  	 * @return Path representing the target location of the historical log file
93  	 */
94  	private Path resolveBackupPath( final Path baseLogPath, final int index ) {
95  		return baseLogPath.resolveSibling( baseLogPath.getFileName().toString() + "." + index );
96  	}
97  }