Skip to content

Instantly share code, notes, and snippets.

@ppanyukov
Created March 13, 2012 03:07
Show Gist options
  • Save ppanyukov/2026328 to your computer and use it in GitHub Desktop.
Save ppanyukov/2026328 to your computer and use it in GitHub Desktop.
Asynchronous log writer implemented using F# agent
module LogAgent =
// Log writer implementation using F# asynchronous agents
// Written as an excersise
open System
open System.IO
type private Message =
| Debug of string
| Info of string
| Warn of string
| Error of string
| Fatal of string
with
static member toString logMessage =
match logMessage with
| Debug m -> m |> sprintf "DEBUG:%s"
| Info m -> m |> sprintf "INFO:%s"
| Warn m -> m |> sprintf "WARN:%s"
| Error m -> m |> sprintf "ERROR:%s"
| Fatal m -> m |> sprintf "FATAL:%s"
override this.ToString() =
Message.toString this
type private LogCommand =
| Log of Message
| Flush
| Close of AsyncReplyChannel<unit>
type LogAgent(logFile:string) as this =
let writer = lazy(File.AppendText logFile)
let agent = MailboxProcessor.Start (fun agent ->
// Do the loop until the Stop command is received
// Keep the number of lines written to the log
let rec loop(count) = async {
let! command = agent.Receive()
match command with
| Log message ->
let count = count + 1
let message = Message.toString message
writer.Value.WriteLine message
return! loop(count)
| Flush ->
if writer.IsValueCreated then
writer.Value.Flush()
return! loop(count)
| Close reply ->
let message = sprintf "%d messages written into log" count
Console.WriteLine message
this.doClose()
reply.Reply(ignore())
return ignore()
}
loop(0))
interface IDisposable with
member this.Dispose() = this.doClose()
member private this.doClose() =
let message = sprintf "Discarding %d messages in the queue" (agent.CurrentQueueLength)
Console.WriteLine(message)
let d = agent :> IDisposable
d.Dispose()
if writer.IsValueCreated then
writer.Value.Dispose()
member private this.log objToMessage obj =
obj |> objToMessage |> LogCommand.Log |> agent.Post
member this.fatal = this.log Fatal;
member this.error = this.log Error
member this.warn = this.log Warn
member this.info = this.log Info
member this.debug = this.log Debug
member this.queueLength = agent.CurrentQueueLength
member this.flush() = LogCommand.Flush |> agent.Post
member this.close() = LogCommand.Close |> agent.PostAndReply
module Demo =
open LogAgent
let usage() =
// Create log.
// Can bind with "use" if we want to dispose of it automatically.
// Otherwise can bind with let and call "close" manually.
use log = new LogAgent(@"c:\temp\foo.log")
// pump lots of log messages asynchronously
let pump = async {
[0..20000]
|> Seq.iter (fun i -> sprintf "Log #%d" i |> log.info)
}
Async.Start pump
// wait for a bit for the pump to push through some messages and then close
System.Threading.Thread.Sleep(100)
log.close()
// Sleep a bit and see that the pump is still going, but messages
// are not written anywhere. No error reported. Is this the behaviour
// we want?
printfn "Sleeping for 2 seconds"
System.Threading.Thread.Sleep(2000)
[<EntryPoint>]
let main(_) =
usage()
0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment