.net 在多线程环境中使用TraceSource

.net 在多线程环境中使用TraceSource,.net,asynchronous,f#,trace,tracesource,.net,Asynchronous,F#,Trace,Tracesource,我使用异步工作流在F#中编写了一个应用程序。 现在我想做的是添加一些跟踪到它 基本上有一个可以实例化多次的类a。每个实例都是独立异步(本身)和并行(与其他实例)工作的。 我现在的基本想法是为的每个实例添加一个TraceSource实例,这很可能是我想要做的。我设法通过以下方式解决了使用异步对象分发TraceSource的问题: 但是,如果每个TraceSource实例都具有相同的名称,则其中一些实例将写入同一文件(log.txt),而另一些实例将写入{guid}log.txt 如果我给每个实例一

我使用异步工作流在F#中编写了一个应用程序。 现在我想做的是添加一些跟踪到它

基本上有一个可以实例化多次的类a。每个实例都是独立异步(本身)和并行(与其他实例)工作的。 我现在的基本想法是为的每个实例添加一个TraceSource实例,这很可能是我想要做的。我设法通过以下方式解决了使用异步对象分发TraceSource的问题:

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

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

所以我的问题是:有没有更好的方法来做到这一点,即在没有用户交互的情况下,仍然可以获得所需的输出和灵活性(通过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)
模块其他模块=
设doSomethingAsync m=async{return()}
[]
键入一个(名称:字符串)作为x=
让处理器=
MailboxProcessor.启动(
趣味收件箱->异步{
尽管如此
let!msg=inbox.Receive()
do!x.B(味精)
do!OtherModule.doSomethingAsync(msg)})
抽象成员B:字符串->异步
成员x.Do(t:string)=处理器.Post(t)
这个类有很多实例,每个实例都活得很长。您现在已经了解了上述情况。
(您还希望跟踪抽象成员,这可以通过受保护的tracesource来完成…这在F#中不可用。您还希望跟踪一些模块函数。这就是我选择上述分发模型的原因。如果您以任何其他方式这样做,您将很难查看日志。)

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

我可能会尝试使用F#agent——您可以创建一个
TracingAgent
,使用
Error
Warning
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
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)

这样,您就必须自己传递
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

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

键入MultiOutputRaceListener(目录)=
继承TraceListener()
让可变输出:TextWriter=null
let writers=字典()
let setOutput(id:int)=
锁编写器
将writers.TryGetValue(id)与
|true,w->output
设w=newstreamwriter(Path.Combine(目录,id.ToString()+“.log”))
writers.Add(id,w)

输出经过一些测试和思考,我想出了以下解决方案:

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  
类型ITracer=
可继承的
抽象成员日志:Diagnostics.TraceEventType->Printf.StringFormat字符串
else System.IO.Path.Combine(“日志”,sprintf“%s.log”l.Name)
让newFileName=
如果oldRelFilePath=”“,则“”
其他的
让fileName=Path.getfilenamwithoutextension(oldRelFilePath)
让extension=Path.GetExtension(oldRelFilePath)
路径。联合收割机(
GetDirectoryName(oldRelFilePath),
sprintf“%s.%s%s”文件名扩展名)
让constr=t.GetConstructor(如果newFileName=”“,那么[| |]else[| typeof |])
如果(constr=null),则
failwith(未找到%s类型的sprintf“TraceListener构造函数”(t.FullName))
让listener=constr.Invoke(如果newFileName=”,那么[| |]否则[| newFileName |]):?>TraceListener
产量监听器|]
x、 Listeners.Clear()
x、 Listeners.AddRange(newTracers)
类型DefaultStateTracer(traceSource:traceSource,activityName:string)=
让trace=traceSource
let activity=Guid.NewGuid()
设doInId f=
让oldId=Trace.CorrelationManager.ActivityId
尝试
Trace.CorrelationManager.ActivityId Trace.TraceEvent(TraceEventType.Start,0,activityName);)
接口IDisposable与
成员x.Dispose()=
doInId(fun()->trace.TraceEvent
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)