Source file logger.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
open Format
module Color = struct
type t =
| Default
| Black
| Red
| Green
| Yellow
| Blue
| Purple
| Cyan
| White
let terminal_encoding = function
| Default -> ""
| Black -> ";30"
| Red -> ";31"
| Green -> ";32"
| Yellow -> ";33"
| Blue -> ";34"
| Purple -> ";35"
| Cyan -> ";36"
| White -> ";37"
let to_string = function
| Default -> "default"
| Black -> "black"
| Red -> "red"
| Green -> "green"
| Yellow -> "yellow"
| Blue -> "blue"
| Purple -> "purple"
| Cyan -> "cyan"
| White -> "white"
let of_string = function
| "black" -> Black
| "red" -> Red
| "green" -> Green
| "yellow" -> Yellow
| "blue" -> Blue
| "purple" -> Purple
| "cyan" -> Cyan
| "white" -> White
| _ -> Default
let string_to_terminal_color_codes color_name =
terminal_encoding (of_string color_name)
end
module Emphasis = struct
type t = Default | Bold | Italic
let terminal_encoding = function
| Default -> "0"
| Bold -> "1"
| Italic -> "3"
let to_string = function
| Default -> "default"
| Bold -> "bold"
| Italic -> "italic"
let of_string = function "bold" -> Bold | "italic" -> Italic | _ -> Default
end
type font = Color.t * Emphasis.t
module Profile = struct
type t = {
name : string;
debug : font;
step : font;
info : font;
result : font;
warning : font;
error : font;
fatal : font;
}
let default =
{
name = "default";
debug = (Cyan, Default);
step = (Default, Default);
info = (Green, Default);
result = (Default, Bold);
warning = (Yellow, Default);
error = (Red, Bold);
fatal = (Red, Bold);
}
let default_light =
{
name = "default_light";
debug = (Cyan, Default);
step = (Default, Default);
info = (Purple, Default);
result = (Default, Bold);
warning = (Yellow, Default);
error = (Red, Bold);
fatal = (Red, Bold);
}
let dark =
{
name = "dark";
debug = (Blue, Default);
step = (White, Default);
info = (Default, Italic);
result = (Default, Bold);
warning = (Yellow, Italic);
error = (Red, Bold);
fatal = (Red, Bold);
}
let light =
{
name = "light";
debug = (Cyan, Default);
step = (Default, Default);
info = (Black, Italic);
result = (Black, Bold);
warning = (Yellow, Italic);
error = (Red, Bold);
fatal = (Red, Bold);
}
let of_string s =
match String.lowercase_ascii s with
| "default" -> default
| "default_light" -> default_light
| "dark" -> dark
| "light" -> light
| _ -> raise Not_found
let all_profiles = [ default; default_light; dark; light ]
end
let with_tags_on ppf fmt =
let mark_tags = pp_get_mark_tags ppf ()
and print_tags = pp_get_print_tags ppf () in
pp_set_mark_tags ppf true;
pp_set_print_tags ppf true;
kfprintf
(fun ppf ->
pp_set_mark_tags ppf mark_tags;
pp_set_print_tags ppf print_tags)
ppf fmt
type stag += Font of (Color.t * Emphasis.t)
(** Reference color profile - should be one listed in Profile. *)
let profile = ref Profile.default
let set_profile profile_input = profile := profile_input
let get_profile () = !profile
module ChannelKind = struct
type t =
| Debug
| Step
| Info
| Result
| Warning
| Error
| Fatal
let to_string = function
| Debug -> "debug"
| Step -> "step"
| Info -> "info"
| Result -> "result"
| Warning -> "warning"
| Error -> "error"
| Fatal -> "fatal"
let values = [ Debug; Step; Info; Result; Warning; Error; Fatal ]
let loglevel = function
| Debug -> 0
| Step -> 5
| Info -> 10
| Warning -> 20
| Error -> 100
| Fatal -> max_int
| Result -> max_int
let of_string s =
match String.lowercase_ascii s with
| "debug" -> Debug
| "step" -> Step
| "info" -> Info
| "result" -> Result
| "warning" -> Warning
| "error" -> Error
| "fatal" -> Fatal
| s -> failwith (sprintf "%s not a channel string identifier" s)
let is_string_identifier s =
match of_string s with _ -> true | exception Failure _ -> false
end
module type S = sig
type channel
val fatal_channel : channel
val error_channel : channel
val result_channel : channel
val warning_channel : channel
val info_channel : channel
val step_channel : channel
val debug_channel : channel
val fatal : ?e:exn -> ('a, Format.formatter, unit, 'b) format4 -> 'a
val error : ('a, Format.formatter, unit) format -> 'a
val result : ('a, Format.formatter, unit) format -> 'a
val warning : ?level:int -> ('a, Format.formatter, unit) format -> 'a
val set_warning_level : int -> unit
val get_warning_level : unit -> int
val info : ?level:int -> ('a, Format.formatter, unit) format -> 'a
val set_info_level : int -> unit
val get_info_level : unit -> int
val step : ('a, Format.formatter, unit) format -> 'a
val debug : ?level:int -> ('a, Format.formatter, unit) format -> 'a
val fdebug :
?level:int -> (unit -> (unit, Format.formatter, unit) format) -> unit
val set_debug_level : int -> unit
val get_debug_level : unit -> int
val is_debug_enabled : unit -> bool
val set_tagged_entry : bool -> unit
val set_log_level : string -> unit
val quiet : unit -> unit
val channel_set_color : bool -> channel -> unit
val channel_get_color : channel -> bool
val set_color : bool -> unit
val get_color : unit -> bool
val set_logging : (string -> unit) option -> channel -> unit
end
module type ChannelGroup = sig
val name : string
end
type channel = {
kind : ChannelKind.t;
mutable ppfs : Format.formatter list;
}
module Make (G : ChannelGroup) = struct
let set_log_level, log_level_of_chkind, get_log_level, quiet =
let loglevel = ref (ChannelKind.loglevel Info) in
( (fun (s : string) ->
loglevel := ChannelKind.of_string s |> ChannelKind.loglevel),
(fun ck ->
let ck_loglevel = ChannelKind.loglevel ck in
if ck_loglevel < !loglevel then loglevel := ck_loglevel),
(fun () -> !loglevel),
fun () -> loglevel := max_int )
type nonrec channel = channel
let default_out kind = { kind; ppfs = [ Format.std_formatter ] }
let err_out kind = { kind; ppfs = [ Format.err_formatter ] }
let debug_channel = default_out Debug
and step_channel = default_out Step
and info_channel = default_out Info
and result_channel = default_out Result
and warning_channel = err_out Warning
and error_channel = err_out Error
and fatal_channel = err_out Fatal
let channels =
[
debug_channel;
step_channel;
info_channel;
warning_channel;
result_channel;
error_channel;
fatal_channel;
]
let set_formatters ppfs channel = channel.ppfs <- ppfs
let reset_channel : channel -> unit =
fun channel ->
match channel.kind with
| Info | Result | Step | Debug ->
set_formatters [ Format.std_formatter ] channel
| Warning | Error | Fatal -> set_formatters [ Format.err_formatter ] channel
let set_tagged_entry, get_tagged_entry =
let tag = ref true in
((fun ta -> tag := ta), fun () -> !tag)
let channel_group_delimiter = ':'
let channel_name chan_kind =
let chan_kind_name = ChannelKind.to_string chan_kind in
if G.name = "" then chan_kind_name
else sprintf "%s%c%s" G.name channel_group_delimiter chan_kind_name
let channel_kind_of_tagstring tag_string =
match String.index tag_string channel_group_delimiter with
| n ->
assert (n <> 0);
String.sub tag_string (n + 1) (String.length tag_string - n - 1)
| exception Not_found -> tag_string
let is_channel_tagstring tag_string =
channel_kind_of_tagstring tag_string |> ChannelKind.is_string_identifier
let tag_functions ppf =
let mark_open_stag = function
| String_tag tag_string -> (
match Color.string_to_terminal_color_codes tag_string with
| "" -> ""
| code -> sprintf "\027[0;%sm" code)
| Font (color, emphasis) ->
sprintf "\027[%s%sm"
(Emphasis.terminal_encoding emphasis)
(Color.terminal_encoding color)
| _ -> ""
and print_open_stag = function
| String_tag tag_string ->
if get_tagged_entry () && is_channel_tagstring tag_string then
fprintf ppf "[%s] " tag_string
| _ -> ()
and print_close_stag _tag_string = ()
and mark_close_stag _ = "\027[0m" in
{ mark_open_stag; mark_close_stag; print_open_stag; print_close_stag }
let local_profile (channel : ChannelKind.t) (profile : Profile.t) : stag =
match channel with
| Debug -> Font profile.debug
| Step -> Font profile.step
| Info -> Font profile.info
| Result -> Font profile.result
| Warning -> Font profile.warning
| Error -> Font profile.error
| Fatal -> Font profile.fatal
let log finally channel txt =
let ppfs = channel.ppfs in
let pp fmt txt =
if ChannelKind.loglevel channel.kind >= get_log_level () then (
Format.pp_open_stag fmt (local_profile channel.kind !profile);
Format.pp_open_stag fmt (String_tag (channel_name channel.kind));
Format.kfprintf
(fun fmt ->
Format.kfprintf
(fun fmt -> Format.kfprintf finally fmt "@]@}@}@.")
fmt txt)
fmt "@[<hov 0>")
else Format.ikfprintf finally fmt txt
in
let rec aux = function
| [] -> assert false
| [ ppf ] -> pp ppf txt
| ppf :: ppfs ->
ignore @@ pp ppf txt;
aux ppfs
in
aux ppfs
let mk_level_functions chan =
let level = ref 0 in
( (fun () -> !level),
(fun n ->
assert (n >= 0);
level := n;
log_level_of_chkind chan.kind),
fun lvl -> lvl <= !level )
let get_debug_level, set_debug_level, debug_pass =
mk_level_functions debug_channel
let is_debug_enabled =
let threshold = ChannelKind.loglevel Debug in
fun () -> get_log_level () <= threshold
let get_info_level, set_info_level, info_pass =
mk_level_functions info_channel
let get_warning_level, set_warning_level, warning_pass =
mk_level_functions warning_channel
let leveled_channel finally channel level_pass ?(level = 0) txt =
if level_pass level then log finally channel txt
else Format.ifprintf Format.std_formatter txt
let finally_unit _ = ()
let step txt = log finally_unit step_channel txt
let debug ?(level = 0) txt =
leveled_channel finally_unit debug_channel debug_pass ~level txt
let fdebug ?(level = 0) f =
if debug_pass level then log finally_unit debug_channel (f ())
else Format.ifprintf Format.std_formatter ""
let info ?(level = 0) txt =
leveled_channel finally_unit info_channel info_pass ~level txt
let warning ?(level = 0) txt =
leveled_channel finally_unit warning_channel warning_pass ~level txt
let fatal ?(e = Failure "abort") txt =
log (fun _ -> raise e) fatal_channel txt
let error txt = log finally_unit error_channel txt
let result txt = log finally_unit result_channel txt
let _ =
List.iter
(fun channel ->
let ppfs = channel.ppfs in
List.iter
(fun ppf ->
pp_set_formatter_stag_functions ppf (tag_functions ppf);
pp_set_print_tags ppf true)
ppfs)
channels
let channel_set_color, channel_get_color =
let color_tbl = Hashtbl.create (List.length ChannelKind.values) in
( (fun b channel ->
Hashtbl.replace color_tbl channel b;
let ppfs = channel.ppfs in
List.iter (fun ppf -> pp_set_mark_tags ppf b) ppfs),
fun channel ->
match Hashtbl.find color_tbl channel with
| color_bool -> color_bool
| exception Not_found -> false )
let set_color, get_color =
let v = ref false in
( (fun b ->
v := b;
List.iter (channel_set_color b) channels),
fun () -> !v )
let set_logging send channel =
match send with
| None -> reset_channel channel
| Some f ->
let buffer = Buffer.create 2048 in
let out_string str start len =
Buffer.add_substring buffer str start len
in
let flush () =
let msg = Buffer.contents buffer in
Buffer.reset buffer;
f msg
in
set_formatters [ Format.make_formatter out_string flush ] channel
end
module type GROUP = sig
include S
module Sub (_ : ChannelGroup) : S with type channel = channel
end
module Group (G : ChannelGroup) : GROUP = struct
let loggers : (module S with type channel = channel) Queue.t = Queue.create ()
let iter : ((module S with type channel = channel) -> unit) -> unit =
fun f -> Queue.iter f loggers
include Make (G)
let set_warning_level n =
set_warning_level n;
iter (fun logger ->
let module L = (val logger) in
L.set_warning_level n)
let set_info_level n =
set_info_level n;
iter (fun logger ->
let module L = (val logger) in
L.set_info_level n)
let set_debug_level n =
set_debug_level n;
iter (fun logger ->
let module L = (val logger) in
L.set_debug_level n)
let set_tagged_entry v =
set_tagged_entry v;
iter (fun logger ->
let module L = (val logger) in
L.set_tagged_entry v)
let set_log_level t =
set_log_level t;
iter (fun logger ->
let module L = (val logger) in
L.set_log_level t)
let quiet () =
quiet ();
iter (fun logger ->
let module L = (val logger) in
L.quiet ())
let channel_set_color v c =
channel_set_color v c;
iter (fun logger ->
let module L = (val logger) in
L.channel_set_color v c)
let set_color v =
set_color v;
iter (fun logger ->
let module L = (val logger) in
L.set_color v)
let set_logging f c =
set_logging f c;
iter (fun logger ->
let module L = (val logger) in
L.set_logging f c)
module Sub (G : ChannelGroup) : S with type channel = channel = struct
module L = Make (G)
include L
let () = Queue.add (module L : S with type channel = channel) loggers
end
end