C#-使用Serilog框架快速实现日志及其相关扩展
目录
一、Serilog日志实现
1、实现 ILogEventSink接口
2、日志类Log
3、日志级别LogLevel
4、ILogger接口
5、日志服务实现
6、日志视图View
7、ViewModel
二、功能扩展
1、日志扩展方法
2、Trace追踪扩展日志
3、自动滚动至底部
一、Serilog日志实现
安装NuGet包:Serilog
Sink有很多种,这里介绍两种:
Console接收器(安装Serilog.Sinks.Console);
File接收器(安装Serilog.Sinks.File);
MinimumLevel:最小记录级别
rollingInterval:生成日志文件周期
outputTemplate:输出日志模板
继承ILogEventSink接口实现 Emit:当Sink器接收到新日志时触发
通过该接口将接收器接收的日志添加进内部日志集合
将该接口实现类实例化对象通过WriteTo.Sink(myEventSink)与Logger绑定
1、实现 ILogEventSink接口
public class LogEveSink : ILogEventSink
{
readonly object _lock = new object();
static readonly Lazy<LogEveSink> _sink = new Lazy<LogEveSink>(() => new LogEveSink());
public static LogEveSink Instance => _sink.Value;
/// <summary>
/// 日志内部集合
/// </summary>
private ObservableCollection<Log> _logs;
/// <summary>
/// 绑定到前台的日志视图
/// </summary>
public ListCollectionView Logs { get; set; }
private LogEveSink()
{
_logs = new ObservableCollection<Log>();
Logs = new ListCollectionView(_logs);
}
//private readonly ITextFormatter _formatter =
// new MessageTemplateTextFormatter("{Message} Location:{FilePath}[{LineNumber}]");
public void Emit(LogEvent logEvent)
{
lock (_lock)
{
if (_logs.Count > 500)
{
if (!Application.Current.CheckAccess())
Application.Current.Dispatcher.Invoke(() =>
{
_logs.Clear();
});
else
_logs.Clear();
}
if (logEvent != null)
{
//var textWriter = new StringWriter();
//_formatter.Format(logEvent, textWriter);
if (!Application.Current.CheckAccess())
Application.Current?.Dispatcher.InvokeAsync(() =>
{
_logs.Insert(0, new Log()
{
Time = logEvent?.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff"),
Level = logEvent.Level,
User = "Auston",
Message = logEvent.MessageTemplate.ToString() //textWriter.ToString()
});
});
else
{
_logs.Insert(0, new Log()
{
Time = logEvent?.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff"),
Level = logEvent.Level,
User = "Auston",
Message = logEvent.MessageTemplate.ToString() //textWriter.ToString()
});
}
}
}
}
}
2、日志类Log
public class Log
{
public string Time { get; set; }
public LogEventLevel Level { get; set; }
public string User { get; set; }
public string Message { get; set; }
}
3、日志级别LogLevel
public enum LogLevel
{
INFO,
WARN,
ERROR,
DEBUG,
FATAL
}
4、ILogger接口
public interface ILogger
{
void WriteLog(string message, LogLevel level=LogLevel.INFO);
UserControl GetLogView();
}
5、日志服务实现
public class LoggerService : ILogger
{
public static LoggerService Default => new LoggerService();
Serilog.ILogger logger;
LogView logView = new LogView();
//string outputTemplate = "{NewLine}Date: {Timestamp:yyyy-MM-dd HH:mm:ss.fff}\tLevel: {Level}\tCallName: {SourceContext}->{MemberName}"
// + "{NewLine}Path: {FilePath}[{LineNumber}]"
// + "{NewLine}Message: {Message}";
string outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} " +
"[{Level:u3}] " +
"Message:{Message}{NewLine}" +
"{Exception}{NewLine}";
public LoggerService()
{
logger = new LoggerConfiguration()
.Enrich.FromLogContext()//记录相关上下文信息
.MinimumLevel.Debug()
.WriteTo.Sink(LogEveSink.Instance)
.WriteTo.File("Logs\\ALL\\.txt", rollingInterval: RollingInterval.Day, outputTemplate: outputTemplate)
.WriteTo.Logger(log => log.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Error)
.WriteTo.File("Logs\\Error\\.txt", rollingInterval: RollingInterval.Day, outputTemplate: outputTemplate))
.CreateLogger();
}
public void WriteLog(string message, LogLevel level = LogLevel.INFO)
{
switch (level)
{
case LogLevel.DEBUG:
logger.Debug(message);
break;
case LogLevel.INFO:
logger.Information(message);
break;
case LogLevel.WARN:
logger.Warning(message);
break;
case LogLevel.ERROR:
logger.Error(message);
break;
case LogLevel.FATAL:
logger.Fatal(message);
break;
default:
logger.Verbose(message);
break;
}
}
public UserControl GetLogView()
{
return logView;
}
}
6、日志视图View
<UserControl x:Class="Test.Logger.View.LogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:vm="clr-namespace:Test.Logger.ViewModel"
x:Name="LogUC" d:DesignHeight="450"
d:DesignWidth="800" mc:Ignorable="d">
<UserControl.DataContext>
<vm:LogViewModel x:Name="viewmodel" />
</UserControl.DataContext>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
</ResourceDictionary.MergedDictionaries>
<!--<CollectionViewSource x:Key="SortSoruce" Source="{Binding ElementName=viewmodel, Path=LogSink.Logs}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription Direction="Descending" PropertyName="Time" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>-->
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Margin="5" VerticalAlignment="Center"
Orientation="Horizontal">
<RadioButton Margin="5"
Command="{Binding LogFilter}"
CommandParameter="0" Content="ALL"
FontWeight="Bold" GroupName="filter"
IsChecked="True" />
<RadioButton Margin="5"
Command="{Binding LogFilter}"
CommandParameter="1" Content="INFO"
FontWeight="Bold" Foreground="Green"
GroupName="filter" />
<RadioButton Margin="5"
Command="{Binding LogFilter}"
CommandParameter="2" Content="WARN"
FontWeight="Bold" Foreground="Orange"
GroupName="filter" />
<RadioButton Margin="5"
Command="{Binding LogFilter}"
CommandParameter="3" Content="ERROR"
FontWeight="Bold" Foreground="Red"
GroupName="filter" />
</StackPanel>
<DataGrid Grid.Row="1" Margin="5"
AutoGenerateColumns="False"
EnableColumnVirtualization="True"
EnableRowVirtualization="True" FontWeight="Bold"
IsReadOnly="True"
ItemsSource="{Binding ElementName=viewmodel, Path=LogSink.Logs}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<DataGrid.Columns>
<DataGridTextColumn Width="Auto"
Binding="{Binding Time}"
Header="Time" />
<DataGridTextColumn Width="Auto"
Binding="{Binding Level}"
Header="Level" />
<DataGridTextColumn Width="Auto"
Binding="{Binding User}"
Header="User" />
<DataGridTextColumn Width="*"
Binding="{Binding Message}"
Header="Message">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="TextBlock.TextAlignment" Value="Left" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Level}" Value="Information">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="Warning">
<Setter Property="Foreground" Value="Orange" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="Error">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</UserControl>
7、ViewModel
public class LogViewModel : ObservableObject
{
private LogEveSink logSink = LogEveSink.Instance;
public LogEveSink LogSink
{
get => logSink;
set => SetProperty(ref logSink, value);
}
/// <summary>
/// 日志过滤命令
/// </summary>
public RelayCommand<string> LogFilter
{
get
{
return new RelayCommand<string>((para) =>
{
DoFilter(para);
});
}
}
/// <summary>
/// 日志过滤命令注册函数
/// </summary>
/// <param name="mask"></param>
private void DoFilter(string mask)
{
switch (mask)
{
case "0":
LogSink.Logs.Filter = null;
break;
case "1":
LogSink.Logs.Filter = i => ((Log)i).Level == LogEventLevel.Information || ((Log)i).Level == LogEventLevel.Verbose || ((Log)i).Level == LogEventLevel.Debug;
break;
case "2":
LogSink.Logs.Filter = i => ((Log)i).Level == LogEventLevel.Warning;
break;
case "3":
LogSink.Logs.Filter = i => ((Log)i).Level == LogEventLevel.Error || ((Log)i).Level == LogEventLevel.Fatal;
break;
}
LogSink.Logs.Refresh();
}
}
二、功能扩展
1、日志扩展方法
static class LogExtension
{
public static void CallError<T>(this ILogger logger, string message,
[CallerMemberName] string meberName = "",
[CallerFilePath] string filepath = "",
[CallerLineNumber] int lineNum = 0)
=> logger.ForContext<T>()
.ForContext("MemberName", meberName)
.ForContext("FilePath", filepath)
.ForContext("LineNumber", lineNum)
.Error(message);
public static void CallError<T>(this ILogger logger, Exception e, string message,
[CallerMemberName] string meberName = "",
[CallerFilePath] string filepath = "",
[CallerLineNumber] int lineNum = 0)
=> logger.ForContext<T>()
.ForContext("MemberName", meberName)
.ForContext("FilePath", filepath)
.ForContext("LineNumber", lineNum)
.Error(e, message);
}
2、Trace追踪扩展日志
继承抽象类TraceListener,重写方法TraceEvent
注意:添加监听对象Trace.Listeners.Add(this),推荐放到App.cs;
public class TraceLog
{
public string Message { get; set; }
public LogLevel Level { get; set; }
}
public class LoggerTraceListener : TraceListener
{
public static LoggerTraceListener Default => new LoggerTraceListener();
public LoggerTraceListener()
{
Trace.Listeners.Add(this);
}
ILogger logger;
ConcurrentQueue<TraceLog> _traceLogs = new ConcurrentQueue<TraceLog>();
public void InitLogger()
{
logger = IOCService.Instance.AccessService<ILogger>();
if (logger != null)
{
Task.Run(() =>
{
while (_traceLogs.TryDequeue(out TraceLog log))
{
logger.WriteLog(log.Message, log.Level);
}
});
}
}
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
{
LogLevel logLevel = LogLevel.INFO;
switch (eventType)
{
case TraceEventType.Error:
logLevel = LogLevel.ERROR;
break;
case TraceEventType.Warning:
logLevel = LogLevel.WARN;
break;
case TraceEventType.Information:
logLevel = LogLevel.INFO;
break;
default:
logLevel = LogLevel.DEBUG;
break;
}
if (logger != null)
{
logger.WriteLog(message, logLevel);
return;
}
_traceLogs.Enqueue(new TraceLog() { Message = message, Level = logLevel });
}
public override void Write(string message)
{
//MessageBox.Show(message);
}
public override void WriteLine(string message)
{
//MessageBox.Show(message + "\r\n");
}
}
3、自动滚动至底部
通过ObservableCollection类的CollectionChanged事件实现日志自动滚动到底部:
集合改变触发事件,更改附加属性AutoScroll值,值更改触发CallBack将日志滚动到底部;
注意:MouseEnter与MouseLeave两事件的响应原因:查看日志时,防止日志自动滚动到底部;
<DataGrid attach:ScrollHelper.AutoScroll="{Binding AutoScroll}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
ItemsSource="{Binding LogService.Logs}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseEnterCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Time}" Header="时间" />
<DataGridTextColumn Binding="{Binding Lev}" Header="级别" />
<DataGridTextColumn Binding="{Binding Message}" Header="信息">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="TextBlock.TextAlignment" Value="Left" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Lev}" Value="Error">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Lev}" Value="Warn">
<Setter Property="Foreground" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
public LogService LogService { get; set; }=LogService.GetInstance();
private bool _autoScroll;
public bool AutoScroll
{
get { return _autoScroll; }
set => SetProperty(ref _autoScroll, value);
}
[RelayCommand]
public void MouseEnter()
{
LogService._logs.CollectionChanged -= Scroll;
}
[RelayCommand]
public void MouseLeave()
{
LogService._logs.CollectionChanged += Scroll;
}
private void Scroll(object sender, NotifyCollectionChangedEventArgs e)
{
AutoScroll = !AutoScroll;
}
public MainWinViewModel()
{
LogService.OpenListen();
LogService._logs.CollectionChanged += Scroll;
}