123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710(******************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2019 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)openError_monadtypecurrent={day:int*int*int;fd:Lwt_unix.file_descr}typerotating={rights:int;days_kept:int;base_path:string;current:currentref;}typeoutput=|StaticofLwt_unix.file_descr|Rotatingofrotating|SyslogofSyslog.ttypet={output:output;format:[`One_per_line|`Netstring|(* See https://tools.ietf.org/html/rfc5424#section-6 *)`Pp_RFC5424|`Pp_short];colors:bool;filter:[`Level_at_leastofInternal_event.Level.t|`Per_section_prefixof(Internal_event.Section.t*Internal_event.Level.toption)list];}lethostname=Option.value_f(Sys.getenv_opt"TEZOS_EVENT_HOSTNAME")~default:Unix.gethostnametype'eventwrapped={time_stamp:Ptime.t;section:Internal_event.Section.t;event:'event;}letwraptime_stampsectionevent={time_stamp;section;event}letis_syslogo=matchowithSyslog_->true|_->falsemoduleColor=structletreset="\027[0m"letreset_len=4letcolor_len=5letbold="\027[1m"letbold_len=4moduleFG=structletred="\027[31m"letyellow="\027[33m"endendletwrapped_encodingevent_encoding=letopenData_encodinginletptime_encoding=convPtime.to_float_s(funf->matchPtime.of_float_sfwith|None->invalid_arg"File-descriptor-sink: invalid timestamp"|Somes->s)floatinletv0=conv(fun{time_stamp;section;event}->(hostname,time_stamp,section,event))(fun(_,time_stamp,section,event)->{time_stamp;section;event})(obj4(req"hostname"string)(req"time_stamp"ptime_encoding)(req"section"Internal_event.Section.encoding)(req"event"event_encoding))inWith_version.(encoding~name:"fd-sink-item"(first_versionv0))letmake_with_pp_rfc5424ppwrapped_eventname=(* See https://tools.ietf.org/html/rfc5424#section-6 *)Format.asprintf"%a [%a.%s] %a\n"(Ptime.pp_rfc3339~frac_s:3())wrapped_event.time_stampInternal_event.Section.ppwrapped_event.sectionname(pp~all_fields:false~block:false)wrapped_event.eventtypecolor_setting=Enabledofstringoption|Disabledletmake_with_pp_short~colorppwrapped_event=letpp_datefmttime=lettime=Ptime.to_float_stimeinlettm=Unix.localtimetimeinletmonth_string=matchtm.Unix.tm_monwith|0->"Jan"|1->"Feb"|2->"Mar"|3->"Apr"|4->"May"|5->"Jun"|6->"Jul"|7->"Aug"|8->"Sep"|9->"Oct"|10->"Nov"|11->"Dec"|_->assertfalse(* `tm` is built locally, so it should contain invalid month code *)inletms=mod_float(time*.1000.)1000.inFormat.fprintffmt"%s %02d %02d:%02d:%02d.%03.0f"month_stringtm.Unix.tm_mdaytm.Unix.tm_hourtm.Unix.tm_mintm.Unix.tm_secmsinletlines=String.split_on_char'\n'(Format.asprintf"%a"(pp~all_fields:false~block:true)wrapped_event.event)inletcolor_total_size,bold_total_size=matchcolorwith|Enabledcolor_opt->letcolor_total_size=ifOption.is_somecolor_optthenColor.(color_len+reset_len)else0inletbold_total_sizei=ifi=0thenColor.(bold_len+reset_len)else0in(color_total_size,bold_total_size)|Disabled->(0,fun_i->0)inlettimestamp=Format.asprintf"%a: "pp_datewrapped_event.time_stampinlettimestamp_size=String.lengthtimestampinletlines_size=List.fold_left_i(funiaccs->(* computing the total length of a line *)acc+timestamp_size+String.lengths+1+color_total_size+bold_total_sizei)0linesinletbuf=Bytes.createlines_sizeinletprev_pos=ref0inletblitslen=Bytes.blit_strings0buf!prev_poslen;prev_pos:=!prev_pos+leninletenable_color,color_tag_opt=matchcolorwith|Enabledcolor_tag->(true,color_tag)|Disabled->(false,None)inlet()=List.iteri(funis->letbold_first_header=enable_color&&i=0inlets_len=String.lengthsinifbold_first_headerthenblitColor.boldColor.bold_len;blittimestamptimestamp_size;ifbold_first_headerthenblitColor.resetColor.reset_len;Option.iter(funtag->blittagColor.color_len)color_tag_opt;blitss_len;ifOption.is_somecolor_tag_optthenblitColor.resetColor.reset_len;blit"\n"1)linesinBytes.unsafe_to_stringbuflet%expect_test_=letpp_string~all_fields:_~block:_=Format.pp_print_textinletmake_timestampflt=matchPtime.of_float_sfltwithNone->assertfalse|Somev->vinletts=1682584149.77736807inletlocal_dependant_ts=fst@@Unix.mktime@@Unix.gmtimetsinlettime_stamp=make_timestamplocal_dependant_tsinletev={event="toto";time_stamp;section=Internal_event.Section.make_sanitized["my";"section"];}inprint_endline(make_with_pp_short~color:Disabledpp_stringev);[%expect{| Apr 27 08:29:09.000: toto |}];letev_line_cut={evwithevent="totototototototototototototototo before_cut after_cut"}inprint_endline(String.escaped@@make_with_pp_short~color:(EnabledNone)pp_stringev_line_cut);[%expect{|
\027[1mApr 27 08:29:09.000: \027[0mtotototototototototototototototo before_cut\nApr 27 08:29:09.000: after_cut\n|}];print_endline(String.escaped@@make_with_pp_short~color:(EnabledNone)pp_stringev);[%expect{| \027[1mApr 27 08:29:09.000: \027[0mtoto\n|}];print_endline(String.escaped@@make_with_pp_short~color:(Enabled(SomeColor.FG.red))pp_stringev);[%expect{| \027[1mApr 27 08:29:09.000: \027[0m\027[31mtoto\027[0m\n|}];letev={evwithtime_stamp=make_timestampts}inprint_endline(make_with_pp_rfc5424pp_stringev"my-event");[%expect{| 2023-04-27T08:29:09.777-00:00 [my.section.my-event] toto |}];()letmake_for_syslogppwrapped_event=(* Syslog is handling the formating. Only the message is printed. *)Format.asprintf"%a"(pp~all_fields:false~block:false)wrapped_event.eventletday_of_the_yearts=lettoday=matchPtime.of_float_stswithSomes->s|None->Ptime.mininlet(y,m,d),_=Ptime.to_date_timetodayin(y,m,d)letstring_of_day_of_the_year(y,m,d)=Format.sprintf"%d%02d%02d"ymdletcheck_file_format_with_datebase_filenames=letname_no_ext=Filename.remove_extensionbase_filenameinletext=Filename.extensionbase_filenameinletopenRe.Perlinletre_ext="(."^ext^")?"inletre_date="-\\d{4}\\d{2}\\d{2}"inletre=compile@@re(name_no_ext^re_date^re_ext)inRe.execpreslet%expect_test_=print_endline(Bool.to_string(check_file_format_with_date".out""a.out"));[%expect{| false |}];print_endline(Bool.to_string(check_file_format_with_date"some-name.log""some-name-19991231.log"));[%expect{| true |}];print_endline(Bool.to_string(check_file_format_with_date"hello.""hello-19991231."));[%expect{| true |}];print_endline(Bool.to_string(check_file_format_with_date".log""19991231.log"));[%expect{| false |}];print_endline(Bool.to_string(check_file_format_with_date".log.log"".log-19991231.log"));[%expect{| true |}];print_endline(Bool.to_string(check_file_format_with_date"file""file-19991231"));[%expect{| true |}];()letfilename_insert_before_ext~paths=letext=Filename.extensionpathinletchopped=ifext=""thenpathelseFilename.chop_extensionpathinFormat.asprintf"%s-%s%s"choppedsextlet%expect_test_=print_endline(filename_insert_before_ext~path:"foo.bar""baz");[%expect{| foo-baz.bar |}];print_endline(filename_insert_before_ext~path:"/tmp/log.out""11");[%expect{| /tmp/log-11.out |}];print_endline(filename_insert_before_ext~path:"/dev/null""XXX");[%expect{| /dev/null-XXX |}];()letoverwrite_syslog_tagsys_loggersection=Syslog.{sys_loggerwithtag=Format.asprintf"%a"Internal_event.Section.ppsection;}moduleMake_sink(K:sigvalkind:[`Path|`Stdout|`Stderr|`Syslog]end):Internal_event.SINKwithtypet=t=structtypenonrect=tleturi_scheme=matchK.kindwith|`Path->"file-descriptor-path"|`Stdout->"file-descriptor-stdout"|`Stderr->"file-descriptor-stderr"|`Syslog->"file-descriptor-syslog"letfail_parsingurifmt=Format.kasprintf(failwith"Parsing URI: %s: %s"(Uri.to_stringuri))fmtletconfigureuri=letflagname=matchUri.get_query_paramurinamewithSome"true"->true|_->falseinletopenLwt_result_syntaxinletsection_prefixes=letall=List.filter_map(function"section-prefix",l->Somel|_->None)(Uri.queryuri)inmatchallwith[]->None|more->Some(List.concatmore)inlet*filter=match(Uri.get_query_paramuri"level-at-least",section_prefixes)with|None,None->return(`Level_at_leastInternal_event.Level.default)|Somel,None->(matchInternal_event.Level.of_stringlwith|Somel->return(`Level_at_leastl)|None->fail_parsinguri"Wrong level: %S"l)|base_level,Somel->(tryletsections=letparse_sections=matchString.split_on_char':'swith|[one]->(Internal_event.Section.make_sanitized(String.split_on_char'.'one),SomeInternal_event.Level.default)|[one;two]->letlvl=matchString.lowercase_asciitwowith|"none"->None|s->(matchInternal_event.Level.of_stringswith|Somes->Somes|None->Format.kasprintfStdlib.failwith"Wrong level name: %S in argument %S"twos)inletsection=matchonewith|""->Internal_event.Section.empty|_->Internal_event.Section.make_sanitized(String.split_on_char'.'one)in(section,lvl)|_->Format.kasprintfStdlib.failwith"Wrong section-level entry: %S"sinletpairs=List.mapparse_sectionlinmatchbase_levelwith|None->pairs|Somelvl->(matchInternal_event.Level.of_stringlvlwith|Somel->(* establish default for all sections *)pairs@[(Internal_event.Section.empty,Somel)]|None->Format.kasprintfStdlib.failwith"Wrong level name %S in level-at-least argument"lvl)inreturn(`Per_section_prefixsections)withFailures->fail_parsinguri"%s"s)inlet*format=matchUri.get_query_paramuri"format"with|Some"netstring"->return`Netstring|Some"pp-short"->return`Pp_short|Some"pp-rfc5424"->return`Pp_RFC5424|Some"pp"->return`Pp_RFC5424|None|Some"one-per-line"->return`One_per_line|Someother->fail_parsinguri"Unknown format: %S"otherinletcolors=flag"colors"inlet*output=matchK.kindwith|`Path->let*rotate=matchUri.get_query_paramuri"daily-logs"with|Somen->(matchint_of_string_optnwith|Somen->return_somen|None->fail_parsinguri"daily-logs should be an integer : %S"n)|None->return_noneinletwith_pid=flag"with-pid"inletfresh=flag"fresh"inlet*rights=matchUri.get_query_paramuri"chmod"with|Somen->(matchint_of_string_optnwith|Somei->returni|None->fail_parsinguri"Access-rights parameter should be an integer: %S"n)|None->return0o600inlet*path=matchUri.pathuriwith|""|"/"->fail_parsinguri"Missing path configuration."|path->returnpathinletallow_create_dir=flag"create-dirs"inlet*!()=ifallow_create_dirthenLwt_utils_unix.create_dir(Filename.dirnamepath)elseLwt.return_unitinletopenLwt_result_syntaxinlettime_ext,rotation=matchrotatewith|Somedays_kept->lettoday=day_of_the_year(Unix.gettimeofday())in(string_of_day_of_the_yeartoday,Some(days_kept,today))|None->("",None)inletbase_path=ifwith_pidthenfilename_insert_before_ext~path(string_of_int(Unix.getpid()))elsepathinletfixed_path=ifrotate<>Nonethenfilename_insert_before_ext~pathtime_extelsebase_pathinprotect(fun()->Lwt_result.ok@@Lwt_unix.(letflags=[O_WRONLY;O_CREAT;O_CLOEXEC]@iffreshthen[O_TRUNC]else[O_APPEND]inlet*!fd=openfilefixed_pathflagsrightsinmatchrotationwith|Some(days_kept,cur_day)->Lwt.return(Rotating{rights;base_path;days_kept;current=ref{fd;day=cur_day};})|None->Lwt.return(Staticfd)))|`Syslog->let*facility=matchUri.get_query_paramuri"facility"with|None->returnSyslog.User|Somefacility->(matchSyslog.facility_of_string_optfacilitywith|None->fail_parsinguri"Invalid syslog facility."|Somef->returnf)inletpath=matchUri.pathuriwith""|"/"->None|path->Somepathin(* Syslog tag correspond to the event section, so it is added
afterwards *)let*!logger=Syslog.create?path~tag:"octez"facilityinreturn(Sysloglogger)|`Stdout->return(StaticLwt_unix.stdout)|`Stderr->return(StaticLwt_unix.stderr)inlett={output;filter;format;colors}inreturntletwrite_mutex=Lwt_mutex.create()letlist_rotation_filesbase_path=letopenLwt_syntaxinletdirname=Filename.dirnamebase_pathinletbase_filename=Filename.basenamebase_pathinletfile_stream=Lwt_unix.files_of_directorydirnameinletrecexploreacc=let*filename=Lwt_stream.getfile_streaminmatchfilenamewith|None->Lwt.returnacc|Somefilename->ifcheck_file_format_with_datebase_filenamefilenamethenexplore(filename::acc)elseexploreaccinexplore[]letremove_older_filesdirnamen_keptbase_path=letopenLwt_syntaxinlet*files=list_rotation_filesbase_pathinletsorted=List.sort(funxy->-comparexy)filesinList.iteri_s(funifile->ifi>=n_keptthenLwt_unix.unlink(Filename.concatdirnamefile)elseLwt.return_unit)sortedletoutput_one_with_rotation{rights;base_path;current;days_kept}nowto_write=letopenLwt_result_syntaxinlet*()=Lwt_mutex.with_lockwrite_mutex(fun()->let{day;fd}=!currentinlettoday=Ptime.to_datenowinletshould_rotate_output=day<>todayinlet*output=ifnotshould_rotate_outputthenreturnfdelselet*!()=Lwt_unix.closefdinletpath=filename_insert_before_ext~path:base_path(string_of_day_of_the_yeartoday)inlet*fd=protect(fun()->Lwt_result.ok@@Lwt_unix.(letflags=[O_WRONLY;O_CREAT;O_APPEND;O_CLOEXEC]inopenfilepathflagsrights))incurrent:={fd;day=today};returnfdinlet*!()=Lwt_utils_unix.write_stringoutputto_writeinlet*!()=ifshould_rotate_outputthenremove_older_files(Filename.dirnamebase_path)days_keptbase_pathelseLwt.return_unitinreturn_unit)inreturn_unitletoutput_onenowoutputsectionlevelto_write=protect@@fun()->matchoutputwith|Staticoutput->Lwt_result.ok@@Lwt_mutex.with_lockwrite_mutex(fun()->Lwt_utils_unix.write_stringoutputto_write)|Syslogsys_logger->Lwt_result.ok@@Lwt_mutex.with_lockwrite_mutex(fun()->Syslog.syslog~timestamp:(Ptime.to_float_snow)(overwrite_syslog_tagsys_loggersection)levelto_write)|Rotatingoutput->output_one_with_rotationoutputnowto_writeletshould_handle(typea)?(section=Internal_event.Section.empty){filter;_}m=letmoduleM=(valm:Internal_event.EVENT_DEFINITIONwithtypet=a)inmatchfilterwith|`Level_at_leastlevel_at_least->Internal_event.Level.compareM.levellevel_at_least>=0|`Per_section_prefixkvl->(matchList.find(fun(prefix,_)->Internal_event.Section.is_prefix~prefixsection)kvlwith|None->(* default *)Internal_event.Level.compareM.levelInternal_event.Level.default>=0|Some(_,None)->(* exclude list *)false|Some(_,Somelvl)->Internal_event.Level.compareM.levellvl>=0)letlevel_color=function|Internal_event.Warning->SomeColor.FG.yellow|Error|Fatal->SomeColor.FG.red|Info|Notice|Debug->Noneletoutput_color_compatibleout=letopenLwt_syntaxinmatchoutwith|Staticfd->let*is_a_tty=Lwt_unix.isattyfdinreturn(is_a_tty&&Sys.getenv_opt"TERM"<>Some"dumb")|Syslog_|Rotating_->return_falselethandle(typea){output;format;colors;_}m?(section=Internal_event.Section.empty)(event:a)=letopenLwt_result_syntaxinletmoduleM=(valm:Internal_event.EVENT_DEFINITIONwithtypet=a)inletnow=Ptime_clock.now()inletwrapped_event=wrapnowsectioneventinlet*!to_write=ifis_syslogoutputthenLwt.return@@make_for_syslogM.ppwrapped_eventelseletjson()=Data_encoding.Json.construct(wrapped_encodingM.encoding)wrapped_eventinmatchformatwith|`Pp_RFC5424->Lwt.return@@make_with_pp_rfc5424M.ppwrapped_eventM.name|`Pp_short->let*!color=ifcolorsthenlet*!color_compatible=output_color_compatibleoutputinifcolor_compatiblethenLwt.return(Enabled(level_colorM.level))elseLwt.returnDisabledelseLwt.returnDisabledinLwt.return@@make_with_pp_short~colorM.ppwrapped_event|`One_per_line->Lwt.return@@Ezjsonm.value_to_string~minify:true(json())^"\n"|`Netstring->letstr=Ezjsonm.value_to_string~minify:true(json())inLwt.return@@Printf.sprintf"%d:%s,"(String.lengthstr)strinlet*!r=output_onenowoutputsectionM.levelto_writeinmatchrwith|Error[Exn(Unix.Unix_error(Unix.EBADF,_,_))]->(* The file descriptor was closed before the event arrived,
ignore it. *)return_unit|Error_aserr->Lwt.returnerr|Ok()->return_unitletclose{output;_}=letopenLwt_result_syntaxinmatchK.kindwith|`Path|`Syslog->(matchoutputwith|Syslogsys_logger->protect(fun()->Lwt_result.ok@@Syslog.closesys_logger)|Rotatingoutput->Lwt_utils_unix.safe_close!(output.current).fd|Staticoutput->Lwt_utils_unix.safe_closeoutput)|`Stdout|`Stderr->return_unitendmoduleSink_implementation_path=Make_sink(structletkind=`Pathend)moduleSink_implementation_stdout=Make_sink(structletkind=`Stdoutend)moduleSink_implementation_stderr=Make_sink(structletkind=`Stderrend)moduleSink_implementation_syslog=Make_sink(structletkind=`Syslogend)let()=Internal_event.All_sinks.register(moduleSink_implementation_path)let()=Internal_event.All_sinks.register(moduleSink_implementation_stdout)let()=Internal_event.All_sinks.register(moduleSink_implementation_stderr)let()=Internal_event.All_sinks.register(moduleSink_implementation_syslog)