123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192(*---------------------------------------------------------------------------
Copyright (c) 2025 The more programmers. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)(* Reporting levels *)typelevel=Quiet|Stdout|Stderr|Error|Warning|Info|Debugletcurrent_level=Atomic.makeWarningletlevel()=Atomic.getcurrent_levelletset_levell=Atomic.setcurrent_levellletlevel_to_string=function|Quiet->"quiet"|Stdout->"stdout"|Stderr->"stderr"|Error->"error"|Warning->"warning"|Info->"info"|Debug->"debug"letlevel_of_strings=matchString.trimswith|"quiet"->OkQuiet|"stdout"->OkStdout|"stderr"->OkStderr|"error"->OkError|"warning"->OkWarning|"info"->OkInfo|"debug"->OkDebug|e->letpp_level=B0__fmt.codeinletkind=B0__fmt.any"log level"inletdom=["quiet";"stdout";"stderr";"error";"warning";"info";"debug"]inletunknown=B0__fmt.(unknown'~kindpp_level~hint:must_be)inB0__fmt.error"%a"unknown(e,dom)(* Default reporter *)letheader_stdout_style=[`Fg`Cyan]letheader_stderr_style=[`Fg`Cyan]letheader_err_style=[`Fg`Red]letheader_warn_style=[`Fg`Yellow]letheader_info_style=[`Fg`Blue]letheader_debug_style=[`Faint;`Fg`Magenta]leterror_style=`Bold::header_err_styleletwarning_style=`Bold::header_warn_styleletinfo_style=`Bold::header_info_styleletdebug_style=`Bold::header_debug_styleletpp_level_headerlevelppfheader=matchlevelwith|Stdout->B0__fmt.stheader_stdout_styleppfheader|Stderr->B0__fmt.stheader_stderr_styleppfheader|Error->B0__fmt.stheader_err_styleppfheader|Warning->B0__fmt.stheader_warn_styleppfheader|Info->B0__fmt.stheader_info_styleppfheader|Debug->B0__fmt.stheader_debug_styleppfheader|Quiet->()letpp_levelppflevel=matchlevelwith|Stdout|Stderr->()|Error->B0__fmt.sterror_styleppf"Error"|Warning->B0__fmt.stwarning_styleppf"Warning"|Info->B0__fmt.stinfo_styleppf"Info"|Debug->B0__fmt.stdebug_styleppf"Debug"|Quiet->()letexec=(* We use the name as given to execv because sometimes executables
masquerade as others by execv'ing. We want to use the name of the
program not of the masquerade (which may be meaningless). *)(* assert (Array.length Sys.argv > 0) *)Filename.basenameSys.argv.(0)letdefault_kmsgklevelmsgf=msgf@@fun?headerfmt->letppf=iflevel=StdoutthenB0__fmt.stdoutelseB0__fmt.stderrinletfinishppf=Format.pp_close_boxppf();Format.pp_close_boxppf();B0__fmt.flush_nlppf();k()inFormat.pp_open_boxppf0;beginmatchheaderwith|None->beginmatchlevelwith|Stderr|Stdout->Format.pp_open_boxppf0|level->(* "%s: @[%a: " *)B0__fmt.stringppfexec;B0__fmt.stringppf": ";Format.pp_open_boxppf0;pp_levelppflevel;B0__fmt.stringppf": ";end|Someheader->ifheader=""thenFormat.pp_open_boxppf0elsebegin(* "%s: @[[%s] " *)B0__fmt.stringppfexec;B0__fmt.stringppf": ";Format.pp_open_boxppf0;B0__fmt.charppf'[';pp_level_headerlevelppfheader;B0__fmt.stringppf"] ";endend;B0__fmt.kpffinishppffmt(* Log monitoring *)leterr_count'=Atomic.make0leterr_count()=Atomic.geterr_count'letwarn_count'=Atomic.make0letwarn_count()=Atomic.getwarn_count'(* Log functions *)type('a,'b)msgf=(?header:string->('a,Format.formatter,unit,'b)format4->'a)->'btype'alog=('a,unit)msgf->unittypereporter={kmsg:'a'b.(unit->'b)->level->('a,'b)msgf->'b}letreporter_default={kmsg=default_kmsg}letreporter=Atomic.makereporter_defaultletkmsgklevelmsgf=matchlevelwith|Error->Atomic.increrr_count';ifAtomic.getcurrent_level<Errorthenk()else(Atomic.getreporter).kmsgklevelmsgf|Warning->Atomic.incrwarn_count';ifAtomic.getcurrent_level<Warningthenk()else(Atomic.getreporter).kmsgklevelmsgf|_->letcurrent_level=Atomic.getcurrent_levelinifcurrent_level=Quiet||current_level<levelthenk()else(Atomic.getreporter).kmsgklevelmsgfletkunit_=()letmsglevelmsgf=kmsgkunitlevelmsgfletquietmsgf=kmsgkunitQuietmsgfletstdoutmsgf=kmsgkunitStdoutmsgfletstderrmsgf=kmsgkunitStderrmsgfleterrmsgf=kmsgkunitErrormsgfletwarnmsgf=kmsgkunitWarningmsgfletinfomsgf=kmsgkunitInfomsgfletdebugmsgf=kmsgkunitDebugmsgf(* Logging result errors *)letpp_err=B0__fmt.linesletif_error?(level=Error)?header~use=function|Okv->v|Errormsg->kmsg(fun_->use)level(funm->m?header"%a"pp_errmsg)letif_error'?(level=Error)?header~use=function|Ok_asv->v|Errormsg->kmsg(fun_->Okuse)level(funm->m?header"%a"pp_errmsg)letif_error_pppp?(level=Error)?header~use=function|Okv->v|Errore->kmsg(fun_->use)level(funm->m?header"%a"ppe)letif_error_pp'pp?(level=Error)?header~use=function|Ok_asv->v|Errore->kmsg(fun_->Okuse)level(funm->m?header"%a"ppe)(* Logging timings *)(* The churn here is because we have a recursive dep on Os.Mtime *)typetime_func={time:'a'b.?level:level->('a->(('b,Format.formatter,unit,'a)format4->'b)->'a)->(unit->'a)->'a}lettime_func_init={time=fun?(level=Info)mf->assertfalse}lettime_func=Atomic.maketime_func_initletset_time_functime=Atomic.settime_functimelettime?levelfmtf=(Atomic.gettime_func).time?levelfmtf(* Values *)letvalue?(level=Stderr)?idppv=matchidwith|None->kmsg(fun_->v)level(funm->m"%a"ppv)|Someid->kmsg(fun_->v)level(funm->m"id: @[%a@]"ppv)(* Module reporter *)moduleReporter=structtypet=reporter={kmsg:'a'b.(unit->'b)->level->('a,'b)msgf->'b}letnop=letkmsgklevelmsgf=k()in{kmsg}letdefault=reporter_defaultletget()=Atomic.getreporterletsetr=Atomic.setreporterrend