4fc1736412d30bcfc50346698f96219a1c7f992f
6 open System.IO.Compression
7 open System.Diagnostics
9 open System.Collections.Generic
11 type Severity = DEBUG = 1 | INFO = 2 | WARNING = 3 | ERROR = 4 | FATAL = 5
14 abstract NewEntry : severity
: Severity -> header
: string -> message
: string -> unit
16 type private Message =
25 type private Command =
27 | Stop of AsyncReplyChannel<unit>
32 let extractNumberFromLogfilepath (path
: string) : int option =
36 let filename = path.Substring (path.LastIndexOf Path.DirectorySeparatorChar + 1)
37 let filenameWithoutExtension = filename.Remove (filename.IndexOf '.')
38 match Int32.TryParse filenameWithoutExtension with
42 let [<Literal>] MAX_SIZE_FILE = 52428800L // [byte] (50 MB).
43 let [<Literal>] NB_ENTRIES_CHECK_SIZE = 100; // Each 100 entries added we check the size of the log file to test if it is greater than 'MAX_SIZE_FILE'.
44 let [<Literal>] COMPRESS_ARCHIVED_FILES = true
45 let [<Literal>] FILENAME_FORMAT = "{0:D4}.log"
46 let [<Literal>] COMPRESSED_FILE_POSTFIX = ".gzip"
47 let encoding = Encoding.GetEncoding "UTF-8"
49 let compress (filename : string) =
50 use inputStream = new FileStream (filename, FileMode.Open, FileAccess.Read)
51 let filenameCompressed = filename + COMPRESSED_FILE_POSTFIX
52 use compressedStream = new GZipStream (new FileStream (filenameCompressed, FileMode.Create, FileAccess.Write), CompressionLevel.Optimal)
53 inputStream.CopyTo compressedStream
55 let moduleName = System.Diagnostics.StackFrame(1).GetMethod().Module.Name
57 let mutable stream : StreamWriter = null
58 let mutable filename : string = null
60 let mutable logDir : string = null
62 let monitor = Object ()
64 let listeners = List<IListener> ()
73 static let instance = new Log ()
75 let openLogFile (entryNumber
: int64
) =
76 if not
(isNull
logDir) then
78 if isNull
stream || (entryNumber
% (int64
NB_ENTRIES_CHECK_SIZE) = 0L) && stream.BaseStream.Length > MAX_SIZE_FILE
80 if not
(isNull
stream)
83 if COMPRESS_ARCHIVED_FILES then
87 // Search the last id among the log files.
89 for existingFile
in Directory.GetFiles logDir do
90 match extractNumberFromLogfilepath existingFile
with
91 | Some n' when n' > n -> n <- n'
94 filename <- Path.Combine (logDir, String.Format (FILENAME_FORMAT, n))
96 if File.Exists (filename + COMPRESSED_FILE_POSTFIX) || FileInfo(filename).Length > MAX_SIZE_FILE
98 filename <- Path.Combine (logDir, String.Format (FILENAME_FORMAT, n + 1))
100 | :? FileNotFoundException -> () // The file may not exist.
102 stream <- new StreamWriter (filename, true, encoding)
104 | ex -> Console.Error.WriteLine ("Can't
open the
file log: {0}
", ex)
106 let write (msg : Message) (entryNumber : int64) =
107 openLogFile entryNumber
111 "{0:yyyy
-MM-dd
HH:mm
:ss
.fff
} [{1}
] {{{2}
}} ({3}
)",
112 TimeZone.CurrentTimeZone.ToLocalTime DateTime.UtcNow,
115 (if String.IsNullOrEmpty msg.ThreadName then string msg.ThreadId else sprintf "%s
-%i
" msg.ThreadName msg.ThreadId)
118 for listener in listeners do
119 listener.NewEntry msg.Severity header msg.Message
121 if not (isNull stream)
124 stream.WriteLine ("{0}
: {1}
", header, msg.Message)
127 | :? IOException as ex -> Console.Error.WriteLine ("Unable to write to the
log file: {0}
", ex)
130 new MailboxProcessor<Command> (
132 let rec loop (nbEntries : int64) =
134 let! command = inbox.Receive ()
137 write message nbEntries
138 return! loop (nbEntries + 1L)
139 | Stop replyChannel ->
140 replyChannel.Reply ()
148 let setLogDirectory (dir : string) =
153 if not <| isNull stream then
158 if not <| Directory.Exists logDir
160 Directory.CreateDirectory logDir |> ignore
162 | _ -> Console.Error.WriteLine ("Unable to create
the log directory
: {0}
", logDir)
165 interface IDisposable with
166 member this.Dispose () =
167 if not (isNull stream)
170 (writeAgent :> IDisposable).Dispose ()
172 member private this.Write (message : string) (severity : Severity) =
173 let moduleNameCaller =
174 match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name
175 if name <> moduleName then Some name else None) with
183 ThreadName = Thread.CurrentThread.Name
184 ThreadId = Thread.CurrentThread.ManagedThreadId
185 ModuleCaller = moduleNameCaller
189 writeAgent.Post command
192 /// Will stop and wait a reply. Used to flush the remaining messages.
194 member private this.Stop () =
195 writeAgent.PostAndReply (
200 member this.LogDirectory
202 and set value = setLogDirectory value
204 static member SetLogDirectory (dir : string) =
205 instance.LogDirectory <- dir
207 member this.AddListener (listener : IListener) =
210 if not <| listeners.Contains listener
212 listeners.Add listener
215 member this.RmListener (listener : IListener) =
216 lock monitor (fun () -> listeners.Remove listener |> ignore)
218 static member AddListener (listener : IListener) = instance.AddListener listener
219 static member RmListener (listener : IListener) = instance.RmListener listener
221 static member LogWithTime (severity : Severity) (f : unit -> 'a) (format : Printf.StringFormat<'b, 'a>) : 'b =
222 let sw = Stopwatch ()
226 Printf.kprintf (fun s -> instance.Write (s + sprintf " (time
: %d ms
)" sw.ElapsedMilliseconds) severity; res) format
228 static member Debug format =
230 Printf.kprintf (fun s -> instance.Write s Severity.DEBUG) format
232 Printf.kprintf (fun _ -> ()) format // TODO: can it be simplify?
235 static member Info format =
236 Printf.kprintf (fun s -> instance.Write s Severity.INFO) format
238 static member Warning format =
239 Printf.kprintf (fun s -> instance.Write s Severity.WARNING) format
241 static member Error format =
242 Printf.kprintf (fun s -> instance.Write s Severity.ERROR) format
244 static member Fatal format =
245 Printf.kprintf (fun s -> instance.Write s Severity.FATAL) format
247 static member Shutdown () =