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
|
// 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
{
/// <summary>
/// Auto-rotating log file.
/// </summary>
public class LogFile : Concurrent<LogFile>
{
public string FilePath { get; }
public int MaxSize { get; }
public int TruncSize { get; }
public List<byte[]> Delimiters { get; }
/// <summary>
/// Create auto-rotating log file. Upon reaching <see cref="maxSize"/> the file is
/// truncated to <see cref="truncSize"/>. If any <see cref="delimiters"/> are specified,
/// the log is further truncated to align with the first record delimiter.
/// </summary>
/// <param name="path">
/// Path to log file
/// </param>
/// <param name="maxSize">
/// Maximum size of log file.
/// Log is truncated if it grows to a length of 'maxSize' or greater.
/// </param>
/// <param name="truncSize">
/// Size to which the log file is truncated to.
/// </param>
/// <param name="delimiters">
/// Log record delimiter(s).
/// After truncating, the start of file will align with the first delimiter.
/// </param>
/// <exception cref="ArgumentException">
/// * <see cref="path"/> contains invalid characters.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// * <see cref="maxSize"/> is zero or negative.
/// * <see cref="truncSize"/> is zero or negative.
/// * <see cref="truncSize"/> is greater than <see cref="maxSize"/>.
/// </exception>
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);
}
}
}
}
|