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 }