talircd/lib/logging/logging.ml

220 lines
5.5 KiB
OCaml
Raw Normal View History

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;
}
2024-01-31 17:28:47 +00:00
(* TODO: config these per writer *)
let _color = true
let _timestamp = true
let _namespace = true
let _level = true
2024-01-12 02:49:48 +00:00
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
2024-01-31 17:28:47 +00:00
let ansi_header = function
| TRACE -> "\x1b[34m"
| DEBUG -> "\x1b[36m"
| INFO -> "\x1b[32m"
| WARN -> "\x1b[33m"
| ERROR -> "\x1b[31m"
let ansi_dim = "\x1b[2m"
let ansi_bold = "\x1b[1m"
let ansi_off = "\x1b[0m"
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;
}
2024-01-31 17:28:47 +00:00
let pr_msg bp msg ts ns lvl ~align ~indent =
begin
if _timestamp && _color then Buffer.add_string bp ansi_dim;
if _timestamp then Printf.bprintf bp "%a " pr_timestamp ts;
if _timestamp && _color then Buffer.add_string bp ansi_off;
if _namespace then Printf.bprintf bp "%s " ns;
if _namespace then pr_spaces bp (align bp);
if _level && _color then Buffer.add_string bp (ansi_header lvl);
if _level then Printf.bprintf bp "%-5s " (header lvl);
if _level && _color then Buffer.add_string bp ansi_off;
if _color then Buffer.add_string bp ansi_bold;
pr_lines bp msg ~indent:(indent bp);
if _color then Buffer.add_string bp ansi_off;
Buffer.add_string bp "\n";
end
2024-01-12 02:49:48 +00:00
let writer t ~ts ~ns ~lvl msg =
2024-01-31 17:28:47 +00:00
let align bp =
let n = t.align_to - Buffer.length bp in
t.align_to <- Buffer.length bp;
n
in
let indent bp =
let subtract =
if _color then
4 + (if _timestamp then 8 else 0) + (if _level then 9 else 0)
else
0
in
Buffer.length bp - subtract
in
2024-01-12 02:49:48 +00:00
begin
Mutex.lock t.mutex;
2024-01-31 17:28:47 +00:00
pr_msg t.bp msg ts ns lvl ~align ~indent;
2024-01-12 02:49:48 +00:00
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