Module Logs

module Logs: sig .. end
Logging.

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



Reporting levels


type level = 
| App
| Error
| Warning
| Info
| Debug
The type for reporting levels. For level semantics see the usage conventions.

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
level () is the reporting level given to new sources.
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
level_of_string s parses the representation of Logs.level_to_string from s.

Log sources


type src 
The type for log sources. A source defines a named unit of logging whose reporting level can be set independently.
val default : src
default is a logging source that is reserved for use by applications. See usage conventions.
module Src: sig .. end
Sources.

Log functions


module Tag: sig .. end
Message tags.
type ('a, 'b) msgf = (?header:string ->
?tags:Tag.set ->
('a, Format.formatter, unit, 'b) Pervasives.format4 -> 'a) ->
'b
The type for client specified message formatting functions.

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:


type 'a log = ('a, unit) msgf -> unit 
The type for log functions. See the basics to understand how to use log functions.
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
kmsg k is like Logs.msg but calls k for returning.

Logging result value Errors


val 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:
val on_error_msg : ?src:src ->
?level:level ->
?header:string ->
?tags:Tag.set ->
use:(unit -> 'a) -> ('a, [ `Msg of string ]) Result.result -> 'a
on_error_msg is like Logs.on_error but uses Format.pp_print_text to format the message.

Source specific log functions


module type LOG = sig .. end
The type for source specific logging functions.
val src_log : src -> (module Logs.LOG)
src_log src is a set of logging functions for src.

Reporters


type reporter = {
   report : 'a 'b.
src ->
level ->
over:(unit -> unit) -> (unit -> 'b) -> ('a, 'b) msgf -> 'b
;
}
The type for reporters.

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:

See an example.
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.

Logs monitoring


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.

Basics

Logging

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.

Reporter setup

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.

Usage conventions

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:

Note on synchronous logging

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:

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.

Example with custom reporter and tags

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.