2

我使用异步工作流在 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)

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

4

3 回答 3

1

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

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

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

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.

于 2012-07-09T15:11:26.510 回答
1

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

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)
于 2012-07-09T17:05:14.257 回答
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  

实际上,我还找到了一个不依赖于反射的解决方案:您自己继承所有重要的 TraceListeners 并公开它们初始化的数据。然后在 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”,那么您将获得类似“logs\Namespace.A.name.log”的文件。

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

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

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

于 2012-07-10T07:12:21.713 回答