but I can not find how to use Format.kfprintf, I think that like Printf.ksprintf, it is used when you want to create function that can handle different formats (http://form-ocaml.forge.ocamlcore.org/posts/ksprintf.html) but I am stuck.
kprintf is deprecated, you should use ksprintf instead. These functions takes a callback which is called with the format string fully expanded. For instance:
kfprintf is useful for implementing custom “printf”-like functions,
e.g. this simple logging system (inspired from Daniel Bünzli’s work):
# let log_enabled = ref true;;
val log_enabled : bool ref = {contents = true}
(* now the log function itself. It uses a continuation passing trick
to do the least amount of work possible when logging is disabled *)
# let log k =
if !log_enabled then (
k
(fun fmt ->
Format.printf "(@[<2>debug@ :time %.2f@ " (Sys.time());
Format.kfprintf (fun out -> Format.fprintf out "@])@.")
Format.std_formatter fmt)
);;
val log :
((('a, Format.formatter, unit, unit) format4 -> 'a) ->
unit) ->
unit = <fun>
(* now we can use the log function *)
# log (fun k->k "hello %d" 42);;
(debug :time 1.40 hello 42)
- : unit = ()
(* disabled, nothing happen *)
# log_enabled := false;;
- : unit = ()
# log (fun k->k "hello %d" 42);;
- : unit = ()
Sadly the types are a bit complicated to follow, but notice how kfprintf
is used to call custom code (the final “@])@.” to close parenthesis,
print a new line, and flush) after the user’s message.
let format_reporter
?(pp_header = pp_exec_header)
?(app = Format.std_formatter)
?(dst = Format.err_formatter) ()
=
let report src level ~over k msgf =
let k _ = over (); k () in
msgf @@ fun ?header ?tags fmt ->
let ppf = if level = App then app else dst in
Format.kfprintf k ppf ("%a@[" ^^ fmt ^^ "@]@.") pp_header (level, header)
in
{ report }
Now I understand Format.kprintf but what blocks me is this part: ("%a@[" ^^ fmt ^^ "@]@."). In Logs the default ouput is app_name: [level] message but with the code above, the part pp_header (level, header) that prints the header is at the last position of the argument and fmt is in fact something like (fun m -> m "mylog").
To give credit where it is due, the closure trick was actually suggested by @yallop and profiled by @drup to validate its performance before it got into logs.
The fmt is the one you specify in fun m -> m fmt so it’s well ordered: the "%a@[" is used by pp_header (level, header) and following arguments (if any and specified in the fun m -> m fmt function) will follow.
I read the original thread https://github.com/mirage/ocaml-git/pull/130 but still don’t understand how the fastest CPS implementation compares with heavyweight approach when we remove logging in compile-time?
Indeed that measurement doesn’t seem to have been made.
The reason might be that you rarely want to this in practice: you can be sure you will end-up regretting removing logging at some point when you get into trouble.
The actual incurred overhead (basically closure creation) on non-logging would still be interesting to know though.
Also, I added a benchmark testing the CPS technique to the flambda bench suite, so you can check that it is still valid. I don’t remember the URL, but I’m sure one of the flambda people (cc @chambart@mshinwell) will jump in and give it to us.