2012-07-09 108 views
2

我已经使用异步工作流在F#中编写了一个应用程序。 现在我想要做的是添加一些追踪到它!在多线程环境中使用TraceSource

基本上有一个类A可以多次实例化。每个实例都是独立工作的,并且是异步的(本身)和并行的(对其他人)。 我现在的基本想法是为A的每个实例添加一个TraceSource实例,这很可能是我想要做的。我设法如果每个TraceSource实例给出了相同的名称通过https://github.com/matthid/fsharpasynctrace

然而,解决分配与异步对象TraceSource的问题,其中一些将被写在同一个文件(log.txt的)和其他人将写入{guid} log.txt。

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

所以我的问题是:是否有更好的方式来做到这一点,即没有用户交互,仍然获得所需的输出和灵活性(通过应用程序。配置)?

注:由于基本上一切都在线程池,并且因为可以有很多在同一时间跨实例的操作,跟踪类或线程是不是一种选择,在所有。

注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) 

你有很多此类的实例,并且每个实例都住得很长。你现在有上面描述的情况。 (你也想跟踪抽象成员,这可以通过受保护的跟踪源来完成......这在F#中是不可用的,而且你想跟踪一些模块函数,这就是为什么我选择了上述分布模型。它会以任何其他方式通过日志很难)

回答

1

我还没有测试过这一点,但它似乎会工作。 TraceSource上的TraceXXX方法接受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) 
+0

我仍然在考虑这个解决方案... 如果我设法改变它,以便我可以在app.config中更改目录,我会将其标记为答案(尽管这可能需要一些时间)。有两件事:首先,我想使用id作为实例本身(因为它们运行时间很长),secound我认为这不是线程安全的,因为setOutput可以在执行“base”之前由两个线程调用。呼叫。我想你也许能够让我走上正轨。 – matthid 2012-07-09 17:27:19

+0

您可以使用['initializeData'](http://msdn.microsoft.com/en-us/library/hfaf9h0e.aspx)属性在.config文件中指定目录。我不确定我了解你使用id作为实例的含义。这已经是它的工作方式了。我相信'TraceSource'在调用侦听器方法之前获得一个锁,这将使它成为线程安全的。如果情况并非如此,我会将'base'调用包装在一个lambda中,并将其传递给'setOutput',以便可以在'writers'的同一个锁中调用它。 – Daniel 2012-07-09 18:52:34

+0

想想吧,如果'TraceSource'序列化对'TraceListener'的调用,'writers'上的锁定可以被删除。 – Daniel 2012-07-09 19:07:18

1

您的解决方案看起来很有趣,但我认为使用基于async的自定义工作流只是为了传递用于跟踪的对象可能是矫枉过正。

我可能会尝试使用F#代理 - 你可以与方法,如ErrorWarningTrace报告个别类型的消息的创建TracingAgent。初始化代理时,可以指定应该使用的文件。当你从多个线程调用代理时,这很好,因为代理在处理消息时序列化消息。

所以,你的用户代码应该是这样的:

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内的合理位置。

+0

哼,那我就放松了TraceSource的灵活性吧? TraceSource已经为我提供了每个功能(如更改监听器,输出,多输出 - > console + xml),我必须自己实现。 我可以做到这一点,但问题是关于使用所有这些功能。 (或者我不明白你的答案) – matthid 2012-07-09 16:24:24

+0

@reddragon嗯,我真的不明白你在问什么。但是,从高层角度来看,我认为将代理功能封装在代理中(然后根据需要进行配置)可能会比替换/封装标准F#异步工作流程更好。 – 2012-07-09 16:40:38

+0

“这样,你必须自己传递跟踪对象,但这不应该成为一个问题,因为通常你会使用少量的全局跟踪器。” 我真的没有看到这比分发TraceSource实例更好,这也是线程安全的。除此之外,我不必自己实施一切。 github链接并不是问题的真正组成部分。它的唯一目的是展示我如何分发我的跟踪实例。你能告诉我我的问题不清楚吗?我会在我的第一篇文章中尝试改进。 – matthid 2012-07-09 16:52:02

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配置的 “日志\ Namespace.A.log” 那么你会得到文件,如 “日志\ Namespace.A.name.log” 。

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

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

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