2024-01-12 02:49:48 +00:00
|
|
|
type level =
|
2024-01-12 03:33:40 +00:00
|
|
|
| TRACE (* 0 *)
|
|
|
|
| DEBUG (* 1 *)
|
|
|
|
| INFO (* 2 *)
|
|
|
|
| WARN (* 3 *)
|
|
|
|
| ERROR (* 4 *)
|
2024-01-12 02:49:48 +00:00
|
|
|
|
2024-01-12 03:33:40 +00:00
|
|
|
external int_of_level : level -> int = "%identity"
|
2024-01-12 02:49:48 +00:00
|
|
|
|
|
|
|
type writer =
|
|
|
|
ts:float ->
|
|
|
|
ns:string ->
|
2024-01-12 03:33:40 +00:00
|
|
|
lvl:level ->
|
2024-01-12 02:49:48 +00:00
|
|
|
string ->
|
|
|
|
unit
|
|
|
|
|
|
|
|
let ( +++ ) w1 w2 ~ts ~ns ~lvl msg =
|
|
|
|
w1 ~ts ~ns ~lvl msg;
|
|
|
|
w2 ~ts ~ns ~lvl msg
|
|
|
|
|
|
|
|
type logger = {
|
|
|
|
namespace : string;
|
|
|
|
mutable child : logger;
|
|
|
|
sibling : logger;
|
2024-01-12 03:33:40 +00:00
|
|
|
mutable min_level : int;
|
2024-01-12 02:49:48 +00:00
|
|
|
mutable writers : writer option;
|
|
|
|
}
|
|
|
|
|
|
|
|
let rec root_logger = {
|
|
|
|
namespace = "";
|
2024-01-12 03:33:40 +00:00
|
|
|
min_level = 0;
|
2024-01-12 02:49:48 +00:00
|
|
|
child = root_logger;
|
|
|
|
sibling = root_logger;
|
|
|
|
writers = None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let make_logger parent_logger ns =
|
|
|
|
let namespace =
|
|
|
|
if parent_logger == root_logger then ns
|
|
|
|
else String.concat "." [parent_logger.namespace; ns];
|
|
|
|
in
|
|
|
|
let rec logger = {
|
|
|
|
namespace;
|
2024-01-12 03:33:40 +00:00
|
|
|
min_level = Int.max_int;
|
2024-01-12 02:49:48 +00:00
|
|
|
child = logger;
|
|
|
|
sibling = parent_logger.child;
|
|
|
|
writers = None;
|
|
|
|
} in
|
|
|
|
parent_logger.child <- logger;
|
|
|
|
logger
|
|
|
|
|
|
|
|
type 'a log_function =
|
|
|
|
((('a, Format.formatter, unit) format -> 'a) -> unit) -> unit
|
|
|
|
|
|
|
|
let write logger lvl msg =
|
|
|
|
let ts = Unix.gettimeofday () in
|
|
|
|
let ns = logger.namespace in
|
|
|
|
Option.iter (fun w -> w ~ts ~ns ~lvl msg) logger.writers
|
|
|
|
|
2024-01-12 03:33:40 +00:00
|
|
|
let[@inline] logf logger lvl : _ log_function = fun logk ->
|
|
|
|
if int_of_level lvl >= logger.min_level then
|
2024-01-12 02:49:48 +00:00
|
|
|
logk (fun fmt ->
|
|
|
|
Format.kasprintf (write logger lvl)
|
|
|
|
("@[<hov>" ^^ fmt))
|
|
|
|
|
|
|
|
|
|
|
|
module type Logs = sig
|
|
|
|
val logger : logger
|
|
|
|
val trace : _ log_function
|
|
|
|
val debug : _ log_function
|
|
|
|
val info : _ log_function
|
|
|
|
val warn : _ log_function
|
|
|
|
val error : _ log_function
|
|
|
|
val log : level -> _ log_function
|
|
|
|
end
|
|
|
|
|
|
|
|
let make_logs parent_logger ns =
|
|
|
|
(module struct
|
|
|
|
let logger = make_logger parent_logger ns
|
2024-01-12 03:33:40 +00:00
|
|
|
let[@inline] trace k = logf logger TRACE k
|
|
|
|
let[@inline] debug k = logf logger DEBUG k
|
|
|
|
let[@inline] info k = logf logger INFO k
|
|
|
|
let[@inline] warn k = logf logger WARN k
|
|
|
|
let[@inline] error k = logf logger ERROR k
|
|
|
|
let[@inline] log level k = logf logger level k
|
2024-01-12 02:49:48 +00:00
|
|
|
end : Logs)
|
|
|
|
|
|
|
|
let logs ns = make_logs root_logger ns
|
|
|
|
let sublogs logger ns = make_logs logger ns
|
|
|
|
|
|
|
|
(* TODO: filters: namespace[*] => min_level *)
|
|
|
|
|
2024-01-12 03:33:40 +00:00
|
|
|
let rec add_writer_rec min_level writer parent_logger logger =
|
2024-01-12 02:49:48 +00:00
|
|
|
if parent_logger != logger then
|
|
|
|
begin
|
2024-01-12 03:33:40 +00:00
|
|
|
logger.min_level <- min min_level logger.min_level;
|
2024-01-12 02:49:48 +00:00
|
|
|
logger.writers <-
|
|
|
|
(match logger.writers with
|
|
|
|
| None -> Some writer
|
|
|
|
| Some writer' -> Some (writer' +++ writer));
|
2024-01-12 03:33:40 +00:00
|
|
|
add_writer_rec min_level writer logger logger.child;
|
|
|
|
add_writer_rec min_level writer parent_logger logger.sibling
|
2024-01-12 02:49:48 +00:00
|
|
|
end
|
|
|
|
|
2024-01-12 03:33:40 +00:00
|
|
|
let[@inline] add_writer ?(min_level = WARN) writer =
|
|
|
|
add_writer_rec (int_of_level min_level) writer root_logger root_logger.child
|
2024-01-12 02:49:48 +00:00
|
|
|
|
|
|
|
module Pretty = struct
|
|
|
|
type t = {
|
|
|
|
mutex : Mutex.t;
|
|
|
|
bp : Buffer.t;
|
|
|
|
mutable align_to : int;
|
|
|
|
out : out_channel;
|
|
|
|
}
|
|
|
|
|
|
|
|
(* TODO: config timestamp *)
|
|
|
|
(* TODO: config color *)
|
|
|
|
(* TODO: config namespace *)
|
|
|
|
|
|
|
|
let header = function
|
2024-01-12 03:33:40 +00:00
|
|
|
| TRACE -> "TRACE"
|
|
|
|
| DEBUG -> "DEBUG"
|
|
|
|
| INFO -> "INFO"
|
|
|
|
| WARN -> "WARN"
|
|
|
|
| ERROR -> "ERROR"
|
2024-01-12 02:49:48 +00:00
|
|
|
|
|
|
|
let ansi_color = function
|
2024-01-12 03:33:40 +00:00
|
|
|
| TRACE -> 34
|
|
|
|
| DEBUG -> 36
|
|
|
|
| INFO -> 32
|
|
|
|
| WARN -> 33
|
|
|
|
| ERROR -> 31
|
2024-01-12 02:49:48 +00:00
|
|
|
|
|
|
|
let pr_timestamp bp ts =
|
|
|
|
let ts_ms = int_of_float (ts *. 1000.0) mod 1000 in
|
|
|
|
let tm = Unix.localtime ts in
|
|
|
|
Printf.bprintf bp "%04d-%02d-%02d %02d:%02d:%02d.%03d"
|
|
|
|
(tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
|
|
|
|
tm.tm_hour tm.tm_min tm.tm_sec ts_ms
|
|
|
|
|
|
|
|
let pr_spaces bp n =
|
|
|
|
for _ = 1 to n do
|
|
|
|
Buffer.add_char bp ' '
|
|
|
|
done
|
|
|
|
|
|
|
|
let rec pr_lines_rec indent bp s i =
|
|
|
|
match String.index_from s i '\n' with
|
|
|
|
| exception Not_found ->
|
|
|
|
Buffer.add_substring bp s i (String.length s - i)
|
|
|
|
| k ->
|
|
|
|
Buffer.add_substring bp s i (k + 1 - i);
|
|
|
|
pr_spaces bp indent;
|
|
|
|
pr_lines_rec indent bp s (k + 1)
|
|
|
|
|
|
|
|
let pr_lines ~indent bp s =
|
|
|
|
pr_lines_rec indent bp s 0
|
|
|
|
|
|
|
|
let make (out : out_channel) = {
|
|
|
|
mutex = Mutex.create ();
|
|
|
|
bp = Buffer.create 512;
|
|
|
|
align_to = 0;
|
|
|
|
out;
|
|
|
|
}
|
|
|
|
|
|
|
|
let writer t ~ts ~ns ~lvl msg =
|
|
|
|
begin
|
|
|
|
Mutex.lock t.mutex;
|
2024-01-12 03:33:40 +00:00
|
|
|
Printf.bprintf t.bp "\x1b[1m%a\x1b[22m %s "
|
|
|
|
pr_timestamp ts ns;
|
2024-01-12 02:49:48 +00:00
|
|
|
pr_spaces t.bp (t.align_to - Buffer.length t.bp);
|
|
|
|
t.align_to <- Buffer.length t.bp;
|
2024-01-12 03:33:40 +00:00
|
|
|
Printf.bprintf t.bp "\x1b[%dm%-5s\x1b[39;1m "
|
|
|
|
(ansi_color lvl) (header lvl);
|
2024-01-12 02:49:48 +00:00
|
|
|
pr_lines t.bp msg ~indent:(t.align_to - 3);
|
|
|
|
Printf.bprintf t.bp "\x1b[0m\n";
|
|
|
|
Buffer.output_buffer t.out t.bp;
|
|
|
|
flush t.out;
|
|
|
|
Buffer.clear t.bp;
|
|
|
|
Mutex.unlock t.mutex
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
let init_pretty_writer ?min_level out =
|
|
|
|
Pretty.make out |>
|
|
|
|
Pretty.writer |>
|
2024-01-12 03:33:40 +00:00
|
|
|
add_writer ?min_level
|