// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace QtVsTools.Core.Common { public static partial class Utils { /// /// Auto-rotating log file. /// public class LogFile : Concurrent { public string FilePath { get; } public int MaxSize { get; } public int TruncSize { get; } public List Delimiters { get; } /// /// Create auto-rotating log file. Upon reaching the file is /// truncated to . If any are specified, /// the log is further truncated to align with the first record delimiter. /// /// /// Path to log file /// /// /// Maximum size of log file. /// Log is truncated if it grows to a length of 'maxSize' or greater. /// /// /// Size to which the log file is truncated to. /// /// /// Log record delimiter(s). /// After truncating, the start of file will align with the first delimiter. /// /// /// * contains invalid characters. /// /// /// * is zero or negative. /// * is zero or negative. /// * is greater than . /// public LogFile(string path, int maxSize, int truncSize, params string[] delimiters) { FilePath = path switch { { Length: > 0 } when !Path.GetInvalidPathChars().Any(path.Contains) => path, _ => throw new ArgumentException(nameof(path)) }; MaxSize = maxSize switch { > 0 => maxSize, _ => throw new ArgumentOutOfRangeException(nameof(maxSize)) }; TruncSize = truncSize switch { > 0 when truncSize < maxSize => truncSize, _ => throw new ArgumentOutOfRangeException(nameof(truncSize)) }; Delimiters = delimiters?.Select(Encoding.UTF8.GetBytes).ToList() ?? new(); } public void Write(string logEntry) { var data = Encoding.UTF8.GetBytes(logEntry); lock (StaticCriticalSection) { try { using var log = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, MaxSize); log.Seek(0, SeekOrigin.End); log.Write(data, 0, data.Length); if (log.Length > MaxSize) Rotate(log); log.Flush(); } catch (Exception e) { e.Log(); } } } private void Rotate(FileStream log) { var data = new byte[TruncSize]; log.Seek(-TruncSize, SeekOrigin.End); log.Read(data, 0, TruncSize); log.Seek(0, SeekOrigin.Begin); var idxStart = Delimiters switch { { Count: > 0 } => Delimiters .Select(data.IndexOfArray) .Where(x => x >= 0) .Append(TruncSize) .Min(), _ => 0 }; if (idxStart < TruncSize) log.Write(data, idxStart, TruncSize - idxStart); log.SetLength(TruncSize - idxStart); } } } }