我正在使用NLog登录 ASP.Net 应用程序并使用 Microsoft Sql Server 的数据库目标。
我有一些可选的日志参数,并不总是指定。我希望在未提供它们时将它们写为 null,但是NLog似乎总是将它们写为空字符串。
有没有办法将其配置为默认写入 null ?
这是一个老问题,但由于给出的解决方案有点“ hacky ”,我想给出我自己的解决方案,我认为它比 db 过程更容易实现,并且比使用案例更优雅。
您可以尝试使用NULLIF
比较 2 个表达式并在它们相等时返回 NULL 的函数来编写 NULL,否则它返回第一个表达式(msdn NULLIF page)。
这样,您的 NLog 配置文件上的 commandText 将如下所示:
INSERT INTO [dbo].[log] ([message], [optional])
VALUES (@message, NULLIF(@optional, ''))
NLog 使用 StringBuilder 来制作参数值。即使未指定参数,它也会将值初始化为 builder.ToString() ,它是空字符串。
您可以像这样更改您的命令文本:
INSERT INTO [dbo].[log] ([message], [optional])
VALUES
(
@message,
case
when len(@optional) = 0 then null
else @optional
end
)
不过,这对我来说似乎是个黑客。我希望有更好的解决方案。
NLog版本。4.7.4 添加了参数选项AllowDbNull
,所以你可以这样做:
<parameter name="@exception" layout="${exception:format=tostring}" allowDbNull="true" />
<parameter name="@correlationid" layout="${activityid}" dbType="DbType.Guid" allowDbNull="true" />
Nlog 会在allowDbNull="true"
.
[编辑]
也许比我在下面提出的更明显的解决方案是从使用 INSERT 语句更改为使用数据库过程进行记录。如果您使用数据库过程,那么您可以自己处理从空字符串到 null 的转换。我不确定您是否可以将数据库过程与 NLog 的数据库目标一起使用。Log4net 支持它,所以我猜 NLog 也支持。
这是一个示例(在对链接问题的回答中)我发现有人使用 NLog 使用存储过程登录到数据库的配置。
http://nlog-forum.1685105.n2.nabble.com/Using-a-stored-procedure-for-the-DB-Target-td2621725.html
我在这里看到:
http://nlog.codeplex.com/workitem/5418
抱怨它不起作用(至少在 NLog 2.0 beta 中)。
这两个示例之间的一个区别是工作示例使用“exec LoggingProcedureName ...”,而非工作示例使用“LoggingProcedureName ...”
希望这可以帮助。
[结束编辑]
我无法评论为什么 NLog 写入空字符串而不是 null 或如何使 NLog 写入 null 而不是空字符串,但我想知道您是否可以通过其他配置按照您想要的方式进行这项工作?
日志记录参数何时是可选的?您的代码中是否有某些地方您总是记录一些值,而其他地方您从不记录一些值?您(作为开发人员)能否知道哪些可选参数适用于您的应用程序的哪些部分?
您能否配置多个数据库目标,每个目标都指定了“正确”参数?然后,您可以将 Loggers 指向适合代码位置的特定数据库目标。
假设您的应用程序(按名称空间)划分为(通常)执行“之前”、“期间”和“之后”的代码。
在“之前”代码中,您可能正在记录参数 A。在“期间”代码中,您可能正在记录参数 B。在“之后”代码中,您可能正在记录参数 C。因此,您的记录表可能有列喜欢:
DateTime, Logger, LogLevel, A, B, C, Message, Exception
现在,您有一个数据库目标,它为每个日志记录语句插入所有这些值。
如果您有三个数据库目标插入如下值怎么办:
DataTime, Logger, LogLevel, A, Message, Exception
DataTime, Logger, LogLevel, B, Message, Exception
DataTime, Logger, LogLevel, C, Message, Exception
您可以像这样配置您的部分:
<rules>
<logger name="Before.*" minlevel="Trace" writeTo="databaseA" />
<logger name="During.*" minlevel="Trace" writeTo="databaseB" />
<logger name="After.*" minlevel="Trace" writeTo="databaseC" />
</rules>
显然,这个想法可能存在几个问题:
可能(或容易)将您的记录器划分为匹配参数的“可选性”。
可选参数的组合可能太多,无法实现这一点(可能与 1 相同的缺点)。
一次让数据库目标日志处于活动状态可能不是一个好主意。也许这会导致性能问题。
嗯,这就是我所拥有的。我什至不知道我的想法是否可行,更不用说它是否实用了。
可能更好的解决方案是让 NLog 在每个数据库参数上允许一个额外的属性,允许您说“发送 null 而不是空字符串”。
我想我应该建议您也可以在NLog 论坛中提出这个问题。该论坛上的“Craig”今天早些时候提出了相同(或类似)的问题。也许你是克雷格。
我使用稍微不同的方法。
因为我不喜欢编写查询,所以我创建了一个为我执行此操作的扩展,我的 NLog 配置如下所示:
<target xsi:type="Database" name="Log" commandText="[dbo].[Log]" dbProvider="System.Data.SqlClient" connectionString="..">
<parameter name="@Timestamp" layout="${longdate:universalTime=true}" />
<parameter name="@LogLevel" layout="${level:uppercase=true}" />
<parameter name="@Logger" layout="${logger}" />
<parameter name="@Message" layout="${message}" />
<parameter name="@Exception:null" layout="${onexception:${exceptionLayout}}" />
</target>
这里注意两点:
commandText="[dbo].[Log]"
必须遵循格式[Schema].[Table]
@Exception:null
地方null
没有<commandText>
元素,但我使用此扩展INSERT
从参数自动创建。
public static class NLogExtensions
{
// The commandText attribute must conatain at least the table name.
// Each identifier must be enclosed in square brackets: [schemaName].[tableName].
public static void GenerateDatabaseTargetInsertQueries(this NLog.Config.LoggingConfiguration config)
{
var tableNameMatcher = new Regex(@"^(\[(?<schemaName>.+?)\].)?\[(?<tableName>.+?)\]$");
var autoCommandTextDatabaseTargets =
config.AllTargets
.OfType<DatabaseTarget>()
.Where(x => tableNameMatcher.IsMatch(x.CommandText()))
.Select(x => x);
foreach (var databaseTarget in autoCommandTextDatabaseTargets)
{
databaseTarget.CommandText = databaseTarget.CreateCommandText();
}
}
internal static string CommandText(this DatabaseTarget databaseTarget)
{
return ((NLog.Layouts.SimpleLayout)databaseTarget.CommandText).OriginalText;
}
internal static string CreateCommandText(this DatabaseTarget databaseTarget)
{
const string insertQueryTemplate = "INSERT INTO {0}({1}) VALUES({2})";
return string.Format(
insertQueryTemplate,
databaseTarget.CommandText(),
string.Join(", ", databaseTarget.Parameters.Select(x => x.Name())),
string.Join(", ", databaseTarget.Parameters.Select(x =>
{
var sql =
x.Nullable()
? string.Format("NULLIF({0}, '')", x.FullName())
: x.FullName();
// Rename the SqlParameter because otherwise SqlCommand will complain about it.
x.Name = x.FullName();
return sql;
})));
}
}
和
public static class NLogDatabaseTarget
{
public static void GenerateInsertQueries()
{
NLog.LogManager.Configuration.GenerateDatabaseTargetInsertQueries();
}
}
此外,它可以解析参数名称并插入NULLIF
可空参数。
另一个扩展帮助我解析它:
public static class DatabaseParameterInfoExtensions
{
// https://regex101.com/r/wgoA3q/2
private static readonly Regex ParamRegex = new Regex("^(?<prefix>.)(?<name>[a-z0-9_\-]+)(?:[:](?<null>null))?", RegexOptions.IgnoreCase);
public static string Prefix(this DatabaseParameterInfo parameter)
{
return ParamRegex.Match(parameter.Name).Groups["prefix"].Value;
}
public static string Name(this DatabaseParameterInfo parameter)
{
return ParamRegex.Match(parameter.Name).Groups["name"].Value;
}
public static string FullName(this DatabaseParameterInfo parameter)
{
return string.Format("{0}{1}", parameter.Prefix(), parameter.Name());
}
public static bool Nullable(this DatabaseParameterInfo parameter)
{
return ParamRegex.Match(parameter.Name).Groups["null"].Success;
}
}
这意味着您需要将commandText="[dbo].[Log]"
属性添加到数据库目标,删除查询并将 添加:null
到可空列的参数名称中。
在代码中,您只需调用它,扩展就会发挥作用。
NLogDatabaseTarget.GenerateInsertQueries();