Skip to content

Commit a36651e

Browse files
committed
feat: (utils) 更新本地循环日志文件功能
1 parent ef32b7f commit a36651e

13 files changed

+799
-222
lines changed
Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
1-
// -----------------------------------------------------------------------
2-
// <copyright file="FileLoggerFactoryExtensions.cs" company="OSharp开源团队">
3-
// Copyright (c) 2014-2017 OSharp. All rights reserved.
4-
// </copyright>
5-
// <site>http://www.osharp.org</site>
6-
// <last-editor></last-editor>
7-
// <last-date>2017-09-17 21:17</last-date>
8-
// -----------------------------------------------------------------------
9-
101
using System;
112

3+
using Microsoft.Extensions.DependencyInjection;
124
using Microsoft.Extensions.Logging;
135

14-
using OSharp.Logging.RollingFile;
6+
using OSharp.Logging.RollingFile.Formatters;
157

168

17-
//power by https://github.com/andrewlock/NetEscapades.Extensions.Logging
18-
namespace Microsoft.Extensions.DependencyInjection
9+
namespace OSharp.Logging.RollingFile
1910
{
2011
/// <summary>
21-
/// Extensions for adding the <see cref="FileLoggerProvider" /> to the <see cref="ILoggingBuilder" />
12+
/// Extensions for adding the <see cref="OSharp.Logging.RollingFile.FileLoggerProvider" /> to the <see cref="ILoggingBuilder" />
2213
/// </summary>
2314
public static class FileLoggerFactoryExtensions
2415
{
@@ -29,20 +20,21 @@ public static class FileLoggerFactoryExtensions
2920
public static ILoggingBuilder AddFile(this ILoggingBuilder builder)
3021
{
3122
builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
23+
builder.Services.AddSingleton<ILogFormatter, SimpleLogFormatter>();
24+
#if NETCOREAPP3_0
25+
builder.Services.AddSingleton<ILogFormatter, JsonLogFormatter>();
26+
#endif
3227
return builder;
3328
}
34-
29+
3530
/// <summary>
3631
/// Adds a file logger named 'File' to the factory.
3732
/// </summary>
3833
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
3934
/// <param name="filename">Sets the filename prefix to use for log files</param>
4035
public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string filename)
4136
{
42-
builder.AddFile(options =>
43-
{
44-
options.FileName = filename;
45-
});
37+
builder.AddFile(options => options.FileName = filename);
4638
return builder;
4739
}
4840

@@ -63,4 +55,4 @@ public static ILoggingBuilder AddFile(this ILoggingBuilder builder, Action<FileL
6355
return builder;
6456
}
6557
}
66-
}
58+
}

src/OSharp.Utils/Logging/RollingFile/FileLoggerOptions.cs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22

33
using OSharp.Logging.RollingFile.Internal;
44

@@ -8,18 +8,19 @@ namespace OSharp.Logging.RollingFile
88
/// <summary>
99
/// Options for file logging.
1010
/// </summary>
11-
///power by https://github.com/andrewlock/NetEscapades.Extensions.Logging
1211
public class FileLoggerOptions : BatchingLoggerOptions
1312
{
1413
private int? _fileSizeLimit = 10 * 1024 * 1024;
1514
private int? _retainedFileCountLimit = 2;
16-
private string _fileName = "log-";
17-
15+
private int? _filesPerPeriodicityLimit = 10;
16+
private string _fileName = "logs-";
17+
private string _extension = "txt";
18+
1819

1920
/// <summary>
2021
/// Gets or sets a strictly positive value representing the maximum log size in bytes or null for no limit.
21-
/// Once the log is full, no more messages will be appended.
22-
/// Defaults to <c>10MB</c>.
22+
/// Once the log is full, no more messages will be appended, unless <see cref="FilesPerPeriodicityLimit"/>
23+
/// is greater than 1. Defaults to <c>10MB</c>.
2324
/// </summary>
2425
public int? FileSizeLimit
2526
{
@@ -34,6 +35,24 @@ public int? FileSizeLimit
3435
}
3536
}
3637

38+
/// <summary>
39+
/// Gets or sets a value representing the maximum number of files allowed for a given <see cref="Periodicity"/> .
40+
/// Once the specified number of logs per periodicity are created, no more log files will be created. Note that these extra files
41+
/// do not count towards the RetrainedFileCountLimit. Defaults to <c>1</c>.
42+
/// </summary>
43+
public int? FilesPerPeriodicityLimit
44+
{
45+
get { return _filesPerPeriodicityLimit; }
46+
set
47+
{
48+
if (value <= 0)
49+
{
50+
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FilesPerPeriodicityLimit)} must be greater than 0.");
51+
}
52+
_filesPerPeriodicityLimit = value;
53+
}
54+
}
55+
3756
/// <summary>
3857
/// Gets or sets a strictly positive value representing the maximum retained file count or null for no limit.
3958
/// Defaults to <c>2</c>.
@@ -68,11 +87,27 @@ public string FileName
6887
}
6988
}
7089

90+
/// <summary>
91+
/// Gets or sets the filename extension to use for log files.
92+
/// Defaults to <c>txt</c>.
93+
/// Will strip any prefixed .
94+
/// </summary>
95+
public string Extension
96+
{
97+
get { return _extension; }
98+
set { _extension = value?.TrimStart('.'); }
99+
}
100+
101+
/// <summary>
102+
/// Gets or sets the periodicity for rolling over log files.
103+
/// </summary>
104+
public PeriodicityOptions Periodicity { get ;set; } = PeriodicityOptions.Daily;
105+
71106
/// <summary>
72107
/// The directory in which log files will be written, relative to the app process.
73108
/// Default to <c>Logs</c>
74109
/// </summary>
75110
/// <returns></returns>
76-
public string LogDirectory { get; set; } = "Log";
111+
public string LogDirectory { get; set; } = "Logs";
77112
}
78113
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in https://github.com/aspnet/Logging for license information.
3+
// https://github.com/aspnet/Logging/blob/2d2f31968229eddb57b6ba3d34696ef366a6c71b/src/Microsoft.Extensions.Logging.AzureAppServices/Internal/FileLoggerProvider.cs
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
12+
using Microsoft.Extensions.Logging;
13+
using Microsoft.Extensions.Options;
14+
15+
using OSharp.Logging.RollingFile.Formatters;
16+
using OSharp.Logging.RollingFile.Internal;
17+
18+
19+
namespace OSharp.Logging.RollingFile
20+
{
21+
/// <summary>
22+
/// An <see cref="ILoggerProvider" /> that writes logs to a file
23+
/// </summary>
24+
[ProviderAlias("File")]
25+
public class FileLoggerProvider : BatchingLoggerProvider
26+
{
27+
private readonly string _path;
28+
private readonly string _fileName;
29+
private readonly string _extension;
30+
private readonly int? _maxFileSize;
31+
private readonly int? _maxRetainedFiles;
32+
private readonly int _maxFileCountPerPeriodicity;
33+
private readonly PeriodicityOptions _periodicity;
34+
35+
/// <summary>
36+
/// Creates an instance of the <see cref="OSharp.Logging.RollingFile.FileLoggerProvider" />
37+
/// </summary>
38+
/// <param name="options">The options object controlling the logger</param>
39+
/// <param name="formatter"></param>
40+
public FileLoggerProvider(IOptionsMonitor<FileLoggerOptions> options, IEnumerable<ILogFormatter> formatter) : base(options, formatter)
41+
{
42+
var loggerOptions = options.CurrentValue;
43+
_path = loggerOptions.LogDirectory;
44+
_fileName = loggerOptions.FileName;
45+
_extension = string.IsNullOrEmpty(loggerOptions.Extension) ? null : "." + loggerOptions.Extension;
46+
_maxFileSize = loggerOptions.FileSizeLimit;
47+
_maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
48+
_maxFileCountPerPeriodicity = loggerOptions.FilesPerPeriodicityLimit ?? 1;
49+
_periodicity = loggerOptions.Periodicity;
50+
}
51+
52+
53+
/// <inheritdoc />
54+
protected override async Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken cancellationToken)
55+
{
56+
Directory.CreateDirectory(_path);
57+
58+
foreach (var group in messages.GroupBy(GetGrouping))
59+
{
60+
var baseName = GetBaseName(group.Key);
61+
var fullName = GetLogFilePath(baseName, group.Key);
62+
63+
if (fullName == null)
64+
{
65+
return;
66+
}
67+
68+
using (var streamWriter = File.AppendText(fullName))
69+
{
70+
foreach (var item in group)
71+
{
72+
await streamWriter.WriteAsync(item.Message);
73+
}
74+
}
75+
}
76+
77+
RollFiles();
78+
}
79+
80+
private string GetLogFilePath(string baseName, (int Year, int Month, int Day, int Hour, int Minute) fileNameGrouping)
81+
{
82+
if (_maxFileCountPerPeriodicity == 1)
83+
{
84+
var fullPath = Path.Combine(_path, $"{baseName}{_extension}");
85+
return IsAvailable(fullPath) ? fullPath : null;
86+
}
87+
88+
var counter = GetCurrentCounter(baseName);
89+
90+
while (counter < _maxFileCountPerPeriodicity)
91+
{
92+
var fullName = Path.Combine(_path,$"{baseName}.{counter}{_extension}");
93+
if (!IsAvailable(fullName))
94+
{
95+
counter++;
96+
continue;
97+
}
98+
99+
return fullName;
100+
}
101+
102+
return null;
103+
104+
bool IsAvailable(string filename)
105+
{
106+
var fileInfo = new FileInfo(filename);
107+
return !(_maxFileSize > 0 && fileInfo.Exists && fileInfo.Length > _maxFileSize);
108+
}
109+
}
110+
111+
private int GetCurrentCounter(string baseName)
112+
{
113+
try
114+
{
115+
var files = Directory.GetFiles(_path, $"{baseName}.*{_extension}");
116+
if (files.Length == 0)
117+
{
118+
// No rolling file currently exists with the base name as pattern
119+
return 0;
120+
}
121+
122+
// Get file with highest counter
123+
var latestFile = files.OrderByDescending(file => file).First();
124+
125+
var baseNameLength = Path.Combine(_path, baseName).Length + 1;
126+
var fileWithoutPrefix = latestFile
127+
.AsSpan()
128+
.Slice(baseNameLength);
129+
var indexOfPeriod = fileWithoutPrefix.IndexOf('.');
130+
if (indexOfPeriod < 0)
131+
{
132+
// No additional dot could be found
133+
return 0;
134+
}
135+
136+
var counterSpan = fileWithoutPrefix.Slice(0, indexOfPeriod);
137+
if (int.TryParse(counterSpan.ToString(), out var counter))
138+
{
139+
return counter;
140+
}
141+
142+
return 0;
143+
}
144+
catch (Exception)
145+
{
146+
return 0;
147+
}
148+
149+
}
150+
151+
private string GetBaseName((int Year, int Month, int Day, int Hour, int Minute) group)
152+
{
153+
switch (_periodicity)
154+
{
155+
case PeriodicityOptions.Minutely:
156+
return $"{_fileName}{group.Year:0000}{group.Month:00}{group.Day:00}{group.Hour:00}{group.Minute:00}";
157+
case PeriodicityOptions.Hourly:
158+
return $"{_fileName}{group.Year:0000}{group.Month:00}{group.Day:00}{group.Hour:00}";
159+
case PeriodicityOptions.Daily:
160+
return $"{_fileName}{group.Year:0000}{group.Month:00}{group.Day:00}";
161+
case PeriodicityOptions.Monthly:
162+
return $"{_fileName}{group.Year:0000}{group.Month:00}";
163+
}
164+
throw new InvalidDataException("Invalid periodicity");
165+
}
166+
167+
private (int Year, int Month, int Day, int Hour, int Minute) GetGrouping(LogMessage message)
168+
{
169+
return (message.Timestamp.Year, message.Timestamp.Month, message.Timestamp.Day, message.Timestamp.Hour, message.Timestamp.Minute);
170+
}
171+
172+
/// <summary>
173+
/// Deletes old log files, keeping a number of files defined by <see cref="FileLoggerOptions.RetainedFileCountLimit" />
174+
/// </summary>
175+
protected void RollFiles()
176+
{
177+
if (_maxRetainedFiles > 0)
178+
{
179+
var groupsToDelete = new DirectoryInfo(_path)
180+
.GetFiles(_fileName + "*")
181+
.GroupBy(file => GetFilenameForGrouping(file.Name))
182+
.OrderByDescending(f => f.Key)
183+
.Skip(_maxRetainedFiles.Value);
184+
185+
foreach (var groupToDelete in groupsToDelete)
186+
{
187+
foreach (var fileToDelete in groupToDelete)
188+
{
189+
fileToDelete.Delete();
190+
}
191+
}
192+
}
193+
194+
string GetFilenameForGrouping(string filename)
195+
{
196+
var hasExtension = !string.IsNullOrEmpty(_extension);
197+
var isMultiFile = _maxFileCountPerPeriodicity > 1;
198+
if (!hasExtension && !isMultiFile)
199+
return filename;
200+
201+
if(!isMultiFile || !hasExtension)
202+
return Path.GetFileNameWithoutExtension(filename);
203+
204+
return Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(filename));
205+
}
206+
}
207+
}
208+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Text;
2+
3+
using Microsoft.Extensions.Logging;
4+
5+
6+
namespace OSharp.Logging.RollingFile.Formatters
7+
{
8+
/// <summary>
9+
/// Formats log messages that are written to the log file
10+
/// </summary>
11+
public interface ILogFormatter
12+
{
13+
/// <summary>
14+
/// Gets the name of the formatter
15+
/// </summary>
16+
string Name { get; }
17+
18+
/// <summary>
19+
/// Writes the log message to the specified StringBuilder.
20+
/// </summary>
21+
/// <param name="logEntry">The log entry.</param>
22+
/// <param name="scopeProvider">The provider of scope data.</param>
23+
/// <param name="stringBuilder">The string builder for building the message to write to the log file.</param>
24+
/// <typeparam name="TState">The type of the object to be written.</typeparam>
25+
void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, StringBuilder stringBuilder);
26+
}
27+
}

0 commit comments

Comments
 (0)