Add a logger assembly and split the main assembly in two : the UI and the parasitemia...
[master-thesis.git] / Parasitemia / Logger / Logger.fs
diff --git a/Parasitemia/Logger/Logger.fs b/Parasitemia/Logger/Logger.fs
new file mode 100644 (file)
index 0000000..552e37d
--- /dev/null
@@ -0,0 +1,168 @@
+namespace Logger
+
+open System
+open System.Text
+open System.IO
+open System.Diagnostics
+open System.Threading
+open System.Collections.Generic
+
+type Severity = DEBUG = 1 | USER = 2 | WARNING = 3 | ERROR = 4 | FATAL = 5
+
+type IListener = abstract NewEntry : Severity -> string -> unit
+
+[<Sealed>]
+type Log () =
+    let maxSizeFile = 10L * 1024L * 1024L // [byte] (10 MB).
+    let nbEntriesCheckSize = 100; // Each 100 entries added we check the size of the log file to test if it is greater than 'MAX_SIZE_FILE'.
+    let LogDefaultDirectory = "Parasitemia\\Log"
+    let filenameFormat = "{0:D4}.log"
+    let encoding = Encoding.GetEncoding("UTF-8")
+
+    let moduleName = System.Diagnostics.StackFrame(1).GetMethod().Module.Name
+
+    let mutable stream: StreamWriter = null
+
+    let mutable logDir: string = null
+    let mutable absoluteDir: string = null
+
+    let mutable nbEntries = 0L
+
+    let monitor = Object()
+
+    let listeners = List<IListener>()
+
+    let debug =
+#if DEBUG
+        true
+#else
+        false
+#endif
+
+    static let instance = new Log()
+
+    let setLogDirectory (dir: string) =
+        lock monitor (fun () ->
+            logDir <- dir
+            absoluteDir <- Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), logDir)
+
+            if stream <> null
+            then
+                stream.Close()
+                stream <- null
+
+            try
+                if not <| Directory.Exists(absoluteDir)
+                then
+                    Directory.CreateDirectory(absoluteDir) |> ignore
+            with
+            | _ as ex -> Console.Error.WriteLine("Unable to create the log directory: {0}", absoluteDir))
+
+    let openLogFile () =
+        try
+            if stream = null || (nbEntries % (int64 nbEntriesCheckSize) = 0L) && stream.BaseStream.Length > maxSizeFile
+            then
+                if stream <> null
+                then
+                    stream.Close()
+
+                let mutable n = 1
+                for existingFile in Directory.GetFiles(absoluteDir) do
+                    let current_n = ref 0
+                    if Int32.TryParse(existingFile.Remove(existingFile.LastIndexOf('.')), current_n) && !current_n > n
+                    then
+                        n <- !current_n
+
+                let mutable filename = Path.Combine(absoluteDir, String.Format(filenameFormat, n))
+                try
+                    if (FileInfo(filename).Length > maxSizeFile)
+                    then
+                        filename <- Path.Combine(absoluteDir, String.Format(filenameFormat, n + 1))
+                with
+                | :? FileNotFoundException -> () // The file may not exist.
+
+                stream <- new StreamWriter(filename, true, encoding)
+        with
+        | _ as ex -> Console.Error.WriteLine("Can't open the file log: {0}", ex)
+
+    do
+        setLogDirectory LogDefaultDirectory
+
+    interface IDisposable with
+        member this.Dispose () =
+            if stream <> null
+            then
+                stream.Dispose()
+
+    member private this.Write (message: string, severity: Severity) =
+        lock monitor (fun () ->
+            nbEntries <- nbEntries + 1L
+            openLogFile ()
+
+            if stream <> null
+            then
+                let mutable moduleNameCaller = moduleName
+                match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name
+                                                                              if name <> moduleName then Some name else None) with
+                | Some name -> moduleNameCaller <- name
+                | _ -> ()
+
+                let threadName = Thread.CurrentThread.Name
+
+                for listener in listeners do
+                    listener.NewEntry severity message
+
+                try
+                    stream.WriteLine(
+                        "{0:yyyy-MM-dd HH:mm:ss.fff} [{1}] {{{2}}} ({3}) : {4}",
+                        TimeZone.CurrentTimeZone.ToLocalTime(DateTime.UtcNow),
+                        severity.ToString(),
+                        moduleNameCaller,
+                        (if String.IsNullOrEmpty(threadName) then Thread.CurrentThread.ManagedThreadId.ToString() else String.Format("{0}-{1}", threadName, Thread.CurrentThread.ManagedThreadId)),
+                        message
+                    )
+                    stream.Flush()
+                with
+                | :? IOException as ex -> Console.Error.WriteLine("Unable to write to the log file: {0}", ex))
+
+
+    member private this.AddListener (listener: IListener) =
+        lock monitor (fun () ->
+            if not <| listeners.Contains(listener)
+            then
+                listeners.Add(listener))
+
+    member private this.RmListener (listener: IListener) =
+        lock monitor (fun () ->
+            listeners.Remove(listener) |> ignore)
+
+    static member AddListener (listener: IListener) = instance.AddListener(listener)
+    static member RmListener (listener: IListener) = instance.RmListener(listener)
+
+    static member LogWithTime (message: string, severity: Severity, f: unit -> 'a, [<ParamArray>] args: Object[]) : 'a =
+        let sw = Stopwatch()
+        sw.Start()
+        let res = f ()
+        sw.Stop()
+        instance.Write(String.Format(message, args) + sprintf " (time: %d ms)" sw.ElapsedMilliseconds, severity)
+        res
+
+    static member Debug (message: string, [<ParamArray>] args: Object[]) =
+#if DEBUG
+        instance.Write(String.Format(message, args), Severity.DEBUG)
+#else
+        ()
+#endif
+
+    static member User (message: string, [<ParamArray>] args: Object[]) =
+        instance.Write(String.Format(message, args), Severity.USER)
+
+    static member Warning (message: string, [<ParamArray>] args: Object[]) =
+        instance.Write(String.Format(message, args), Severity.WARNING)
+
+    static member Error (message: string, [<ParamArray>] args: Object[]) =
+        instance.Write(String.Format(message, args), Severity.ERROR)
+
+    static member Fatal (message: string, [<ParamArray>] args: Object[]) =
+        instance.Write(String.Format(message, args), Severity.FATAL)
+