在 Web 服务日志中动态设置 log4net 文件名

Dynamically set log4net filename in a web service log

我在 Web 服务中使用 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.[=23= 的 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.config -它抛出异常

Exception thrown: 'System.TypeInitializationException' in SrvImplF.dll An exception of type 'System.TypeInitializationException' occurred in SrvImplF.dll but was not handled in user code Additional information: The type initializer for 'FSharp.Configuration.AppSettingsTypeProvider' threw an exception.

我认为最后这只是我的类型提供程序配置中的一个小错误,反正也是直接输入值

        let configFile = "C:\reports\ClaimRpt\Log\ClaimLog_{UserNameWCF}.txt" //Settings.MyServiceLog
        appender.File <- configFile.Replace("{UserNameWCF}",sel.User)

没有异常,但日志文件名保持不变ClaimLog_(null).txt

您必须在 设置 属性 后调用 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 其中 'attaches' 或 'scopes' 属性,因为每个请求都由单独的(合并、重用)处理) 线。 如果您使用 async/await 设置 属性 并在同一线程中获取记录器,请格外注意。