2012-07-09 156 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