我在网络服务中使用log4net,我想根据请求本身发送的用户参数设置每个请求的日志文件名。
我在配置
中定义了一个属性<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="ClaimLog" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="C:\projects\reports\ClaimRpt\Log\ClaimLog_%property{UserNameWCF}.txt" />
并将其设置在我的网络方法的开头
type ClaimServiceF() =
let log = LogManager.GetLogger("ClaimServiceF")
...
member this.BeginReport(sel: Model.Selection) : Model.RptResponse =
GlobalContext.Properties.Item("UserNameWCF") <- sel.User
let rptResp = new Model.RptResponse()
log.Info("Started")
match sel with
| null ->
我将F#库(SrvImplF.dll)包装在C#WCF项目(why?)中并使用Visual Studio的WCF测试客户端进行测试。
但是,日志文件名保持其初始值ClaimLog_(null).txt
,即使我可以设置断点并看到sel.User
已正确填充
我还尝试过appSettings的AppSettings类型提供程序
<appSettings>
<add key="my_service_log" value="C:\projects\reports\ClaimRpt\Log\ClaimLog_{UserNameWCF}.txt" />
</appSettings>
编码
open FSharp.Configuration
type Settings = AppSettings<"app.config">
...
let appender = LogManager.GetRepository().GetAppenders() |> Seq.find(fun x -> x.Name.Equals("ClaimLog") ) :?> log4net.Appender.RollingFileAppender
...
appender.File <- Settings.MyServiceLog.Replace("{UserNameWCF}",sel.User)
但是 - 除了我需要在dll下创建一个虚构的app.config并在那里复制app键,因为类型提供程序没有看到包装项目的app.config - 它会抛出异常
抛出异常:&#39; System.TypeInitializationException&#39;在SrvImplF.dll中 类型&#39; System.TypeInitializationException&#39;的例外情况发生在 SrvImplF.dll但未在用户代码中处理附加信息: 类型初始值设定项 &#39; FSharp.Configuration.AppSettingsTypeProvider&#39;抛出异常。
我认为这最后只是我的类型提供程序配置中的一个微不足道的错误,无论如何也直接输入值
let configFile = "C:\reports\ClaimRpt\Log\ClaimLog_{UserNameWCF}.txt" //Settings.MyServiceLog
appender.File <- configFile.Replace("{UserNameWCF}",sel.User)
没有例外,但日志文件名保持不变ClaimLog_(null).txt
答案 0 :(得分:2)
在设置属性后,您必须致电LogManager.GetLogger("ClaimServiceF")
。
请注意,您的方法很容易出现竞争条件。如果多个客户端同时访问您的应用程序,您最终可能会记录到错误的文件。例如假设以下两个并行请求的执行顺序:
// incoming request A
GlobalContext.Properties.Item("UserNameWCF") <- sel.User
// incoming request B
GlobalContext.Properties.Item("UserNameWCF") <- sel.User
// request A
var logger = LogManager.GetLogger("ClaimServiceF")
// logger has 'UserNameWCF' set to 'sel.User' of request B
要解决此问题,请考虑使用ThreadContext附加&#39;或者&#39;范围&#39;请求的属性,因为每个请求都由一个单独的(池化,重用)线程处理。
如果您使用async
/ await
来设置属性并在同一个线程中获取记录器,请格外注意。