Search code examples
.netasynchronousf#tracetracesource

Using TraceSource in Multithreaded environment


I have written an application in F# using asyncronous-workflows. Now what I'd like to do is to add some Tracing to it!

There is basically a class A which can be instantiated several times. Each one of the instances is working independend asynchronous (in itself) and parallel (to others). My Basic idea now is to add a TraceSource instance for every instance of A, which is most likely what I want to do. I managed to solve the problem of distributing the TraceSource with the Async objects via https://github.com/matthid/fsharpasynctrace

However If every TraceSource instance is given the same name, some of them will be written in the same file (log.txt) and others will be written to {guid}log.txt.

If I give every instance an other name the user has to edit the app.config file to get the logging right. Every instance of A has a logical name given by the user so ideally I would save the log for the instance in name_log.txt. (This is because the user is basically creating instances of A at runtime)

So my question is: Is there a better way to do this, ie without the user interacting and still get the desired output and flexibility (via app.config)?

Note: Because basically everything is in the threadpool, and because there can be a lot of actions across instances at the same time, tracing classes or threads is not an option at all.

Note2: I can think of extending the app.config in some way and doing it myself, is this my only option?

EDIT: To make the Question more clear:

Imagine the following class:

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)

You have lots of instances of this class and every instance is living very long. You have now the situation described above. (You also want to trace abstract member, which could be done by a protected tracesource... which is not available in F#. And you want to trace some module Functions. Thats why i have choosen the above distribution model. If you do it any other way you will have a hard time going through the logs.)


Solution

  • I haven't tested this, but it seems like it would work. The TraceXXX methods on TraceSource accept an id parameter. What about using that as an "instance identifier"? You could then write a custom trace listener to redirect the output based on that id. Maybe this will serve as a starting point:

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

    Usage

    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)