在多线程环境中使用TraceSource

时间:2012-07-09 14:56:58

标签: .net asynchronous f# trace tracesource

我使用asyncronous-workflows在F#中编写了一个应用程序。 现在我想做的是添加一些跟踪!

基本上有一个可以多次实例化的A类。每个实例都是独立工作(本身)和并行(与其他实例)。 我现在的基本想法是为A的每个实例添加一个TraceSource实例,这很可能是我想要做的。我设法通过https://github.com/matthid/fsharpasynctrace

解决了使用Async对象分发TraceSource的问题

但是,如果每个TraceSource实例都具有相同的名称,则其中一些实例将写入同一文件(log.txt),其他实例将写入{guid} log.txt。

如果我为每个实例提供另一个名称,则用户必须编辑app.config文件才能获得正确的日志记录。 A的每个实例都有一个用户给出的逻辑名称,所以理想情况下我会在name_log.txt中保存实例的日志。 (这是因为用户基本上是在运行时创建A的实例)

所以我的问题是:有没有更好的方法来做到这一点,即没有用户交互,仍然可以获得所需的输出和灵活性(通过app.config)?

注意:因为基本上所有内容都在线程池中,并且因为同时跨实例可能会有很多操作,所以跟踪类或线程根本不是一个选项。

注意2:我可以考虑以某种方式扩展app.config并自己动手,这是我唯一的选择吗?

编辑: 使问题更清楚:

想象一下以下课程:

module OtherModule = 
    let doSomethingAsync m = async{return()}
[<AbstractClass>]
type A (name:string) as x = 
    let processor = 
        MailboxProcessor.Start(
            fun inbox -> async {
                while true do
                    let! msg = inbox.Receive()
                    do! x.B(msg)
                    do! OtherModule.doSomethingAsync(msg)})
    abstract member B : string -> Async<unit>
    member x.Do(t:string) = processor.Post(t)

你有很多这个类的实例,每个实例都活得很长。你现在有了上述情况。 (您还希望跟踪抽象成员,这可以通过受保护的traceource来完成...这在F#中是不可用的。并且您想要跟踪一些模块函数。这就是为什么我选择了上面的分发模型。如果你这样做任何其他方式,你将很难通过日志。)

3 个答案:

答案 0 :(得分:1)

您的解决方案看起来非常有趣,但我认为使用基于async的自定义工作流来传递用于跟踪的对象可能是一种过度杀伤。

我可能会尝试使用F#代理 - 您可以使用TracingAgentErrorWarning等方法创建Trace来报告各种类型的消息。初始化代理程序时,可以指定应使用的文件。当您从多个线程调用代理时,这很好,因为代理在处理消息时序列化消息。

因此,您的用户代码如下所示:

let tracer = TracingAgent("Workflow 01")

let doSomeThingInner v = async {
    tracer.Critical "CRITICAL! %s" v
    return "ToOuter" }

let testIt () = async {
    tracer.Verbose "Verbose!" 
    let! d = doSomeThingInner "ToInner"
    tracer.Warning "WARNING: %s" d }

testIt () |> Async.RunSynchronously

这样,你必须自己传递tracer个对象,但这不应该是一个问题,因为通常你会使用少量的全局跟踪器。如果由于某种原因想要更改输出文件,可以向代理添加消息以执行此操作。

代理的结构类似于:

type TracingAgent(log) = 
  let inbox = MailboxProcessor.Start(fun inbox -> async {
    while true do
      let! msg = inbox.Receive()
      // Process the message - write to a log
    })
  // Methods that are used to write to the log file
  member x.Warning fmt = 
    Printf.kprintf (fun str -> inbox.Post(Warning(str))) fmt

  // Optionally a method that changes the log file
  member x.ChangeFile(file) = 
    inbox.Post(ChangeFile(file))

可以从配置文件加载日志记录的配置 - 我认为这样做的逻辑位置将在TracingAgent内。

答案 1 :(得分:1)

我没有测试过这个,但似乎它会起作用。 TraceXXX上的TraceSource方法接受id参数。如何将其用作“实例标识符”?然后,您可以编写自定义跟踪侦听器,以根据该ID重定向输出。也许这将作为一个起点:

type MultiOutputTraceListener(directory) =
  inherit TraceListener()

  let mutable output : TextWriter = null
  let writers = Dictionary()

  let setOutput (id: int) =
    lock writers <| fun () ->
      match writers.TryGetValue(id) with
      | true, w -> output <- w
      | _ ->
        let w = new StreamWriter(Path.Combine(directory, id.ToString() + ".log"))
        writers.Add(id, w)
        output <- w

  override x.Write(msg: string) = output.Write(msg)
  override x.WriteLine(msg: string) = output.WriteLine(msg)

  override x.TraceData(eventCache, source, eventType, id, data: obj) =
    setOutput id
    base.TraceData(eventCache, source, eventType, id, data)

  override x.TraceData(eventCache, source, eventType, id, data) =
    setOutput id
    base.TraceData(eventCache, source, eventType, id, data)

  override x.TraceEvent(eventCache, source, eventType, id, message) =
    setOutput id
    base.TraceEvent(eventCache, source, eventType, id, message)

  override x.TraceEvent(eventCache, source, eventType, id, format, args) =
    setOutput id
    base.TraceEvent(eventCache, source, eventType, id, format, args)

  override x.Dispose(disposing) =
    if disposing then
      for w in writers.Values do
        w.Dispose()

用法

module Tracing =
  let Source = TraceSource("MyTraceSource")

type A(id) =
  member x.M() =
    Tracing.Source.TraceEvent(TraceEventType.Verbose, id, "Entering method M()")
    ...

let a1 = A(1)
let a2 = A(2)

答案 2 :(得分:0)

经过一些测试并思考答案后,我提出了以下解决方案:

type ITracer = 
    inherit IDisposable
    abstract member log : Diagnostics.TraceEventType ->Printf.StringFormat<'a, unit> -> 'a


type ITracer with
    member x.logVerb fmt = x.log System.Diagnostics.TraceEventType.Verbose fmt
    member x.logWarn fmt = x.log System.Diagnostics.TraceEventType.Warning fmt
    member x.logCrit fmt = x.log System.Diagnostics.TraceEventType.Critical fmt
    member x.logErr fmt =  x.log System.Diagnostics.TraceEventType.Error fmt
    member x.logInfo fmt = x.log System.Diagnostics.TraceEventType.Information fmt

type MyTraceSource(traceEntry:string,name:string) as x= 
    inherit TraceSource(traceEntry)
    do 
        let newTracers = [|
            for l in x.Listeners do
                let t = l.GetType()
                let initField =
                    t.GetField(
                        "initializeData", System.Reflection.BindingFlags.NonPublic ||| 
                                          System.Reflection.BindingFlags.Instance)
                let oldRelFilePath =
                    if initField <> null then
                         initField.GetValue(l) :?> string
                    else System.IO.Path.Combine("logs", sprintf "%s.log" l.Name)

                let newFileName =
                    if oldRelFilePath = "" then ""
                    else
                        let fileName = Path.GetFileNameWithoutExtension(oldRelFilePath)
                        let extension = Path.GetExtension(oldRelFilePath)
                        Path.Combine(
                            Path.GetDirectoryName(oldRelFilePath),
                            sprintf "%s.%s%s" fileName name extension)
                let constr = t.GetConstructor(if newFileName = "" then [| |] else [| typeof<string> |])
                if (constr = null) then 
                    failwith (sprintf "TraceListener Constructor for Type %s not found" (t.FullName))
                let listener = constr.Invoke(if newFileName = "" then [| |]  else [| newFileName |]) :?> TraceListener
                yield listener |]
        x.Listeners.Clear()
        x.Listeners.AddRange(newTracers)

type DefaultStateTracer(traceSource:TraceSource, activityName:string) = 
    let trace = traceSource
    let activity = Guid.NewGuid()
    let doInId f = 
        let oldId = Trace.CorrelationManager.ActivityId
        try
            Trace.CorrelationManager.ActivityId <- activity
            f()
        finally
            Trace.CorrelationManager.ActivityId <- oldId
    let logHelper ty (s : string) =  
        doInId 
            (fun () ->
                trace.TraceEvent(ty, 0, s)
                trace.Flush())
    do 
        doInId (fun () -> trace.TraceEvent(TraceEventType.Start, 0, activityName);)

    interface IDisposable with
        member x.Dispose() = 
            doInId (fun () -> trace.TraceEvent(TraceEventType.Stop, 0, activityName);)

    interface ITracer with 
        member x.log ty fmt = Printf.kprintf (logHelper ty) fmt  

实际上我发现了一个不依赖于反射的解决方案:您自己继承所有重要的TraceListener并公开它们初始化的数据。然后使用MyTraceSource构造函数中的已更改数据创建匹配的侦听器。

编辑:非反射解决方案不像上面的反射那样通用。

用法是这样的:

let SetTracer tracer (traceAsy:AsyncTrace<_,_>) = 
    traceAsy.SetInfo tracer
    traceAsy |> convertToAsync

module OtherModule = 
    let doSomethingAsync m = asyncTrace() {
        let! (tracer:ITracer) = traceInfo()
        return()
        }

[<AbstractClass>]
type A (name:string) as x = 

    let processor = 
        let traceSource = new MyTraceSource("Namespace.A", name)
        MailboxProcessor.Start(
            fun inbox -> async {
                while true do
                    let tracer = new DefaultStateTracer(traceSource, "Doing activity Foo now") :> ITracer
                    let! msg = inbox.Receive()
                    let w = x.B(msg) |> SetTracer tracer
                    do! OtherModule.doSomethingAsync(msg) |> SetTracer tracer})
    abstract member B : string -> AsyncTrace<ITracer, unit>
    member x.Do(t:string) = processor.Post(t)

如果您在app.config“logs \ Namespace.A.log”中配置,那么您将获得类似的文件 “日志\ Namespace.A.name.log”。

注意:您仍然需要复制可以通过app.config配置的其他属性,但现在应该很容易完成。

如果您认为这不是正确的方法,请发表评论。

编辑:将此跟踪解决方案添加到https://github.com/matthid/fsharpasynctrace