原文地址 https://github.com/serilog-mssql/serilog-sinks-mssqlserver
Serilog的接收器,他可以写入到Microsoft SQL Server数据库。此接收器将把日志事件数据写入数据表,还可以选择将属性存储在XML或JSON列中,以便查询它们。重要属性也可以写入单独列中。
Package - Serilog.Sinks.MSSqlServer
最低版本.NET Framework 4.6.2, .NET 6.0, .NET Standard 2.0
Quick Start 快速入门
最基础简单的初始化方式
Log.Logger = new LoggerConfiguration()
.WriteTo
.MSSqlServer(
connectionString: "Server=localhost;Database=LogDb;Integrated Security=SSPI;",
sinkOptions: new MSSqlServerSinkOptions { TableName = "LogEvents" })
.CreateLogger();
Sample Programs 示例程序
sample目录中的源代码提供了简单的示例程序,它们展示了通过不同目标框架的代码和配置来初始化接收器的不同方法。
Sink Configuration 接收器配置
接收器可以完全通过代码、使用配置文件(或其他类型的配置提供程序)、两者的组合或使用各种Serilog配置包进行配置。有两个配置注意事项:配置接收器本身,以及配置接收器使用的表。
这个接收器使用serilog典型的WriteTo的配置方式(或AuditTo,或类似的变体)。接收器是通过传递MSSqlServerSinkOptions对象进行配置。表通过传递可选的ColumnOptions对象进行配置。
接收器所有的配置方式都接受下面参数,排序可能不一样。建议使用命名参数,有些平台目标还有附加其他参数。
- connectionString 数据库连接字符串
- sinkOptions 接收器配置
- columnOptions 数据表可选列配置
- restrictedToMinimumLevel 最低日志级别
- formatProvider
- logEventFormatter
Basic Arguments 基本参数
至少connectionString
连接字符串和MSSqlServerSinkOptions.TableName表名是必须的。如果使用外部配置源,如XML文件或JSON文件,您可以使用命名的连接字符串,而不是提供完整的“raw”连接字符串。
MSSqlServerSinkOptions
对象的属性将在MSSqlServerSinkOptions Object节讨论。
表配置可选ColumnOptions
对象在ColumnOptions Object章节和其他章节深入讨论。
参数restrictedToMinimumLevel
设置LogEventLevel
日志消息级别,如果接收器设置了LevelSwitch,则忽略restrictedToMinimumLevel
的设置。
这个接收器是一个“周期性批处理接收器”,在将日志事件写入数据库之前,接收器将对日志事件按一定数量排队,然后按批次写入数据库。这里有一个超时时间,达到超时时间,批处理未达到设置的数量也会写入到数据库。默认批处理50行,超时时间5秒。
通过MSSqlServerSinkOptions.BatchPostingLimit设置批处理数量。
通过MSSqlServerSinkOptions.BatchPeriod设置超时时间。
在大容量日志记录环境考虑中增加批处理大小,在一个循环写入单条日志的测试中,默认批处理大小平均每秒约14000行,将批处理大小增加到1000行,写入速度将提高到每秒43000行。但是还要考虑风险,如果客户端或服务器崩溃,或者连接中断,可能会丢失一整批次的日志。您可以通过减少超时来降低影响。运行性能测试,为生产日志表定义和日志事件内容、网络设置和服务器配置找到最佳批处理大小设置。
有关formatProvider参数的详细信息,请参阅Serilog Wiki。
参数logEventFormatter可用于指定实现ITextFormatter的自定义呈现器,该呈现器将用于生成LogEvent列的内容。如果参数被省略或设置为null,将使用默认的JSON格式。更多格式化信息请参阅Custom text formatters
Platform-Specific Arguments 特定平台参数
从支持.NET Standard-style Microsoft.Extension.Configuration包的类库或应用程序配置接收器时,将接受这些附加参数。它们是可选的。
- appConfiguration
- sinkOptionsSection
- columnOptionsSection
如果使用命名连接字符串,appConfiguration 参数仅需要完整的configuration root。
接收器为了读取ConnectionStrings 节点,它需要访问整个配置对象。
如果通过外部配置接收器或日志表,必须通过同名参数引用sinkOptionsSection或columnOptionsSection。
External Configuration and Framework Targets 外部配置和框架目标
由于外部配置的方式已经应用在各种版本的.NET框架中,您应该了解目标框架影响了什么,哪些外部配置选项是可用的。System.Configurationrefers指的是使用XML-based的app.config或web.config文件。.NET Standard和各种兼容框架创建的所有扩展包统称为Microsoft.Extension.Configuration(M.E.C)。M.E.C通常被称为“JSON configuration配置”,尽管包支持许多其他配置源,包括环境变量、命令行、Azure密钥库、XML等
Your Framework | TFM | Project Types | External Configuration |
---|---|---|---|
.NET Framework 4.6.2+ | net462 | app or library | System.Configuration |
.NET Framework 4.6.2+ | net462 | app or library | Microsoft.Extensions.Configuration |
.NET Standard 2.0 | netstandard2.0 | library only | Microsoft.Extensions.Configuration |
.NET 6.0+ | net6.0 | app or library | System.Configuration |
.NET 6.0+ | net6.0 | app or library | Microsoft.Extensions.Configuration |
尽管某些框架中同时使用XML和M.E.Cconfiguration ,但不支持这样做,可能会产生意外的结果,并且会向SelfLog发出警告。如果您实际上需要多个配置源,M.E.Cbuilder-pattern构建器模式就是为了支持这一点而设计的,并且您的语法在各个配置源之间是一致的。
Code-Only (any .NET target)(任何.NET框架代码配置)
接收器的所有功能都可以通过代码进行配置。
var logDB = @"Server=...";
var sinkOpts = new MSSqlServerSinkOptions();
sinkOpts.TableName = "Logs";
var columnOpts = new ColumnOptions();
columnOpts.Store.Remove(StandardColumn.Properties);
columnOpts.Store.Add(StandardColumn.LogEvent);
columnOpts.LogEvent.DataLength = 2048;
columnOpts.PrimaryKey = columnOpts.TimeStamp;
columnOpts.TimeStamp.NonClusteredIndex = true;
var log = new LoggerConfiguration()
.WriteTo.MSSqlServer(
connectionString: logDB,
sinkOptions: sinkOpts,
columnOptions: columnOpts
).CreateLogger();
Code + Microsoft.Extensions.Configuration(代码和json同时配置)
项目可以使用Microsoft.Extensions.configuration生成(或注入)配置对象,并将其传递给接收器的配置方法。如果同时使用代码和外部配置,代码配置的MSSqlServerSinkOptions和ColumnOptions将作为基础配置,外部配置会在此基础上进行更新。
有关详细信息,请参阅 External Configuration Syntax。
var appSettings = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var logDB = @"Server=...";
var sinkOpts = new MSSqlServerSinkOptions { TableName = "Logs" };
var columnOpts = new ColumnOptions();
var log = new LoggerConfiguration()
.WriteTo.MSSqlServer(
connectionString: logDB,
sinkOptions: sinkOpts,
columnOptions: columnOpts,
appConfiguration: appSettings
).CreateLogger();
Code + System.Configuration(代码和xml同时配置)
项目可以从XML配置文件(如app.config或web.config)加载MSSqlServerSinkOptions和ColumnOptions对象。接收器配置方法会自动检查ConfigurationManager,因此不需要任何代码,也不需要额外的包,也没有可展示的代码。
有关详细信息,请参阅External Configuration Syntax。
External using Serilog.Settings.Configuration (外部Json)
需要配置包版本3.0.0或更高版本。
.NET Standard项目可以使用Serilog.Settings.Configuration包里的ReadFrom.Configuration()方法来配置Serilog。这将会应用所有的配置(包括appsettings.json和其它所有有效的Iconfiguration配置)。这个包能够配置MSSqlServerSinkOptions和ColumnOptions。
有关详细信息,请参阅External Configuration Syntax。
External using Serilog.Settings.AppSettings (外部Xml)
项目可以使用Serilog.Settings.AppSettings包的ReadFrom.AppSettings()方法通过xml配置接收器。这将应用app.config或web.config配置的参数。这独立于从外部XML文件配置MSSqlServerSinkOptions或ColumnOptions。有关详细信息,请参阅External Configuration Syntax。
Audit Sink Configuration(审计接收器配置)
审计日志接收器是非常重要的,所以写入日志事件必须成功,保证写入成功比写入性能更重要。与常规写入接收器不同的是,审计接收器失败时会抛出异常,所以应该封装在try/catch中。比如银行账户提款场景,记录提款失败的日志确写入失败了,并且没有任何提示,这样做银行肯定不愿意。
审计接收器的构造函数常规接收器接受的参数基本差不多,需要使用AuditTo配置。
- connectionString 数据库连接字符串
- sinkOptions 接收器配置
- columnOptions数据表可选列配置
- formatProvider
- logEventFormatter
审计接收器需要保证所有的日志事件写入成功,所以restrictedToMinimumLevel日志等级在这里不能配置。
因为审核接收器会立即写入日志事件。MSSqlServerSinkOptions.BatchPostingLimit (批处理数量)和MSSqlServerSinkOptions.BatchPeriod(批处理周期)在这里也不能配置。
对于M.E.C兼容的项目,提供了appConfiguration、sinkOptionsSection和columnOptionsSection参数,这与常规接收器配置基本一样。
Table Definition(表定义)
如果您不使用自动创建表的功能,您需要在数据库中创建一个日志事件表。特别要注意的是,您是否需要Id列(相关选项设置和性能影响的讨论请参阅Standard Columns)。下面显示的表定义是使用自动创建表的默认配置,没有更改任何sink选项。除此之外还有很多其他变化。请参考ColumnOptions Object,了解各种配置特性与表定义的关系。
重要提示:如果您提前创建日志事件表,则sink配置必须与该表完全匹配,否则可能会出现错误。
CREATE TABLE [Logs] (
[Id] int IDENTITY(1,1) NOT NULL,
[Message] nvarchar(max) NULL,
[MessageTemplate] nvarchar(max) NULL,
[Level] nvarchar(128) NULL,
[TimeStamp] datetime NOT NULL,
[Exception] nvarchar(max) NULL,
[Properties] nvarchar(max) NULL
CONSTRAINT [PK_Logs] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Permissions (权限)
至少需要对日志表具有SELECT和INSERT权限才能写入日志条目。(需要SELECT权限是因为sink的批处理行为使用批量插入,在写操作开始之前读取模式)。SQL权限是一个非常复杂的主题。以下是一个可能的解决方案示例(适用于SQL 2012或更高版本):
--创建角色SerilogAutoCreate
CREATE ROLE [SerilogAutoCreate];
GRANT SELECT ON sys.tables TO [SerilogAutoCreate];
GRANT SELECT ON sys.schemas TO [SerilogAutoCreate];
GRANT ALTER ON SCHEMA::[dbo] TO [SerilogAutoCreate]
GRANT CREATE TABLE ON DATABASE::[SerilogTest] TO [SerilogAutoCreate];
--创建角色SerilogWriter
CREATE ROLE [SerilogWriter];
GRANT SELECT TO [SerilogWriter];
GRANT INSERT TO [SerilogWriter];
--创建登录名
CREATE LOGIN [Serilog] WITH PASSWORD = 'password';
--创建用户
CREATE USER [Serilog] FOR LOGIN [Serilog] WITH DEFAULT_SCHEMA = dbo;
GRANT CONNECT TO [Serilog];
--分配角色
ALTER ROLE [SerilogAutoCreate] ADD MEMBER [Serilog];
ALTER ROLE [SerilogWriter] ADD MEMBER [Serilog];
创建SQL登录名Serilog,数据库用户名Serilog,将用户分配给SerilogAutoCreate和SerilogWriter角色,如果您提前创建数据库,则不需要创建SerialAutoCreate角色,如果您关注这个级别的安全性,推荐您执行此操作。
理想情况下,SerilogWriter角色应仅限于日志表,并且该表必须已经存在,才能使用特定于表的GRANT语句,因此如果您担心日志安全性,这也是您不想使用自动创建表的另一个原因。表级别的限制如下所示(假设您将日志表命名为SecuredLog):
GRANT SELECT ON [dbo].[SecuredLog] TO [SerilogWriter];
GRANT INSERT ON [dbo].[SecuredLog] TO [SerilogWriter];
有可以有许多变化。例如,您还可以创建一个专门用于日志记录的模式,并通过这种方式限制访问。
MSSqlServerSinkOptions Object (数据库接收器配置对象)
接收器的基本设置是使用MSSqlServerSinkOptions对象中的属性配置的:
- TableName 表名
- SchemaName 架构名
- AutoCreateSqlDatabase 是否自动创建数据库
- AutoCreateSqlTable 是否自动创建表
- EnlistInTransaction
- BatchPostingLimit 批处理数量限制
- BatchPeriod 批处理周期
- EagerlyEmitFirstEvent
- LevelSwitch
TableName
必需参数,指定写入日志表的名称。
SchemaName
可选参数,指定日志表的数据库架构,默认为dbo。
AutoCreateSqlDatabase
可选参数,如果日志数据库不存在,指定是否创建日志数据库。默认false,如果是true,则AutoCreateSqlTable也应设置为true。
AutoCreateSqlTable
可选参数,如果日志表不存在,指定是否创建日志表。默认false。
EnlistInTransaction
指定日志记录SQL命令参与环境事务的标志。它默认为false。日志记录操作可能会受到代码中周围Transactionscope事物的影响,导致在事务回滚时删除日志数据。默认情况下,接收器会将Enlist=false添加到传递的ConnectionString连接字符串中,从而防止这种情况发生。此选项可用于更改此行为,添加Enlist=true(这是SQL连接的默认值),使日志记录成为事务的一部分。只有当你有充分的理由并且真正知道自己在做什么时,才能将此选项更改为true!
BatchPostingLimit
指定常规接收器批处理写入的日志事件的最大数目。默认值为50。审核接收器不使用此设置,因为它会立即写入每个事件,而不是以批处理方式写入。
BatchPeriod
指定常规接收器批处理日志事件写入数据库的时间间隔。默认为5秒。审核接收器不使用此设置,因为它会立即写入每个事件,而不是以批处理方式写入。
EagerlyEmitFirstEvent
指定一个批处理急切地写入包含第一个接收到的事件的数据库,而不考虑BatchPostingLimit或BatchPeriod。它默认为true。审核接收器不使用此设置,因为它会立即写入每个事件,而不是以批处理方式写入。
LevelSwitch
允许在运行时更改最低级别的开关。如果设置了此项,则会忽略接收器配置方法中的参数restrictedToMinimumLevel。
ColumnOptions Object 表列配置
日志表的功能是通过设置ColumnOptions对象的属性来定义的:
- Store
- PrimaryKey
- ClusteredColumnstoreIndex
- DisableTriggers
- AdditionalColumns
Store 标准列列表
这是在写入日志事件时具有特殊处理的列的列表。这些内容在Standard Columns中进行了说明。Store存储集合中只应存在日志表中的标准列。这是StandardColumn枚举成员的List<>列表,因此您可以简单地添加或删除列来更改列表,与排序无关,ColumnOptions对象对于每个单独的标准列也有一个属性,用于访问列特定的设置。属性与标准列名(Id,Message等)匹配。这些内容在每个标准列的文档中进行了讨论。
PrimaryKey 主键
默认情况下,Id 标准列是表的主键。您可以将此属性设置到任何其他列(任意一个标准列或自定义列;请参阅自定义属性列Custom Property Columns )。SQL Server要求主键索引始终为NOT NULL,因此如果设置为true,列级别AllowNull属性将被重写。
主键是可选的,将此属性设置为null可创建不带主键的堆表。
注意:如果未将主键列上的NonClusteredIndex属性设置为true,则主键约束将创建为聚集索引。出于向后兼容性的原因,聚集索引是默认的,但一般来说,这不是用于日志记录目的的最佳选项(应用程序很少发出完全唯一的属性,对于查询使用唯一的自动递增Id列作为主键并不是特别有用)。
ClusteredColumnstoreIndex 聚集列存储索引
将此设置为true会将表更改为聚集列存储索引(CCI)格式。对CCI的完整讨论超出了本文档的范围,但通常它使用高压缩来显著提高搜索速度。它与表主键或非列存储聚集索引不兼容,并且支持(max)长度的字符数据列,需要SQL 2017或更高版本。
DisableTriggers 禁用触发器
禁用触发器可以显著提高批处理写入性能
AdditionalColumns 附加列
指定在日志表中自定义列的SqlColumn对象的Collection<>集合,有关详细信息,请参阅 Custom Property Columns。
SqlColumn Objects 列对象
ColumnOptions.Store列表中的每个标准列以及添加到AdditionalColumns集合中的任何自定义列都是具有以下属性的SqlColumn列对象:
- ColumnName
- PropertyName
- DataType
- AllowNull
- DataLength
- NonClusteredIndex
ColumnName
可以使用任何有效的SQL列名。标准列已指定默认名称,但这些名称可以在不影响其特殊处理的情况下进行更改。
PropertyName
要用作自定义列值的Serilog属性的可选名称。如果未提供,则使用与指定ColumnName同名的属性。它仅适用于在AdditionalColumns中的自定义列,而对于标准列则被忽略。
PropertyName属性名称可以是SomeProperty这样的简单属性名称,也可以是SomeProperty.SomeSubProperty.SomeThirdLevelProperty这样的层次引用子属性。这可以用于按照结构化日志记录的模式轻松地将其他列绑定到特定的子属性。请注意,不支持集合。这意味着像SomeProperty.SomeArray[2]这样的表达式将不起作用。
DataType
此属性几乎可以设置为System.Data.SqlDbType枚举中的任何值,与此接收器以前版本不同,完全支持端到端的SQL列类型,包括自动创建表格。由于使用.NET DataColumn对象而施加的早期限制不再适用,大多数标准列只支持SQL列类型的有限子集(通常只有一种类型),排除了一些特殊情况下的SQL列类型,如timestamp时间戳和udt,不推荐使用的类型(如text文本和image图像)将被排除,以下是支持的SQL列数据类型:
- bigint
- bit
- char
- date
- datetime
- datetime2
- datetimeoffset
- decimal
- float
- int
- money
- nchar
- nvarchar
- real
- smalldatetime
- smallint
- smallmoney
- time
- tinyint
- uniqueidentifier
- varchar
- xml
数字类型使用默认的精度和比例。对于数字类型,您有责任确保所写的值不超过基础SQL列数据类型的最小值/最大值。例如,SQL decimal类型默认为18位精度(小数位数为0),这意味着最大值为1018-1或99999999999999,而.NET decimal类型的最大值要高得多,为79228162514264337593543950335。
AllowNull
指定列是否可以存储SQL NULL值。其他一些功能(如PrimaryKey)有相关的限制,而一些标准列则施加了限制(例如,Id列从不允许null)。
DataLength
对于字符数据和二进制列,这定义了列大小(如果长度可变,则定义最大大小)。值-1表示(max)长度,是属性的默认值。如果列数据类型不支持此功能,则会忽略该设置。请注意,聚集列存储索引与SQL 2017之前的(max)长度列不兼容。
支持使用此属性的SQL列数据类型:
- char
- nchar
- nvarchar
- varchar
NonClusteredIndex
任何单独的列都可以定义为非聚集索引,包括表主键。请谨慎使用,索引会带来相对较高的写吞吐量损失。缓解这种情况的一种方法是保持非聚集索引脱机,并按计划重建索引。
Standard Columns 标准列
默认情况下(与前面显示的创建表的SQL DDL一致),这些列包含在新的ColumnOptions.Store列表中:
- StandardColumn.Id
- StandardColumn.Message
- StandardColumn.MessageTemplate
- StandardColumn.Level
- StandardColumn.TimeStamp
- StandardColumn.Exception
- StandardColumn.Properties
默认情况下(出于向后兼容性的原因),还有一个未包含的附加标准列:
- StandardColumn.LogEvent
只要基础表定义一致,就可以更改此列表:
//移除列
columnOptions.Store.Remove(StandardColumn.Properties);
//添加列
columnOptions.Store.Add(StandardColumn.LogEvent);
除了下面描述的特殊属性外,每个标准列还具有常见的列属性,如SqlColumn Objects中描述的ColumnName。
Id
Id列是可选的表标识列。它默认为int数据类型,但也可以配置为bigint。示例如何将数据类型更改为bigint:
var colOptions = new Serilog.Sinks.MSSqlServer.ColumnOptions();
colOptions.Id.DataType = System.Data.SqlDbType.BigInt;
Log.Logger = new LoggerConfiguration().WriteTo.MSSqlServer(columnOptions: colOptions)
AllowNull属性始终为false。如果它包含在表中,则它必须是一个自动递增的唯一标识列,并且是自动配置和自动创建的。
此接收器的早期版本假定Id列始终作为具有聚集索引的int identity标识主键存在。其他配置是可能的,也可能是优选的,但是由于向后兼容性的原因,这仍然是默认配置。仔细考虑您预期的日志记录量和查询要求。 默认设置在实际场景中并不理想,因为聚集索引主要在键用于排序或范围搜索时使用。 Id列很少会出现这种情况。
No Id column
无Id列:如果完全删除该列,则日志表将存储为无序堆表(只要不定义不同的聚集主键,这是不推荐的)。这是日志记录的理想写入速度场景,但是添加的任何非聚集索引都会略微降低写入性能。
Non-clustered primary key
非聚集主键:您也可以将列保留为标识主键,但使用非聚集索引。日志仍然存储为无序堆表,但写入非聚集索引的速度稍快。其他列上的非聚集索引将引用Id主键。但是,读取性能会略有下降,因为它需要两次读取(搜索非聚集索引,然后从Id中取消对堆行的引用)。
BigInt data type
BigInt数据类型:对于非常大的日志表,如果绝对需要标识列,则可能希望将Id定义为SQL BigInt数据类型。此8字节整数(相当于c# long整数)将允许9223372036854775807的最大标识值。这将略微降低读取和写入性能。
Message
此列存储格式化的输出(属性占位符替换为属性值)。它默认为nvarchar(max)。DataType属性只能设置为字符存储类型。
如果DataLength设置为不同于-1的特定值,则任何超过该长度的消息都将被有效地截断为该大小。示例:DataLength设置为15,消息为“this is a very long message”(不带引号),则存储在数据库中的截断文本将为:“this is a ve…”(同样不带引号。)。
Level
此列存储事件级别(Error, Information等)。出于向后兼容性的原因,它默认长度为128个字符,但建议使用12个字符。或者,StoreAsEnum属性可以设置为true,这将导致底层枚举整数值存储为SQL tinyint列。DataType属性只能设置为nvarchar或tinyint。将DataType设置为tinyint与将StoreAsEnum设置为true相同。
TimeStamp
此列将日志事件发送到Serilog的时间存储为SQL datetime(默认值)、datetime2或datetimeoffset类型。如果应该使用datetime2或datetimeoffset,则可以按如下方式进行配置。
var columnOptions = new ColumnOptions();
columnOptions.TimeStamp.DataType = SqlDbType.DateTimeOffset;
var columnOptions = new ColumnOptions();
columnOptions.TimeStamp.DataType = SqlDbType.DateTime2;
请注意,如果使用的日志数据库表具有datetimeoffset类型的TimeStamp列,则必须为datetimeoffset配置接收器。如果基础数据库将datetime2用于TimeStamp列,则必须将接收器配置为使用datetime2。另一方面,如果TimeStamp列的类型为datetime或datetime2,则不得配置datetimeoffset。如果不相应地配置数据类型,可能会导致日志表条目具有错误的时区偏移,或者由于日志记录过程中的异常而根本没有创建日志条目。
虽然TimeStamp似乎是一个很好的集群主键候选者,但即使是相对较低的日志记录也会发出相同的时间戳,迫使SQL Server在后台添加一个“uniqueifier”值(实际上是一个像整数一样的自动递增标识)。对于频繁的时间戳范围搜索和排序,非聚集索引更好。
当ConvertToUtc属性设置为true时,时间戳将调整为UTC标准。通常,时间戳值反映发出日志事件的机器的本地时间,包括当前时区信息。例如,如果事件是在东部时间07:00写入的,则东部时区相对于UTC为+4:00,因此在UTC转换后,时间戳将为11:00。偏移量存储为+0:00,但这不是GMT时区,因为UTC不使用偏移量(根据定义)。换一种说法,时区被丢弃并且不可恢复。UTC表示日期和时间,不包括时区信息。这样可以很容易地引用从不同或不断变化的时区编写的时间戳。
Exception
当异常作为日志事件的一部分被记录时,异常消息会自动存储在此处。DataType必须是nvarchar。
与Message和MessageTemplate列类似,将异常的DataLength设置为不同于-1的特定值将有效地将任何异常消息截断为DataLength中规定的长度。
Properties
此列将LogEvent日志事件属性值存储为XML。通常,您将使用此列或基于JSON的LogEvent列,但不能同时使用这两个列。
DataType默认为nvarchar,强烈建议不要更改,但也支持SQL xml类型。使用xml类型可以使SQL server将字符串数据转换为存储效率高的表示形式,该表示形式可以更快地进行搜索,但存在可测量的CPU开销。在提交到xml数据类型之前,请使用实际的工作负载仔细测试。
自定义属性列Custom Property Columns中介绍了ExcludeAdditionalProperties设置。
元素的名称可以由RootElementName、PropertyElementName、ItemElementName、DictionaryElementName、SequenceElementName、StructureElementName和UsePropertyKeyAsElementName选项控制。
UsePropertyKeyAsElementName选项如果设置为true,将使用属性键作为元素名称,而不是使用键作为属性的名称的“property”。
如果设置了OmitDictionaryContainerElement、OmitSequenceContainerElement或OmitStructureContainerElement,则将省略“dictionary”、“sequence”或“structure”容器元素,并且只包括子元素。
如果设置了OmitElementIfEmpty,则如果属性为空,则不会对其进行序列化。
LogEvent
此列将日志事件属性值存储为JSON。通常,您将使用此列或基于XML的“属性”列,但不能同时使用这两个列。此列的DataType必须始终是nvarchar。
ExcludeAddtionalProperties和ExcludeStandardColumns属性在自定义属性列 Custom Property Columns 中进行了描述。
默认情况下,此列的内容呈现为JSON,或者使用调用方作为参数logEventFormatter传递的自定义ITextFormatter。可以在接收器配置 Sink Configuration中找到详细信息。
Custom Property Columns(自定义属性列)
默认情况下,日志声明中包含的任何日志属性都将保存到XML properties列或JSON LogEvent列,但它们也可以通过AdditionalColumns(其他列)集合存储在自己的单独列中,这增加了写入操作的开销,但对于频繁查询的属性非常有用,仅需要ColumnName;默认配置为varchar(max)。
var columnOptions = new ColumnOptions
{
AdditionalColumns = new Collection<SqlColumn>
{
new SqlColumn
{ColumnName = "EnvironmentUserName", PropertyName = "UserName", DataType = SqlDbType.NVarChar, DataLength = 64},
new SqlColumn
{ColumnName = "UserId", DataType = SqlDbType.BigInt, NonClusteredIndex = true},
new SqlColumn
{ColumnName = "RequestUri", DataType = SqlDbType.NVarChar, DataLength = -1, AllowNull = false},
}
};
var log = new LoggerConfiguration()
.WriteTo.MSSqlServer(@"Server=...",
sinkOptions: new MSSqlServerSinkOptions { TableName = "Logs" },
columnOptions: columnOptions)
.CreateLogger();
在本例中,当日志事件包含任何属性UserName、UserId和RequestUri时,属性值将写入相应的列,属性名称必须完全匹配(不区分大小写)。对于UserName属性值将写入EnvironmentUserName列。
与以前版本的接收器不同,标准列名不是保留的,如果从ColumnOptions.Store列表中删除Id 标准列,您可以自由创建一个名为Id的新自定义列,接收器将像完全由您控制的任何其他自定义列一样处理该列。
请注意使用SqlDbType枚举来指定DataType数据类型。与早期版本的接收器不同,.NET system数据类型和DataColumn对象不再用于自定义列定义。
Excluding redundant data(排除冗余数据)
默认情况下,与自定义列匹配的属性仍将包含在保存到XML属性或JSON LogEvent列的数据中,这与结构化日志背后的理念一致,如果有需要,以后更容易将日志数据转换到另一个文档数据存储平台。但是,保存在自己列中的属性可以从这些catch-all列中排除。使用columnOptions.Properties.ExcludeAdditionalProperties参数从Properties XML列中排除冗余属性,如果使用JSON LogEvent列,则使用columnOptions.LogEvent.ExcludeAdditionalProperties。
标准列总是被排除在XML Properties属性列之外,但由于向后兼容性的原因,标准列被包括在JSON数据中。它们可以通过columnOptions.LogEvent.ExcludeStandardColumns从JSON LogEvent列中排除。
External Configuration Syntax (外部配置语法)
项目目标框架兼容System.Configuration的会自动支持基于XML的MSSqlServerSinkOptions参数和ColumnOptions表定义的配置(app.config或web.config),并且Serial.Settings.AppSettings包添加了其他直接接收器参数(如customFormatter或restrictedToMinimumLevel)的基于XML配置。
项目目标框架兼容Microsoft.Extension.Configuration的可以使用Serial.settings.Configurattion包或通过代码提供适当的参数来应用配置驱动的接收器设置和MSSqlServerSinkOptions或ColumnOptions设置。
MSSqlServerSinkOptions类的所有属性以及除properties.PropertyFilter谓词表达式之外的几乎所有ColumnOptions类都是可配置的,显示的所有元素和列表都是可选的,在大多数情况下,配置键名称与类属性名称匹配,但也有一些例外,例如,由于PrimaryKey在通过代码配置时是SqlColumn对象引用,因此外部配置使用primaryKeyColumnName设置来按名称标识主键。
自定义列和独立的标准列条目都支持相同的常规列属性(ColumnName、DataType等)在SqlColumn Objects章节中列出。为了简洁起见,以下记录配置语法的部分省略了其中许多属性。
如果将外部配置与通过代码进行的配置相结合,则除了通过代码提供的MSSqlServerSinkOptions和ColumnOptions对象外,还将应用外部配置更改(外部配置“覆盖”配置中定义的属性,但保留仅通过代码定义的属性)。
重要提示:以下某些示例没有反映出可以按原样复制粘贴的真实配置。显示的某些设置或属性是互斥的,下面列出这些设置或属性仅用于文档记录。
JSON (Microsoft.Extensions.Configuration)
键和值不区分大小写。这是配置接收器参数的示例
{
"Serilog": {
"Using": ["Serilog.Sinks.MSSqlServer"],
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "MSSqlServer",
"Args": {
"connectionString": "NamedConnectionString",
"sinkOptionsSection": {
"tableName": "Logs",
"schemaName": "EventLogging",
"autoCreateSqlTable": true,
"batchPostingLimit": 1000,
"period": "0.00:00:30"
},
"restrictedToMinimumLevel": "Warning",
"columnOptionsSection": { . . . }
}
}
]
}
}
从命名可以看出,columnOptionSection本身就是一个完整的配置节点,出于向后兼容性的原因,AdditionalColumns集合也可以从名为customColumns的键(此处未显示)填充。
"columnOptionsSection": {
"disableTriggers": true,
"clusteredColumnstoreIndex": false,
"primaryKeyColumnName": "Id",
"addStandardColumns": [ "LogEvent" ],
"removeStandardColumns": [ "MessageTemplate", "Properties" ],
"additionalColumns": [
{ "ColumnName": "EventType", "DataType": "int", "AllowNull": false },
{ "ColumnName": "Release", "DataType": "varchar", "DataLength": 32 },
{ "ColumnName": "EnvironmentUserName", "PropertyName": "UserName", "DataType": "varchar", "DataLength": 50 },
{ "ColumnName": "All_SqlColumn_Defaults",
"DataType": "varchar",
"AllowNull": true,
"DataLength": -1,
"NonClusteredIndex": false
}
],
"id": { "nonClusteredIndex": true },
"level": { "columnName": "Severity", "storeAsEnum": false },
"properties": {
"columnName": "Properties",
"excludeAdditionalProperties": true,
"dictionaryElementName": "dict",
"itemElementName": "item",
"omitDictionaryContainerElement": false,
"omitSequenceContainerElement": false,
"omitStructureContainerElement": false,
"omitElementIfEmpty": true,
"propertyElementName": "prop",
"rootElementName": "root",
"sequenceElementName": "seq",
"structureElementName": "struct",
"usePropertyKeyAsElementName": false
},
"timeStamp": { "columnName": "Timestamp", "convertToUtc": true },
"logEvent": {
"excludeAdditionalProperties": true,
"excludeStandardColumns": true
},
"message": { "columnName": "Msg" },
"exception": { "columnName": "Ex" },
"messageTemplate": { "columnName": "Template" }
}
XML ColumnOptions (System.Configuration)
键和值区分大小写。大小写必须完全匹配,如下所示。
、
<configSections>
<section name="MSSqlServerSettingsSection"
type="Serilog.Configuration.MSSqlServerConfigurationSection, Serilog.Sinks.MSSqlServer"/>
</configSections>
<MSSqlServerSettingsSection DisableTriggers="false"
ClusteredColumnstoreIndex="false"
PrimaryKeyColumnName="Id">
<!-- SinkOptions parameters -->
<TableName Value="Logs"/>
<SchemaName Value="EventLogging"/>
<AutoCreateSqlTable Value="true"/>
<BatchPostingLimit Value="150"/>
<BatchPeriod Value="00:00:15"/>
<!-- ColumnOptions parameters -->
<AddStandardColumns>
<add Name="LogEvent"/>
</AddStandardColumns>
<RemoveStandardColumns>
<remove Name="Properties"/>
</RemoveStandardColumns>
<Columns>
<add ColumnName="EventType" DataType="int"/>
<add ColumnName="EnvironmentUserName"
PropertyName="UserName"
DataType="varchar"
DataLength="50" />
<add ColumnName="Release"
DataType="varchar"
DataLength="64"
AllowNull="true"
NonClusteredIndex="false"/>
</Columns>
<Exception ColumnName="Ex" DataLength="512"/>
<Id NonClusteredIndex="true"/>
<Level ColumnName="Severity" StoreAsEnum="true"/>
<LogEvent ExcludeAdditionalProperties="true"
ExcludeStandardColumns="true"/>
<Message DataLength="1024"/>
<MessageTemplate DataLength="1536"/>
<Properties DataType="xml"
ExcludeAdditionalProperties="true"
DictionaryElementName="dict"
ItemElementName="item"
OmitDictionaryContainerElement="false"
OmitSequenceContainerElement="false"
OmitStructureContainerElement="false"
OmitElementIfEmpty="true"
PropertyElementName="prop"
RootElementName="root"
SequenceElementName="seq"
StructureElementName="struct"
UsePropertyKeyAsElementName="false"/>
<TimeStamp ConvertToUtc="true"/>
</MSSqlServerSettingsSection>
XML Sink (Serilog.Settings.AppSettings)
有关接收器配置的完整详细信息,请参阅Serial.Settings.AppSettings软件包文档。这是为该接收器设置一些配置参数的示例。
<add key="serilog:using:MSSqlServer" value="Serilog.Sinks.MSSqlServer" />
<add key="serilog:write-to:MSSqlServer.connectionString" value="EventLogDB"/>
<add key="serilog:write-to:MSSqlServer.tableName" value="Logs"/>
<add key="serilog:write-to:MSSqlServer.autoCreateSqlTable" value="true"/>
Troubleshooting (故障排除)
这是一个相对复杂的接收器,遇到新的问题寻求帮助之前,应先了解下常见问题,如果您发起了新的问题求助,请务必告诉我们您正在使用的所有Serilog软件包以及版本。向我们展示您的真实配置代码和任何外部配置源,以及重现该问题的简单代码示例。如果您收到错误消息,请包含确切的消息。
Always check SelfLog first (始终先检查SerfLog)
配置完成后,这个接收器运行了许多检查以确保一致性,某些配置问题会导致异常,但其他人可能只能通过Serilog的SelfLog功能生成警告,在运行时,异常会通过SelfLog以静默方式报告,请参阅主serilog文档中的Debugging and Diagnostics调试和诊断以启用SelfLog输出。
Always call Log.CloseAndFlush (始终调用Log.CloseAndFlush)
任何Serilog应用程序在关闭前都应调用Log.CloseAndFlush,这点在这样的接收器中尤为重要,它是一个“周期性的批处理接收器”,这意味着出于性能原因,日志事件记录是分批写入的,调用Log.CloseAndFlush应保证内存中的任何批都将写入数据库(但请阅读下面的Visual Studio备注)。您可能希望将Log.CloseAndFlush调用放在console-driven apps控制台驱动的应用程序中的finally块中,其中Main循环控制整个启动和关闭过程,有关示例,请参阅Serial.AspNetCore示例代码。当记录器注册为单例时,更特殊的场景(如依赖项注入)可能需要挂载到ProcessExit事件:
AppDomain.CurrentDomain.ProcessExit += (s, e) => Log.CloseAndFlush();
Consider batched sink SqlBulkCopy behavior (批处理接收器大数据量性能)
如果使用WriteTo初始化接收器,那么它将使用批处理接收器语义,这意味着它不会为每个日志调用直接向数据库发出SQL命令,但它在缓冲区中收集日志事件,然后使用SqlBulkCopy将其中的大部分异步写入数据库,如果SqlBulkCopy未能将批次的单行写入数据库,则整个批次将丢失,不幸的是,要找出批处理中哪些行导致了问题并不容易(而且可能只会对性能产生重大影响),因此,接收器无法在删除问题行的情况下轻松重试操作,典型的问题可能是数据(如日志消息)超过数据库中的字段长度,或者不能为null的字段为null,使用接收器的批处理版本时,避免使用根据数据库架构无效的数据创建日志事件。在将日志事件数据写入数据库之前,请使用包装类或Serilog Enrichers来验证和更正日志事件数据。
Test outside of Visual Studio (在Visual Studio之外进行测试)
当退出Visual Studio调试模式运行的程序时,正常的停机过程可能会中断,当Visual Studio完成调试时,会发出一个几乎即时的进程终止命令,这是ASP.NET和ASP.NET Core应用程序中特别常见的问题,其中Visual Studio在浏览器关闭后立即终止应用程序,即使是finally块通常也无法执行,如果没有记录到最后几件日志事件,尝试在Visual Studio之外的应用程序测试。
Try a dev package (尝试dev包)
如果你正在阅读一个似乎不起作用的功能,检查您是在阅读main分支还是dev分支的文档,默认情况下,大多数Serilog存储库都配置为使用dev分支,如果您只看dev分支文档中描述的内容,您将不得不引用一个dev版本的包。每当合并与代码相关的更改时,存储库都会自动生成一个新的开发包。
Are you really using this sink?(你真的在用这个接收器吗?)
请检查您的NuGet引用,并确认您引用的是Serial.Sinks.MSSqlServer。在.NET Core的早期,这个接收器有一个流行的Core-specific的Fork分支,但文档和NuGet项目URL指向此处。现在该软件包被标记为不推荐使用,关于这点我们仍然看到有些混乱。
.NET Framework apps must reference Microsoft.Data.SqlClient
(.NET Framework应用程序必须引用Microsoft.Data.SqlClient)
如果您正在.NET Framework应用程序中使用接收器,请确保在应用程序项目中添加对Microsoft.Data.SqlClient的nuget包引用,这是必要的,因为SqlClient中的错误可能会导致有关缺少Microsoft程序集的异常。详情见issue 283 和issue 208。
Querying Property Data (查询属性数据)
在查找特定的日志序列时,直接提取和查询属性列会很有帮助。SQL Server具有支持存储XML或JSON数据的列的查询语法。
LogEvent JSON
此功能需要SQL 2016或更新版本。给定以下JSON属性:
{
"Properties": {
"Action": "GetUsers",
"Controller": "UserController"
}
}
以下查询将提取Action属性,并使用SQL Server内置的JSON路径支持基于Controller属性限制查询。
SELECT
[Message], [TimeStamp], [Exception],
JSON_VALUE(LogEvent, '$.Properties.Action') AS Action
FROM [Logs]
WHERE
JSON_VALUE(LogEvent, '$.Properties.Controller') = 'UserController'
Properties XML
给定以下XML属性:
<properties>
<property key="Action">GetUsers</property>
<property key="Controller">UserController</property>
</properties>
以下查询将提取Action属性,并使用SQL Server内置的XQuery支持基于Controller属性限制查询。
SELECT
[Message], [TimeStamp], [Exception],
[Properties].value('(//property[@key="Action"]/node())[1]', 'nvarchar(max)') AS Action
FROM [Logs]
WHERE
[Properties].value('(//property[@key="Controller"]/node())[1]', 'nvarchar(max)') = 'UserController'
Breaking Changes(突破性变化)
Release 6.0.0(6.0.0版本)
Microsoft.Data.SqlClient已升级到>4.0.0,这引入了有关连接字符串的突破性更改。如果SQL Server不使用加密,则必须通过添加Encrypt=False在连接字符串中明确指定加密。否则,连接将失败,并出现SqlException。有关详细信息,请参阅SqlClient documentation。
Deprecated Features(不推荐的功能)
大多数不推荐使用的功能仍然可用,但它们被标记为[Obsolete]属性(这会导致项目中出现编译器警告),并将在未来的版本中删除。您应该尽快切换到替换实现。在可能的情况下,在内部将这些转换为替换实现,以便它们只存在于配置级别。
UseAzureManagedIdentity
由于在接收器版本5.8.0中更新了Microsoft.Data.SqlClient,因此可以使用SqlClient的Active Directory身份验证功能。您可以直接在连接字符串中指定一种受支持的AD身份验证方法,其中包括Azure托管标识。有关详细信息,请参阅SqlClient documentation。
AdditionalDataColumns
请改用AdditionalColumns集合。配置接收器不再依赖于.NET DataColumn对象或.NET系统类型。
Id.BigInt
请改用Id.DataType=SqlDb.BigInt。(BigInt属性仅在开发包中可用)。
Binary and VarBinary
由于Serilog在内部表示属性数据的方式,接收器不可能以字节数组的形式访问属性数据,因此接收器无法写入这些列类型。