module Logs:sig
..end
Logs
provides a basic logging infrastructure. Logging
is performed on sources whose reporting
level can be set independently. Log message
report is decoupled from logging and handled by a
reporter.
See the basics, a few usage conventions to respect and a note on synchronous logging.
v0.6.2-3-g34b43cb - homepage
type
level =
| |
App |
| |
Error |
| |
Warning |
| |
Info |
| |
Debug |
Log sources have an optional reporting level. If
the level is Some l
then any message whose level is smaller or
equal to l
is reported. If the level is None
no message is
ever reported.
val level : unit -> level option
val set_level : ?all:bool -> level option -> unit
set_level ?all l
sets the reporting level given to
new sources. If all
is true
(default), also
sets the reporting level of all existing sources. Use
Logs.Src.set_level
to only affect a specific source. Only applications
should use this function directly see usage conventions.val pp_level : Format.formatter -> level -> unit
pp_level ppf l
prints an unspecified representation of l
on
ppf
.val level_to_string : level option -> string
level_to_string l
converts l
to an US-ASCII string that can be
parsed back by Logs.level_of_string
and by the LEVEL
option
argument of Logs_cli.level
.val level_of_string : string -> (level option, [ `Msg of string ]) Result.result
type
src
val default : src
module Src:sig
..end
module Tag:sig
..end
type('a, 'b)
msgf =(?header:string ->
?tags:Tag.set ->
('a, Format.formatter, unit, 'b) Pervasives.format4 -> 'a) ->
'b
Message formatting functions are called with a message construction function whenever a message needs to be reported. The message formatting function must call the given message construction function with a format string and its arguments to define the message contents, see the basics for examples. The optional arguments of the message construction function are:
header
, an optional printable message header. Default to None
.tags
, a set of tags to attach to the message. Defaults
Logs.Tag.empty
.type'a
log =('a, unit) msgf -> unit
val msg : ?src:src -> level -> 'a log
msg ?src l (fun m -> m fmt ...)
logs with level l
on the source
src
(defaults to Logs.default
) a message formatted with fmt
. For the
semantics of levels see the the usage conventions.val app : ?src:src -> 'a log
app
is msg App
.val err : ?src:src -> 'a log
err
is msg Error
.val warn : ?src:src -> 'a log
warn
is msg Warning
.val info : ?src:src -> 'a log
info
is msg Info
.val debug : ?src:src -> 'a log
debug
is msg Debug
.val kmsg : (unit -> 'b) -> ?src:src -> level -> ('a, 'b) msgf -> 'b
result
value Error
sval on_error : ?src:src ->
?level:level ->
?header:string ->
?tags:Tag.set ->
pp:(Format.formatter -> 'b -> unit) ->
use:('b -> 'a) -> ('a, 'b) Result.result -> 'a
on_error ~level ~pp ~use r
is:
v
if r = Ok v
use e
if r = Error e
. As a side effect msg
is logged
with pp
on level level
(defaults to Logs.Error
).val on_error_msg : ?src:src ->
?level:level ->
?header:string ->
?tags:Tag.set ->
use:(unit -> 'a) -> ('a, [ `Msg of string ]) Result.result -> 'a
module type LOG =sig
..end
val src_log : src -> (module Logs.LOG)
type
reporter = {
|
report : |
A reporter formats and handles log messages that get
reported. Whenever a log function gets called on a source
with a level equal or smaller to the source's reporting
level, the current reporter's field r.report
gets called as r.report src level ~over k msgf
where:
src
is the logging source.level
is the reporting level.over
must be called by the reporter once the logging operation is
over from the reporter's perspective. This may happen before or
after k
is called.k
is the function to invoke to return.msgf
is the message formatting function to call.val nop_reporter : reporter
nop_reporter
is the initial reporter returned by Logs.reporter
, it
does nothing if a log message gets reported.val format_reporter : ?pp_header:(Format.formatter -> level * string option -> unit) ->
?app:Format.formatter -> ?dst:Format.formatter -> unit -> reporter
format_reporter ~pp_header ~app ~dst ()
is a reporter that reports
App
level messages on app
(defauts to Format.std_formatter
)
and all other level on dst
(defaults to Format.err_formatter
).
pp_header
determines how message headers are rendered. The default
prefixes the program name and renders the header with Logs.pp_header
.
Use Logs_fmt.reporter
if you want colored headers rendering.
The reporter does not process or render information about message sources or tags.
Important. This is a synchronous reporter it considers the log
operation to be over once the message was formatted and before
calling the continuation (see the note on synchronous
logging). In particular if the formatters are baked by channels,
it will block until the message has been formatted on the channel
before proceeding which may not be suitable in a cooperative
concurrency setting like Lwt
.
val reporter : unit -> reporter
reporter ()
is the current repporter.val set_reporter : reporter -> unit
set_reporter r
sets the current reporter to r
.val pp_header : Format.formatter -> level * string option -> unit
pp_header ppf (l, h)
prints an unspecified representation
of log header h
for level l
.val err_count : unit -> int
err_count ()
is the number of messages logged with level Error
across all sources.val warn_count : unit -> int
warn_count ()
is the number of messages logged with level
Warning
across all sources.
In order to minimize the overhead whenever a log message is not reported, message formatting only occurs on actual message report via the message formatting function you provide to log functions. This leads to the following logging structure:
let k, v = ... in
Logs.err (fun m -> m "invalid kv (%a,%a)" pp_key k pp_val v);
Logs.err (fun m -> m "NO CARRIER");
The pattern is quite simple: it is as if you were formatting with
a printf
-like function except you get this function in the m
argument of the function you give to the logging function.
If you want to abstract a repeated log report it is better to keep the message formatting function structure for the arguments of the messages. Here's how the above examples can be abstracted and invoked:
let err_invalid_kv args =
Logs.err @@ fun m ->
args (fun k v -> m "invalid kv (%a,%a)" pp_key k pp_val v)
let err_no_carrier args =
Logs.err @@ fun m -> args (m "NO CARRIER")
let () =
err_invalid_kv (fun args -> args "key" "value");
err_no_carrier (fun () -> ());
()
Note that log messages are formatted and hit the reporter only if
they have not been filtered out by the current
reporting level of the source you log on. See also
the log source and reporting level usage conventions.
If you are writing an application you must remember to set the reporter before any logging operation takes place otherwise no messages will be reported. For example if you are using the formatter reporter, logging can be setup as follows:
let main () =
Logs.set_reporter (Logs_fmt.reporter ());
...
exit (if Logs.err_count > 0 then 1 else 0);
()
If you have logging code that is performed in the toplevel
initialization code of modules (not a good idea) or you depend on
(bad) libraries that do so, you must call and link the reporter
install code before these initialization bits are being executed
otherwise you will miss these messages.
The documentation of Logs_cli
module has a full setup
example that includes command line options to control color and log
reporting level.
If you are writing a library you should neither install reporters, nor
set the reporting level of sources, nor log on the Logs.default
source or
at the App
level; follow the the usage conventions. A
library should simply log on an another existing source or define
its own source like in the example below:
let src = Logs.Src.create "mylib.network" ~doc:"logs mylib's network events"
module Log = (val Logs.src_log src : Logs.LOG)
The Log
module defines logging functions that are specific to the
source src
.
A library should never log on the Logs.default
source or at the
App
level these are reserved for use by the application. It
should either create a source for itself or log on the source
defined by one of its dependencies. It should also never set the
reporting level of the sources it deals with or install reporters since
control over this must be left to the application.
The semantics of reporting levels should be understood as follows:
App
, this level can be used for the standard output or console
of an application. It should never be used by libraries.Error
, error condition that prevent the program from
running normally.Warning
, suspicious condition that does not prevent the
program from running normally but may eventually lead to an
error condition.Info
, condition that allows the program user to get a better
understanding of what the program is doing.Debug
, condition that allows the program developer to get a
better undersanding of what the program is doing.
In synchronous logging, a client call to a log function proceeds
only once the reporter has finished the report operation. In
Logs
this depends both on the reporter and the log functions
that the client uses.
Whenever the client uses a log function that results in a report,
it gives the reporter a continuation that defines the result type
of the log function and a callback to be called whenever the log
operation is over from the reporter's perspective (see Logs.reporter
).
The typical use of the callback is to unblock the continuation given
to the reporter. This is used by Logs_lwt
's log functions to make
sure that the threads they return proceed only once the report is over.
In the functions of Logs
however the callback does nothing as there
is no way to block the polymorphic continuation.
Now considering reporters, at the extreme we have:
Logs_fmt.reporter
with
formatters baked by channels: when formatting returns the
message has been written on the channel.
However a purely synchronous reporter like Logs_fmt.reporter
acting on channels does not play well with Lwt
's cooperative
runtime system. It is possible to reuse Logs_fmt.reporter
to
define a cooperative reporter, see this
example. However while this reporter makes Logs_lwt
's log
functions synchronous, those of Logs
behave asynchronously. For
now it seems it that this is unfortunately the best we can do if
we want to preserve the ability to use Logs
with or without
cooperative concurency.
This example uses a tag to attach Mtime
time spans in
log messages. The custom reporter uses these time spans to format
relative timings for runs of a given function. Note that as done
below the timings do include logging time.
let stamp_tag : Mtime.span Logs.Tag.def =
Logs.Tag.def "stamp" ~doc:"Relative monotonic time stamp" Mtime.pp_span
let stamp c = Logs.Tag.(empty |> add stamp_tag (Mtime.count c))
let run () =
let rec wait n = if n = 0 then () else wait (n - 1) in
let c = Mtime.counter () in
Logs.info (fun m -> m "Starting run");
let delay1, delay2, delay3 = 10_000, 20_000, 40_000 in
Logs.info (fun m -> m "Start action 1 (%d)." delay1 ~tags:(stamp c));
wait delay1;
Logs.info (fun m -> m "Start action 2 (%d)." delay2 ~tags:(stamp c));
wait delay2;
Logs.info (fun m -> m "Start action 3 (%d)." delay3 ~tags:(stamp c));
wait delay3;
Logs.info (fun m -> m "Done." ?header:None ~tags:(stamp c));
()
let reporter ppf =
let report src level ~over k msgf =
let k _ = over (); k () in
let with_stamp h tags k ppf fmt =
let stamp = match tags with
| None -> None
| Some tags -> Logs.Tag.find stamp_tag tags
in
let dt = match stamp with None -> 0. | Some s -> (Mtime.to_us s) in
Format.kfprintf k ppf ("%a[%0+04.0fus] @[" ^^ fmt ^^ "@]@.")
Logs.pp_header (level, h) dt
in
msgf @@ fun ?header ?tags fmt -> with_stamp header tags k ppf fmt
in
{ Logs.report = report }
let main () =
Logs.set_reporter (reporter (Format.std_formatter));
Logs.set_level (Some Logs.Info);
run ();
run ();
()
let () = main ()
Here is the standard output of a sample run of the program:
[INFO][+000us] Starting run [INFO][+168us] Start action 1 (10000). [INFO][+206us] Start action 2 (20000). [INFO][+243us] Start action 3 (40000). [INFO][+303us] Done. [INFO][+000us] Starting run [INFO][+012us] Start action 1 (10000). [INFO][+038us] Start action 2 (20000). [INFO][+074us] Start action 3 (40000). [INFO][+133us] Done.