/* * Copyright (c) 2023 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using ProtonVPN.Common.Core.Extensions; using ProtonVPN.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.AppLogs; namespace ProtonVPN.Logging { public class LogCleaner : ILogCleaner { public static readonly TimeSpan MAXIMUM_FILE_AGE = TimeSpan.FromDays(28); private readonly ILogger _logger; public LogCleaner(ILogger logger) { _logger = logger; } public void Clean(string logPath, int maxFiles) { _logger.Info($"Checking for files to be deleted in log folder '{logPath}'. The maximum number of files allowed is {maxFiles}."); IList files = GetFiles(logPath).ToList(); _logger.Info($"The folder '{logPath}' has {files.Count} files."); IList filesToDelete = GetFilesToDelete(files, maxFiles); DeleteFiles(filesToDelete); } private IEnumerable GetFiles(string path) { try { DirectoryInfo directoryInfo = new(path); return directoryInfo.GetFiles(); } catch (Exception e) when (e.IsFileAccessException()) { _logger.Error($"An error occurred when reading the files from the log folder '{path}'.", e); return new FileInfo[0]; } } private IList GetFilesToDelete(IList files, int maxFiles) { IList filesExceedingLimit = GetFilesExceedingLimit(files, maxFiles); IList oldFiles = GetOldFiles(files); IList filesToDelete = filesExceedingLimit.Concat(oldFiles).Distinct().ToList(); string fileNamesToDelete = GetFileNames(filesToDelete); _logger.Info($"The log folder has {filesToDelete.Count} files to delete.{fileNamesToDelete}"); return filesToDelete; } private IList GetFilesExceedingLimit(IList files, int maxFiles) { IList filesExceedingLimit = files .OrderByDescending(LastWriteTime) .Skip(maxFiles) .ToList(); string fileNamesExceedingLimit = GetFileNames(filesExceedingLimit); _logger.Info($"The log folder has {filesExceedingLimit.Count} files exceeding the limit of {maxFiles} files.{fileNamesExceedingLimit}"); return filesExceedingLimit; } private DateTime LastWriteTime(FileInfo file) { try { return file.LastWriteTimeUtc; } catch (Exception e) when (e.IsFileAccessException() || e is ArgumentOutOfRangeException) { _logger.Error(e.Message); return DateTime.MaxValue; } } private string GetFileNames(IList files) { string fileNames = string.Empty; if (files.Any()) { fileNames = $" File names: {string.Join(",", files.Select(fi => fi.Name))}"; } return fileNames; } private IList GetOldFiles(IList files) { DateTime minimumDate = DateTime.UtcNow.Subtract(MAXIMUM_FILE_AGE); IList oldFiles = files.Where(t => LastWriteTime(t) < minimumDate).ToList(); string oldFileNames = GetFileNames(oldFiles); _logger.Info($"The log folder has {oldFiles.Count} old files with a last write date before {minimumDate:O}.{oldFileNames}"); return oldFiles; } private void DeleteFiles(IList files) { foreach (FileInfo file in files) { DeleteFile(file.FullName); } } private void DeleteFile(string fileName) { try { File.Delete(fileName); _logger.Info($"Successfully deleted the file '{fileName}'."); } catch (Exception e) when (e.IsFileAccessException()) { _logger.Error($"An error occurred when deleting the file '{fileName}'.", e); } } } }