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# では使用できない保護された tracesource によって実行できる抽象メンバーもトレースしたいと考えています。また、いくつかのモジュール関数をトレースしたいと考えています。それが、上記の分散モデルを選択した理由です。それを行う場合他の方法では、ログを調べるのに苦労します。)

4

3 に答える 3

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)
于 2012-07-09T17:05:14.257 に答える
1

asyncあなたのソリューションはかなり興味深いように見えますが、トレースに使用されるオブジェクトを渡すだけに基づくカスタムワークフローを使用するのはやり過ぎかもしれません。

私はおそらく F# エージェントを使用してTracingAgentErrorます。エージェントを初期化するときに、使用するファイルを指定できます。複数のスレッドからエージェントを呼び出す場合、エージェントはメッセージを処理するときにメッセージをシリアル化するため、問題ありません。WarningTrace

したがって、ユーザー コードは次のようになります。

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 に答える
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 に答える