123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595(******************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2018 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_monadmoduleMicro_seconds:sig(** Module with time-stamps with “at least micro-seconds” precision. *)typet=privatefloatvalnow:unit->tvalof_float:float->tvalencoding:tData_encoding.tvaldate_string:t->string*stringend=struct(* Time.t is in seconds, we want more precision. *)typet=floatletnow()=Unix.gettimeofday()letof_floatf=fletencoding=letopenData_encodinginconv(* Cf. https://github.com/OCamlPro/ocplib-json-typed/issues/25 *)(funf->f*.1_000_000.|>Int64.of_float)(funi64->Int64.to_floati64/.1_000_000.)int64letdate_stringtime_value=letopenUnixinletopenPrintfinlettm=gmtimetime_valuein(sprintf"%04d%02d%02d"(1900+tm.tm_year)(tm.tm_mon+1)tm.tm_mday,sprintf"%02d%02d%02d-%06d"tm.tm_hourtm.tm_mintm.tm_sec((time_value-.floortime_value)*.1_000_000.|>int_of_float))endmoduleEvent_filter=structtypet=|True|False|Oroftlist|Andoftlist|Nameofstring|Name_matchesofRe.re|Level_inofInternal_event.levellist|Section_inofInternal_event.Section.tlistletrecrun~section~level~namefilter=letcontinue=run~section~level~nameinmatchfilterwith|True->true|False->false|Orl->List.existscontinuel|Andl->List.for_allcontinuel|Names->String.equalsname|Name_matchesre->Re.execprename|Level_inl->List.mem~equal:Internal_event.Level.equallevell|Section_inl->List.mem~equal:Internal_event.Section.equalsectionlletrecppfmtfilter=letopenFormatinmatchfilterwith|True->pp_print_stringfmt"true"|False->pp_print_stringfmt"false"|Orl->fprintffmt"(or@ @[<2>%a@]"(pp_print_list~pp_sep:(funfmt()->fprintffmt"@ ")pp)l|Andl->fprintffmt"(and@ @[<2>%a@]"(pp_print_list~pp_sep:(funfmt()->fprintffmt"@ ")pp)l|Names->fprintffmt"(name-is@ %S)"s|Name_matchesre->fprintffmt"(name-matches@ %a)"Re.pp_rere|Level_inl->fprintffmt"(level-in@ [%s])"(String.concat","(List.mapInternal_event.Level.to_stringl))|Section_inl->fprintffmt"(section-in@ [%a])"(pp_print_list~pp_sep:(funfmt()->fprintffmt",@ ")(funfmts->fprintffmt"(Some %a)"Internal_event.Section.pps))l[@@warning"-32"](* -> The "unused value" warning. *)lett=Trueletf=False[@@warning"-32"](* -> The "unused value" warning. *)letanyl=Orlletalll=Andl[@@warning"-32"](* -> The "unused value" warning. *)letname_iss=Namesletname_matchess=Name_matchessletname_matches_posixs=name_matches(Re.Posix.compile_pats)letlevel_inl=Level_inlletsection_inl=Section_inlletlevels_in_order=Internal_event.[Debug;Info;Notice;Warning;Error;Fatal]letlevel_at_leastlvl=List.fold_left(funaccl->matchaccwith|[]->ifl=lvlthen[l]else[]|_::_asacc->l::acc)[]levels_in_order|>function|[]->raise(Failure"level_at_least not found")|_::_aslevels->level_inlevelsendtypet={path:string;event_filter:Event_filter.t}type'eventwrapped={time_stamp:Micro_seconds.t;section:Internal_event.Section.t;event:'event;}letwraptime_stampsectionevent={time_stamp;section;event}letwrapped_encodingevent_encoding=letopenData_encodinginletv0=conv(fun{time_stamp;section;event}->(time_stamp,section,event))(fun(time_stamp,section,event)->{time_stamp;section;event})(obj3(req"time_stamp"Micro_seconds.encoding)(req"section"Internal_event.Section.encoding)(req"event"event_encoding))inWith_version.(encoding~name:"file-event-sink-item"(first_versionv0))moduleSection_dir=structletof_section(section:Internal_event.Section.t)=Format.asprintf"%a"Internal_event.Section.ppsectionletsection_name=function|"no-section"->OkNone|other->(matchTzString.remove_prefix~prefix:"section-"otherwith|None->Error"wrong-dir-name"|Somes->Ok(Somes))endmoduleSink_implementation:Internal_event.SINKwithtypet=t=structtypenonrect=tleturi_scheme="unix-files"letconfigureuri=letevent_filter=letname_res=Uri.get_query_param'uri"name-matches"|>Option.value~default:[]inletnames=Uri.get_query_param'uri"name"|>Option.value~default:[]inletlevel_o=letopenOption_syntaxinlet*lal=Uri.get_query_paramuri"level-at-least"inlet*lal=Internal_event.Level.of_stringlalinreturn(Event_filter.level_at_leastlal)inletlevels=Option.to_listlevel_oinletsections=letsomes=Uri.get_query_param'uri"section"|>Option.fold~none:[]~some:(List.map(funs->Internal_event.Section.make_sanitized(String.split_on_char'.'s)))inletnone=matchUri.get_query_paramuri"no-section"with|Some"true"->[Internal_event.Section.empty]|_->[]inmatchsomes@nonewith|[]->[]|more->[Event_filter.section_inmore]inEvent_filter.(matchlevels@sections@List.mapname_matches_posixname_res@List.mapname_isnameswith|[]->t|more->anymore)inlett={path=Uri.pathuri;event_filter}inLwt.return_oktletoutput_json~ppfile_pathevent_json=letopenLwt_syntaxinLwt.catch(fun()->let*()=Lwt_utils_unix.create_dir~perm:0o700(Filename.dirnamefile_path)inlet*ru=Lwt_utils_unix.Json.write_filefile_pathevent_jsoninmatchruwith|Ok()->return_ok_unit|Errorel->failwith"ERROR while Handling %a,@ cannot write JSON to %s:@ %a\n%!"pp()file_pathError_monad.pp_print_traceel)(function|e->failwith"ERROR while Handling %a: %s\n%!"pp()(Printexc.to_stringe))letshould_handle(typea)?(section=Internal_event.Section.empty){event_filter;_}m=letmoduleM=(valm:Internal_event.EVENT_DEFINITIONwithtypet=a)inEvent_filter.run~section~level:M.level~name:M.nameevent_filterlethandle(typea){path;_}m?(section=Internal_event.Section.empty)(event:a)=letopenLwt_result_syntaxinletmoduleM=(valm:Internal_event.EVENT_DEFINITIONwithtypet=a)inletnow=Micro_seconds.now()inletdate,time=Micro_seconds.date_stringnowinletevent_json=Data_encoding.Json.construct(wrapped_encodingM.encoding)(wrapnowsectionevent)inlettag=lethash=Marshal.to_stringevent_json[]|>Digest.string|>Digest.to_hexinString.subhash08inletsection_dir=Section_dir.of_sectionsectioninletdir_path=List.fold_leftFilename.concatpath[section_dir;M.name;date;time]inletfile_path=Filename.concatdir_path(Printf.sprintf"%s_%s_%s.json"datetimetag)inlet*()=output_jsonfile_pathevent_json~pp:(funfmt()->M.pp~block:true~all_fields:truefmtevent)inreturn_unitletclose_=Lwt_result_syntax.return_unitendlet()=Internal_event.All_sinks.register(moduleSink_implementation)openSink_implementationmoduleQuery=structletwith_file_kinddirp=letopenLwt_result_syntaxinlet*kind=protect(fun()->let*{Lwt_unix.st_kind;_}=Lwt_result.ok@@Lwt_unix.stat(Filename.concatdirp)inreturnst_kind)inmatchkindwith|Unix.S_DIR->return(`Directoryp)|Unix.S_REG->return(`Regular_filep)|(Unix.S_CHR|Unix.S_BLK|Unix.S_LNK|Unix.S_FIFO|Unix.S_SOCK)ask->return(`Special(k,p))letfold_directorypath~init~f=letopenLwt_result_syntaxinlet*dirhandle=protect(fun()->Lwt_result.ok@@Lwt_unix.opendirpath)inletreciterprev=let*opt=protect(fun()->Lwt.catch(fun()->let*d=Lwt_result.ok@@Lwt_unix.readdirdirhandleinlet*wk=with_file_kindpathdinreturn_somewk)(function|End_of_file->let*()=Lwt_result.ok@@Lwt_unix.closedirdirhandleinreturn_none|(e:exn)->failwith"ERROR while folding %s: %s"path(Printexc.to_stringe)))inlet*p=previnmatchoptwithSomemore->iter(fpmore)|None->previniterinitopenTzFilename.InfixmoduleTime_constraint=structtypeop=[`Lt|`Le|`Ge|`Gt]typet=[`Dateofop*float|`Timeofop*float|`Andoft*t|`Oroft*t|`All]letreccheck_logiccheck_terminal(t:t)string=letcontinue=check_logiccheck_terminalinmatchtwith|`All->true|`And(a,b)->continueastring&&continuebstring|`Or(a,b)->continueastring||continuebstring|(`Date_|`Time_)asterm->check_terminaltermletop_with_string=function|`Lt->funab->String.compareab>0|`Gt->funab->String.compareab<0|`Le->funab->String.compareab>=0|`Ge->funab->String.compareab<=0letcheck_date(t:t)date_string=check_logic(function|`Date(op,f)->lets=Micro_seconds.(date_string(of_floatf)|>fst)inop_with_stringopsdate_string|`Time_->true)tdate_stringletcheck_time(t:t)string=check_logic(function|`Time(op,f)->lets=Micro_seconds.(date_string(of_floatf)|>snd)inop_with_stringopsstring|`Date_->true)tMicro_seconds.date_stringendmoduleReport=structtypeitem=[`Errorof[`Parsing_eventof[`Encodingofstring*exn|`Jsonofstring*errorlist]|`Cannot_recognize_sectionofstring]|`Warningof[`Expecting_regular_file_atofstring|`Expecting_directory_atofstring|`Unknown_event_name_atofstring*string]]letppfmt(x:item)=letopenFormatinleterrorfmt=function|`Parsing_evente->(matchewith|`Encoding(path,exn)->fprintffmt"@[Parse error:@ wrong encoding for %S: %s@]"path(Printexc.to_stringexn)|`Json(path,el)->fprintffmt"@[Parse error:@ wrong JSON for %S: %a@]"pathpp_print_traceel)|`Cannot_recognize_sectionsec->fprintffmt"@[Directory error:@ cannot recognize section directory@ %S@]"secinletwarningfmt=function|`Expecting_regular_file_atpath->fprintffmt"%S@ is not a regular file"path|`Expecting_directory_atpath->fprintffmt"%S@ is not a directory"path|`Unknown_event_name_at(name,path)->fprintffmt"Unknown event name@ %S@ at@ %S"namepathinmatchxwith|`Errore->fprintffmt"@[Error:@ %a@]"errore|`Warninge->fprintffmt"@[Warning:@ %a@]"warningeletmake_returnm((prev:itemlist),value)warning=Lwt.return_ok(mwarning::prev,value)letreturn_with_warningve=make_return(fune->`Warninge)veletreturn_with_errorve=make_return(fune->`Errore)veendopenReportletfold_event_kind_directory~time_querypath~init~f=letopenLwt_result_syntaxinfold_directorypath~init:(returninit)~f:(funprevious->function|`Directory"."|`Directory".."->returnprevious|`DirectorydatewhenTime_constraint.check_datetime_querydate->fold_directory(path//date)~init:(returnprevious)~f:(funprevious->function|`Directory"."|`Directory".."->returnprevious|`DirectorytimewhenTime_constraint.check_timetime_querytime->fold_directory(path//date//time)~init:(returnprevious)~f:(funprevious->function|`Directory"."|`Directory".."->returnprevious|`Regular_filefile->fprevious(path//date//time//file)|`Directoryp|`Special(_,p)->return_with_warningprevious(`Expecting_regular_file_at(path//date//time//p)))|`Directory_(* filtered out *)->returnprevious|`Regular_filep|`Special(_,p)->return_with_warningprevious(`Expecting_directory_at(path//date//p)))|`Directory_(* filtered out *)->returnprevious|`Regular_filep|`Special(_,p)->return_with_warningprevious(`Expecting_directory_at(path//p)))lethandle_event_kind_directory(typea)~time_query~section_path~init~fev=letmoduleEvent=(valev:Internal_event.EVENT_DEFINITIONwithtypet=a)inlethandle_event_filepreviouspath=letopenLwt_result_syntaxinlet*!r=Lwt_utils_unix.Json.read_filepathinmatchrwith|Okjson->(trylet{time_stamp;event;_}=Data_encoding.Json.destruct(wrapped_encodingEvent.encoding)jsoninlet*user_return=f(sndprevious)~time_stamp:(time_stamp:>float)(Internal_event.Generic.Event(Event.name,ev,event))inreturn(fstprevious,user_return)withe->return_with_errorprevious(`Parsing_event(`Encoding(path,e))))|Errorel->return_with_errorprevious(`Parsing_event(`Json(path,el)))infold_event_kind_directory~time_query(section_path//Event.name)~init~f:(funprevfile->handle_event_fileprevfile)letfold?on_unknown?only_sections?only_names?(time_query=`All)uri~init~f=letopenLwt_result_syntaxinletname_matches=matchonly_nameswith|None->fun_->true|Somel->funname->List.mem~equal:String.equalnamelinletsection_matches=matchonly_sectionswith|None->fun_->true|Somel->funname->List.mem~equal:(Option.equalString.equal)namelinlet*{path=sink_path;_}=configureuriinfold_directorysink_path~init:(return([],init))~f:(funprevious->function|`Directory("."|"..")->returnprevious|`Directorydir->(matchSection_dir.section_namedirwith|Oksecwhensection_matchessec->fold_directory(sink_path//dir)~init:(return([],init))~f:(funprevious->function|`Directory("."|"..")->returnprevious|`Directoryevent_namewhenname_matchesevent_name->(letopenInternal_eventinmatchAll_definitions.find((=)event_name)with|Some(Generic.Definition(_,_,ev))->handle_event_kind_directory~time_queryev~section_path:(sink_path//dir)~init:previous~f|None->(matchon_unknownwith|None->return_with_warningprevious(`Unknown_event_name_at(event_name,sink_path//dir))|Somef->fold_event_kind_directory~time_query(sink_path//dir//event_name)~init:previous~f:(funprevfile->let*()=ffileinreturnprev)))|`Directory_(* filtered out *)->returnprevious|`Regular_filep|`Special(_,p)->return_with_warningprevious(`Expecting_directory_at(sink_path//p)))|Ok_(* section does not match *)->returnprevious|Error_->return_with_errorprevious(`Cannot_recognize_sectiondir))|`Regular_filep|`Special(_,p)->return_with_warningprevious(`Expecting_directory_at(sink_path//p)))end