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) =
149 lock monitor (fun () ->
152 if not <| isNull stream then
157 if not <| Directory.Exists(logDir)
159 Directory.CreateDirectory(logDir) |> ignore
161 | _ -> Console.Error.WriteLine("Unable to create
the log directory
: {0}
", logDir))
163 interface IDisposable with
164 member this.Dispose () =
165 if not (isNull stream)
168 (writeAgent :> IDisposable).Dispose()
170 member private this.Write (message : string) (severity : Severity) =
171 let moduleNameCaller =
172 match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name
173 if name <> moduleName then Some name else None) with
181 ThreadName = Thread.CurrentThread.Name
182 ThreadId = Thread.CurrentThread.ManagedThreadId
183 ModuleCaller = moduleNameCaller
187 writeAgent.Post command
190 /// Will stop and wait a reply. Used to flush the remaining messages.
192 member private this.Stop () =
193 writeAgent.PostAndReply(
198 member this.LogDirectory
200 and set value = setLogDirectory value
202 static member SetLogDirectory (dir : string) =
203 instance.LogDirectory <- dir
205 member this.AddListener (listener : IListener) =
206 lock monitor (fun () ->
207 if not <| listeners.Contains(listener)
209 listeners.Add(listener))
211 member this.RmListener (listener : IListener) =
212 lock monitor (fun () ->
213 listeners.Remove(listener) |> ignore)
215 static member AddListener (listener : IListener) = instance.AddListener(listener)
216 static member RmListener (listener : IListener) = instance.RmListener(listener)
218 static member LogWithTime (severity : Severity) (f : unit -> 'a) (format : Printf.StringFormat<'b, 'a>) : 'b =
223 Printf.kprintf (fun s -> instance.Write (s + sprintf " (time
: %d ms
)" sw.ElapsedMilliseconds) severity; res) format
225 static member Debug format =
227 Printf.kprintf (fun s -> instance.Write s Severity.DEBUG) format
229 Printf.kprintf (fun _ -> ()) format // TODO: can it be simplify?
232 static member Info format =
233 Printf.kprintf (fun s -> instance.Write s Severity.INFO) format
235 static member Warning format =
236 Printf.kprintf (fun s -> instance.Write s Severity.WARNING) format
238 static member Error format =
239 Printf.kprintf (fun s -> instance.Write s Severity.ERROR) format
241 static member Fatal format =
242 Printf.kprintf (fun s -> instance.Write s Severity.FATAL) format
244 static member Shutdown () =