123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163(* Yoann Padioleau
*
* Copyright (C) 2020 r2c
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
* license.txt for more details.
*)(*****************************************************************************)(* Prelude *)(*****************************************************************************)(* Small wrapper around the easy_logging library.
*
* My requirements for a logging library are:
* (1) different log levels (Debug, Info, Warning, Error)
* (2) easy logging calls with sprintf style formatting by default
* (3) ability to log on stdout/stderr or in a file
* (4) easy one-line logger creation
* (5) hierarchical loggers where you can enable/disable a whole set
* of loggers
* (6) configurable with an external log config file
*
* There are a few OCaml logging libraries, but they usually miss one or more
* of the requirements above:
* - Logs: popular library according to OPAM metrics. However, Logs is heavily
* functorized (no (4)), and it requires to encapsulate your logging calls
* in a closure 'Log.info (fun m -> m "xxx")' (no (2)). It also lacks
* (5) and (6).
* - dolog: very basic logging library, very easy to use with (1), (2),
* but lacks especially (5) and (6)
* - Bolt: not updated since 2013
* - merlin/logging.ml: not available directly under OPAM and seems
* more limited than easy_logging
* - easy_logging: not really popular according to OPAM (no package depends
* on it), use OCaml objects, some documentation but no good examples,
* some unintuitive interfaces, some issues to compile without -42
* due to ambiguous constructors, 0.8 still not in OPAM, etc.
* But, it supports all the requirements if you know how to use it!
* => I use easy_logging (actually I use easy_logging_yojson for (6))
*
*)openEasy_logging_yojsontypelevel=Easy_logging__.Logging_types.levelmoduleHandlers=Easy_logging_yojson.Handlersclasstypelogger=object(** {3 Classic logging Methods}
Each of these methods takes an optional [string list] of tags, then a set of parameters the way a printf function does. If the log level of the instance is low enough, a log item will be created theb passed to the handlers.
Example :
{[logger#warning "Unexpected value: %s" (to_string my_value)]}
*)methodflash:'a.?tags:stringlist->('a,unit,string,unit)format4->'amethoderror:'a.?tags:stringlist->('a,unit,string,unit)format4->'amethodwarning:'a.?tags:stringlist->('a,unit,string,unit)format4->'amethodinfo:'a.?tags:stringlist->('a,unit,string,unit)format4->'amethodtrace:'a.?tags:stringlist->('a,unit,string,unit)format4->'amethoddebug:'a.?tags:stringlist->('a,unit,string,unit)format4->'a(** {3 Lazy logging methods}
Each of these methods takes a [string lazy_t] as an input (as well as the optional tags. If the log level of the instance is low enough, the lazy value will forced into a [string], a log item will be created then passed to the handlers.
Example:
{[logger#ldebug (lazy (heavy_calculation () ))]}
*)methodldebug:?tags:stringlist->stringlazy_t->unitmethodltrace:?tags:stringlist->stringlazy_t->unitmethodlinfo:?tags:stringlist->stringlazy_t->unitmethodlwarning:?tags:stringlist->stringlazy_t->unitmethodlerror:?tags:stringlist->stringlazy_t->unitmethodlflash:?tags:stringlist->stringlazy_t->unit(** {3 String logging methods}
Each of these methods takes a [string] as an input (as well as the optional tags).
Example:
{[logger#sdebug string_variable]}
*)methodsdebug:?tags:stringlist->string->unitmethodstrace:?tags:stringlist->string->unitmethodsinfo:?tags:stringlist->string->unitmethodswarning:?tags:stringlist->string->unitmethodserror:?tags:stringlist->string->unitmethodsflash:?tags:stringlist->string->unit(** {3 Other methods} *)methodname:stringmethodinternal_level:levelmethodset_level:level->unit(** Sets the log level of the logger instance. *)methodadd_handler:Handlers.t->unit(** Adds a handler to the logger instance. *)methodget_handlers:Handlers.tlistmethodset_handlers:Handlers.tlist->unitmethodadd_tag_generator:(unit->string)->unit(** Will add a tag to each log message, resulting from the call of the supplied fonction (called each time a message is logged)*)methodset_propagate:bool->unit(** Sets the propagate attribute, which decides whether messages passed to this logger are propagated to its ancestors' handlers. *)(** {4 Internal methods} *)methodget_handlers_propagate:Handlers.tlist(** Returns the list of handlers of the logger, recursing with parents handlers
if propagate is true*)methodeffective_level:level(** Returns this logger level if it is not [None], else searches amongst ancestors for the first defined level; returns [NoLevel] if no level can be found. *)end(*****************************************************************************)(* Entry points *)(*****************************************************************************)letall_loggers=ref([]:loggerlist)letapply_to_all_loggersf=List.iter(funlogger->flogger)!all_loggersletget_loggers()=!all_loggersletset_global_levellevel=apply_to_all_loggers(funlogger->logger#set_levellevel)letadd_PID_tag()=letpid_string=Unix.getpid()|>string_of_intinapply_to_all_loggers(funlogger->logger#add_tag_generator(fun()->pid_string))letget_loggerxs:logger=letfinal_name="Main"::xs|>String.concat"."inletlogger=Logging.get_loggerfinal_nameinall_loggers:=logger::!all_loggers;loggerletload_config_filefile=Logging.load_global_config_filefile