12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853(* this is used by some stable types below, so it needs to be at the top of the file *)moduletypeRotation_id_intf=sigtypetvalcreate:?time_source:Async_kernel.Synchronous_time_source.t->Core.Time.Zone.t->t(* For any rotation scheme that renames logs on rotation, this defines how to do
the renaming. *)valrotate_one:t->tvalto_string_opt:t->stringoptionvalof_string_opt:stringoption->toptionvalcmp_newest_first:t->t->intendmoduleStable=structopen!Core.Core_stablemoduleTime=Time_unix.StablemoduleLevel=structmoduleV1=structtypet=[`Debug|`Info|`Error][@@derivingbin_io,sexp]let%expect_test"bin_digest Level.V1"=print_endline[%bin_digest:t];[%expect{| 62fa833cdabec8a41d614848cd11f858 |}];;endendmoduleOutput=structmoduleFormat=structmoduleV1=structtypemachine_readable=[`Sexp|`Sexp_hum|`Bin_prot][@@derivingsexp]typet=[machine_readable|`Text][@@derivingsexp]endendendmoduleSexp_or_string=structmoduleV1=structtypet=[`SexpofSexp.V1.t|`Stringofstring][@@derivingbin_io,sexp]let%expect_test"bin_digest Sexp_or_string.V1"=print_endline[%bin_digest:t];[%expect{| 7604679c48980b04476c108e66cf67c8 |}];;letto_string=function|`Sexpsexp->Core.Sexp.to_stringsexp|`Stringstr->str;;endendmoduleRotation=structmoduleV3=structtypenaming_scheme=[`Numbered|`Timestamped|`Dated|`User_definedof(moduleRotation_id_intf)]typet={messages:intoption;size:Byte_units.V1.toption;time:Time.Ofday.V1.toption;keep:[`All|`Newer_thanofTime.Span.V3.t|`At_leastofint];naming_scheme:naming_scheme;zone:Time.Zone.V1.t}[@@derivingfields]letsexp_of_tt=letax=Core.Sexp.Atomxandlx=Core.Sexp.Listxinletoxnamesexp_of=Core.Option.mapx~f:(funx->l[aname;sexp_ofx])inletmessages=ot.messages"messages"Int.V1.sexp_of_tinletsize=ot.size"size"Byte_units.V1.sexp_of_tinlettime=ot.time"time"Time.Ofday.V1.sexp_of_tinletkeep=l[a"keep";(matcht.keepwith|`All->a"All"|`Newer_thanspan->l[a"Newer_than";Time.Span.V3.sexp_of_tspan]|`At_leastn->l[a"At_least";Int.V1.sexp_of_tn])]inletnaming_scheme=l[a"naming_scheme";(matcht.naming_schemewith|`Numbered->a"Numbered"|`Timestamped->a"Timestamped"|`Dated->a"Dated"|`User_defined_->a"User_defined")]inletzone=l[a"zone";Time.Zone.V1.sexp_of_tt.zone]inletall=Core.List.filter_opt[messages;size;time;Somekeep;Somenaming_scheme;Somezone]inlall;;endend(* Log messages are stored, starting with V2, as an explicit version followed by the
message itself. This makes it easier to move the message format forward while
still allowing older logs to be read by the new code.
If you make a new version you must add a version to the Version module below and
should follow the Make_versioned_serializable pattern.
*)moduleMessage=structmoduleVersion=structtypet=V2[@@derivingbin_io,sexp,compare]let%expect_test"bin_digest Message.Version.V2"=print_endline[%bin_digest:t];[%expect{| 6ae8dff060dc8c96585060b4f76d2974 |}];;let(<>)t1t2=comparet1t2<>0letto_stringt=Core.Sexp.to_string(sexp_of_tt)endmoduletypeVersioned_serializable=sigtypet[@@derivingbin_io,sexp]valversion:Version.tendmoduleStable_message_common=structtype'at={time:Time.V1.t;level:Level.V1.toption;message:'a;tags:(string*string)list}[@@derivingbin_io,sexp]endmoduleMake_versioned_serializable(T:Versioned_serializable):sigtypet[@@derivingbin_io,sexp]endwithtypet=T.t=structtypet=T.ttypeversioned_serializable=Version.t*T.t[@@derivingbin_io,sexp]lett_of_versioned_serializable(version,t)=ifVersion.(<>)versionT.versionthenCore.failwithf!"version mismatch %{Version} <> to expected version %{Version}"versionT.version()elset;;letsexp_of_tt=sexp_of_versioned_serializable(T.version,t)lett_of_sexpsexp=letversioned_t=versioned_serializable_of_sexpsexpint_of_versioned_serializableversioned_t;;includeBinable.Of_binable.V1[@alert"-legacy"](structtypet=versioned_serializable[@@derivingbin_io]end)(structtypet=T.tletto_binablet=T.version,tletof_binableversioned_t=t_of_versioned_serializableversioned_tend)endmoduleV2=Make_versioned_serializable(structtypet=Sexp_or_string.V1.tStable_message_common.t[@@derivingbin_io,sexp]let%expect_test"bin_digest Message.V2"=print_endline[%bin_digest:t];[%expect{| 1dd2225c5392b6ac36b718ee2b1a08db |}];;letversion=Version.V2end)(* this is the serialization scheme in 111.18 and before *)moduleV0=structtypev0_t=stringStable_message_common.t[@@derivingbin_io,sexp]let%expect_test"bin_digest Message.V1.v0_t"=print_endline[%bin_digest:v0_t];[%expect{| d790de8237524f270360ccf1e56f7030 |}];;letv0_to_v2(v0_t:v0_t):V2.t={time=v0_t.time;level=v0_t.level;message=`Stringv0_t.message;tags=v0_t.tags};;letv2_to_v0(v2_t:V2.t):v0_t={time=v2_t.time;level=v2_t.level;message=Sexp_or_string.V1.to_stringv2_t.message;tags=v2_t.tags};;includeBinable.Of_binable.V1[@alert"-legacy"](structtypet=v0_t[@@derivingbin_io]end)(structletto_binable=v2_to_v0letof_binable=v0_to_v2typet=Sexp_or_string.V1.tStable_message_common.tend)letsexp_of_tt=sexp_of_v0_t(v2_to_v0t)lett_of_sexpsexp=v0_to_v2(v0_t_of_sexpsexp)typet=V2.tendendendopenCoreopenImportmoduleScheduler=Raw_schedulermoduleSys=Async_sysmoduleUnix=Unix_syscallsmoduleLevel=structmoduleT=structtypet=[`Debug|`Info|`Error][@@derivingbin_io,compare,enumerate,sexp]letto_string=function|`Debug->"Debug"|`Info->"Info"|`Error->"Error";;letof_string=function|"Debug"->`Debug|"Info"->`Info|"Error"->`Error|s->failwithf"not a valid level %s"s();;endincludeTletarg=Command.Arg_type.enumerated~list_values_in_help:true~case_sensitive:false(moduleT:Command.Enumerable_stringablewithtypet=t);;(* Ordering of log levels in terms of verbosity. *)letas_or_more_verbose_than~log_level~msg_level=matchlog_level,msg_levelwith|`Error,Some`Error->true|`Error,(None|Some(`Debug|`Info))->false|`Info,(None|Some(`Info|`Error))->true|`Info,Some`Debug->false|`Debug,_->true;;moduleStable=Stable.LevelendmoduleRotation=struct(* description of boundaries for file rotation. If all fields are None the file will
never be rotated. Any field set to Some _ will cause rotation to happen when that
boundary is crossed. Multiple boundaries may be set. Log rotation always causes
incrementing rotation conditions (e.g. size) to reset, though this is the
responsibililty of the caller to should_rotate.
*)moduletypeId_intf=Rotation_id_intfincludeStable.Rotation.V3letcreate?messages?size?time?zone~keep~naming_scheme()={messages;size;time;zone=Option.valuezone~default:(forceTime.Zone.local);keep;naming_scheme};;letfirst_occurrence_aftertime~ofday~zone=letfirst_at_or_aftertime=Time.occurrence`First_after_or_attime~ofday~zoneinletcandidate=first_at_or_aftertimein(* we take care not to return the same time we were given *)ifTime.equaltimecandidatethenfirst_at_or_after(Time.addtimeTime.Span.robust_comparison_tolerance)elsecandidate;;letshould_rotatet~last_messages~last_size~last_time~current_time=Fields.fold~init:false~messages:(funaccfield->matchField.getfieldtwith|None->acc|Somerotate_messages->acc||rotate_messages<=last_messages)~size:(funaccfield->matchField.getfieldtwith|None->acc|Somerotate_size->acc||Byte_units.(<=)rotate_sizelast_size)~time:(funaccfield->matchField.getfieldtwith|None->acc|Somerotation_ofday->letrotation_time=first_occurrence_afterlast_time~ofday:rotation_ofday~zone:t.zoneinacc||Time.(>=)current_timerotation_time)~zone:(funacc_->acc)~keep:(funacc_->acc)~naming_scheme:(funacc_->acc);;letdefault?(zone=forceTime.Zone.local)()={messages=None;size=None;time=SomeTime.Ofday.start_of_day;keep=`All;naming_scheme=`Dated;zone};;endmoduleSexp_or_string=structmoduleStable=Stable.Sexp_or_stringincludeStable.V1endletnow~time_source=matchtime_sourcewith|Sometime_source->Synchronous_time_source.nowtime_source|>Time_ns.to_time_float_round_nearest|None->Time.now();;moduleMessage:sigtypet[@@derivingbin_io,sexp]includeEqual.Swithtypet:=tvalcreate:?level:Level.t->?time:Time.t->?time_source:Synchronous_time_source.t->?tags:(string*string)list->Sexp_or_string.t->tvaltime:t->Time.tvallevel:t->Level.toptionvalset_level:t->Level.toption->tvalmessage:t->stringvalraw_message:t->[`Stringofstring|`SexpofSexp.t]valtags:t->(string*string)listvaladd_tags:t->(string*string)list->tvalto_write_only_text:?zone:Time.Zone.t->t->stringvalwrite_write_only_text:t->Writer.t->unitvalwrite_sexp:t->hum:bool->Writer.t->unitvalwrite_bin_prot:t->Writer.t->unitmoduleStable:sigmoduleVersion:sigtypet[@@derivingof_sexp]endmoduleV0:sigtypenonrect=t[@@derivingbin_io,sexp]endmoduleV2:sigtypenonrect=t[@@derivingbin_io,sexp]endendend=structmoduleStable=Stable.MessageopenStable.Stable_message_commonincludeStable.V2letequal(t1:t)(t2:t)=letcompare_tags=Tuple.T2.compare~cmp1:String.compare~cmp2:String.compareinTime.(=.)t1.timet2.time&&[%compare.equal:Level.toption]t1.levelt2.level&&Poly.equalt1.messaget2.message(* The same key can appear more than once in tags, and order shouldn't matter
when comparing *)&&List.compare[%compare:String.t*String.t](List.sort~compare:compare_tagst1.tags)(List.sort~compare:compare_tagst2.tags)=0;;(* this allows for automagical reading of any versioned sexp, so long as we can always
lift to a Message.t *)lett_of_sexp(sexp:Sexp.t)=matchsexpwith|List(List(Atom"time"::_)::_)->Stable.V0.t_of_sexpsexp|List[(Atom_asversion);_]->(matchStable.Version.t_of_sexpversionwith|V2->Stable.V2.t_of_sexpsexp)|_->failwithf!"Log.Message.t_of_sexp: malformed sexp: %{Sexp}"sexp();;letcreate_raw?level~time?(tags=[])message={time;level;message;tags}letcreate?level?time?time_source?tagsmessage=lettime=matchtimewith|Sometime->time|None->now~time_sourceincreate_raw?level~time?tagsmessage;;lettimet=t.timeletlevelt=t.levelletset_leveltlevel={twithlevel}letraw_messaget=t.messageletmessaget=Sexp_or_string.to_string(raw_messaget)lettagst=t.tagsletadd_tagsttags={twithtags=List.rev_appendtagst.tags}letto_write_only_text?(zone=forceTime.Zone.local)t=letprefix=matcht.levelwith|None->""|Somel->Level.to_stringl^" "inletformatted_tags=matcht.tagswith|[]->[]|_::_->" --"::List.concat_mapt.tags~f:(fun(t,v)->[" [";t;": ";v;"]"])inString.concat~sep:""(Time.to_string_abs~zonet.time::" "::prefix::messaget::formatted_tags);;letwrite_write_only_texttwr=Writer.writewr(to_write_only_textt);Writer.newlinewr;;letwrite_sexpt~humwr=Writer.write_sexp~humwr(sexp_of_tt);Writer.newlinewr;;letwrite_bin_prottwr=Writer.write_bin_protwrbin_writer_ttendmoduleOutput:sig(* The output module exposes a variant that describes the output type and sub-modules
that each expose a write function (or create that returns a write function) that is
of type: Level.t -> string -> unit Deferred.t. It is the responsibility of the write
function to contain all state, and to clean up after itself.
*)moduleFormat:sigtypemachine_readable=[`Sexp|`Sexp_hum|`Bin_prot][@@derivingsexp]typet=[machine_readable|`Text][@@derivingsexp]moduleStable:sigmoduleV1:sigtypenonrect=t[@@derivingsexp]endendendtypet[@@derivingsexp_of]valcreate:?rotate:(unit->unitDeferred.t)->?close:(unit->unitDeferred.t)->flush:(unit->unitDeferred.t)->(Message.tQueue.t->unitDeferred.t)->tvalwrite:t->Message.tQueue.t->unitDeferred.tvalrotate:t->unitDeferred.tvalflush:t->unitDeferred.tvalstdout:?format:Format.t->unit->tvalstderr:?format:Format.t->unit->tvalwriter:Format.t->Writer.t->tvalfile:?perm:Unix.file_perm->Format.t->filename:string->tvalrotating_file:?perm:Unix.file_perm->?time_source:Synchronous_time_source.t->?log_on_rotation:(unit->Message.tlist)->Format.t->basename:string->Rotation.t->tvalrotating_file_with_tail:?perm:Unix.file_perm->?time_source:Synchronous_time_source.t->?log_on_rotation:(unit->Message.tlist)->Format.t->basename:string->Rotation.t->t*stringTail.tvalfilter_to_level:t->level:Level.t->tvalcombine:tlist->tmoduleFor_testing:sigvalcreate:map_output:(string->string)->tendend=structmoduleFormat=structtypemachine_readable=[`Sexp|`Sexp_hum|`Bin_prot][@@derivingsexp]typet=[machine_readable|`Text][@@derivingsexp]moduleStable=Stable.Output.FormatendmoduleDefinitely_a_heap_block:sigtypetvalthe_one_and_only:tend=structtypet=stringletthe_one_and_only=String.make1' 'endtypet={write:Message.tQueue.t->unitDeferred.t;rotate:unit->unitDeferred.t;close:unit->unitDeferred.t;flush:unit->unitDeferred.t;(* experimentation shows that this record, without this field, can sometimes raise
when passed to Heap_block.create_exn, which we need to do to add a finalizer.
This seems to occur when the functions are top-level and/or constant. More
investigation is probably worthwhile. *)heap_block:Definitely_a_heap_block.t}letcreate?(rotate=fun()->return())?(close=fun()->return())~flushwrite=lett={write;rotate;close;flush;heap_block=Definitely_a_heap_block.the_one_and_only}inGc.add_finalizer(Heap_block.create_exnt)(funt->lett=Heap_block.valuetindon't_wait_for(let%bind()=t.flush()int.close()));t;;letwritetmsgs=t.writemsgsletrotatet=t.rotate()letflusht=t.flush()letsexp_of_t_=Sexp.Atom"<opaque>"letcombinets=(* There is a crazy test that verifies that we combine things correctly when the same
rotate output is included 5 times in Log.create, so we must make this Sequential to
enforce the rotate invariants and behavior. *)letiter_combine_exns=(* No need for the Monitor overhead in the case of a single t *)matchtswith|[]->fun(_:t->unitDeferred.t)->Deferred.unit|[single_t]->funf->fsingle_t|ts->funf->Deferred.List.map~how:`Sequentialts~f:(funt->Monitor.try_with_or_error~rest:`Log(fun()->ft))>>|Or_error.combine_errors_unit>>|Or_error.ok_exninletwritemsg=iter_combine_exns(funt->t.writemsg)inletrotate()=iter_combine_exns(funt->t.rotate())inletclose()=iter_combine_exns(funt->t.close())inletflush()=iter_combine_exns(funt->t.flush())in{write;rotate;close;flush;heap_block=Definitely_a_heap_block.the_one_and_only};;letfilter_to_levelt~level=letwritemessages=letfiltered_messages=Queue.filtermessages~f:(funmessage->Level.as_or_more_verbose_than~log_level:level~msg_level:(Message.levelmessage))int.writefiltered_messagesincreate~rotate:t.rotate~close:t.close~flush:t.flushwrite;;letbasic_writeformatwmsg=matchformatwith|`Sexp->Message.write_sexpmsg~hum:falsew|`Sexp_hum->Message.write_sexpmsg~hum:truew|`Bin_prot->Message.write_bin_protmsgw|`Text->Message.write_write_only_textmsgw;;letopen_file?permfilename=(* guard the open_file with a unit deferred to prevent any work from happening
before async spins up. Without this no real work will be done, but async will be
initialized, which will raise if we later call Scheduler.go_main. *)return()>>=fun()->Writer.open_file~append:truefilename?perm;;letopen_writer~filename~perm=(* the lazy pushes evaluation to the first place we use it, which keeps writer
creation errors within the error handlers for the log. *)lazy(open_filefilename?perm>>|funw->(* if we are writing to a slow device, or a temporarily disconnected
device it's better to push back on memory in the hopes that the
disconnection will resolve than to blow up after a timeout. If
we had a better logging error reporting mechanism we could
potentially deal with it that way, but we currently don't. *)Writer.set_buffer_age_limitw`Unlimited;w);;letwrite_immediatelywformatmsgs=Queue.itermsgs~f:(funmsg->basic_writeformatwmsg);Writer.bytes_receivedw;;letwrite'wformatmsgs=let%mapw=winwrite_immediatelywformatmsgs;;moduleFile:sigvalcreate:?perm:Unix.file_perm->Format.t->filename:string->tend=structletcreate?permformat~filename=letw=open_writer~filename~permincreate~close:(fun()->ifLazy.is_valwthenforcew>>=Writer.closeelsereturn())~flush:(fun()->ifLazy.is_valwthenforcew>>=Writer.flushedelsereturn())(funmsgs->let%map(_:Int63.t)=write'(forcew)formatmsgsin());;endmoduleLog_writer:sigvalcreate:Format.t->Writer.t->tend=struct(* The writer output type takes no responsibility over the Writer.t it is given. In
particular it makes no attempt to ever close it. *)letcreateformatw=create~flush:(fun()->Writer.flushedw)(funmsgs->Queue.itermsgs~f:(funmsg->basic_writeformatwmsg);return());;endmoduleRotating_file:sigvalcreate:?perm:Unix.file_perm->?time_source:Synchronous_time_source.t->Format.t->basename:string->Rotation.t->log_on_rotation:(unit->Message.tlist)option->t*stringTail.tend=structmoduleMake(Id:Rotation.Id_intf)=structletmake_filename~dirname~basenameid=matchId.to_string_optidwith|None->dirname^/sprintf"%s.log"basename|Somes->dirname^/sprintf"%s.%s.log"basenames;;letparse_filename_id~basenamefilename=ifString.equal(Filename.basenamefilename)(basename^".log")thenId.of_string_optNoneelseletopenOption.Monad_infixinString.chop_prefix(Filename.basenamefilename)~prefix:(basename^".")>>=funid_dot_log->String.chop_suffixid_dot_log~suffix:".log">>=funid->Id.of_string_opt(Someid);;letcurrent_log_files~dirname~basename=Sys.readdirdirname>>|funfiles->List.filter_map(Array.to_listfiles)~f:(funfilename->letfilename=dirname^/filenameinOption.(parse_filename_id~basenamefilename>>|funid->id,filename));;(* errors from this function should be ignored. If this function fails to run, the
disk may fill up with old logs, but external monitoring should catch that, and
the core function of the Log module will be unaffected. *)letmaybe_delete_old_logs~dirname~basenamekeep=(matchkeepwith|`All->return[]|`Newer_thanspan->current_log_files~dirname~basename>>=funfiles->(* This will be compared to the mtime of the file, so we should always use
Time.now (wall-clock time) instead a different time source. *)letnow=Time.now()inletcutoff=Time.subnowspaninDeferred.List.filterfiles~f:(fun(_,filename)->Deferred.Or_error.try_with~run:`Schedule~rest:`Log(fun()->Unix.statfilename)>>|function|Error_->false|Okstats->Time.(<)stats.mtimecutoff)|`At_leasti->current_log_files~dirname~basename>>|funfiles->letfiles=List.sortfiles~compare:(fun(i1,_)(i2,_)->Id.cmp_newest_firsti1i2)inList.dropfilesi)>>=Deferred.List.map~f:(fun(_i,filename)->Deferred.Or_error.try_with~run:`Schedule~rest:`Log(fun()->Unix.unlinkfilename))>>|fun(_:unitOr_error.tlist)->();;typet={basename:string;dirname:string;rotation:Rotation.t;format:Format.t;mutablewriter:Writer.tDeferred.tLazy.t;mutablefilename:string;mutablelast_messages:int;mutablelast_size:int;mutablelast_time:Time.t;log_files:stringTail.t;log_on_rotation:unit->Message.tlist;perm:intoption}[@@derivingsexp_of]letwe_have_written_to_the_current_writert=Lazy.is_valt.writerletclose_writert=ifwe_have_written_to_the_current_writertthen(let%bindw=Lazy.forcet.writerinWriter.closew)elsereturn();;letrotatet~time_source=letbasename,dirname=t.basename,t.dirnameinclose_writert>>=fun()->current_log_files~dirname~basename>>=funfiles->letfiles=List.rev(List.sortfiles~compare:(fun(i1,_)(i2,_)->Id.cmp_newest_firsti1i2))inDeferred.List.iterfiles~f:(fun(id,src)->letid'=Id.rotate_oneidinletdst=make_filename~dirname~basenameid'inifString.equalsrct.filenamethenTail.extendt.log_filesdst;ifId.cmp_newest_firstidid'<>0thenUnix.rename~src~dstelsereturn())>>=fun()->maybe_delete_old_logs~dirname~basenamet.rotation.keep>>|fun()->letfilename=make_filename~dirname~basename(Id.create?time_source(Rotation.zonet.rotation))int.last_size<-0;t.last_messages<-0;t.last_time<-now~time_source;t.filename<-filename;t.writer<-open_writer~filename~perm:t.perm;;letwritet~time_sourcemsgs=letcurrent_time=now~time_sourcein(ifRotation.should_rotatet.rotation~last_messages:t.last_messages~last_size:(Byte_units.of_bytes_intt.last_size)~last_time:t.last_time~current_timethenrotate~time_sourcet>>=fun()->letmsgs=t.log_on_rotation()|>Queue.of_listinletrotation_msgs=Queue.lengthmsgsinwrite'(Lazy.forcet.writer)t.formatmsgs>>|funsize->rotation_msgs,sizeelsereturn(0,Int63.zero))>>=fun(rotation_msgs,on_rotation_log_size)->write'(Lazy.forcet.writer)t.formatmsgs>>|funsize->t.last_messages<-t.last_messages+rotation_msgs+Queue.lengthmsgs;t.last_size<-Int63.to_int_exnsize+Int63.to_int_exnon_rotation_log_size;t.last_time<-current_time;;letcreate?perm?time_source~log_on_rotationformat~basenamerotation=letlog_on_rotation=matchlog_on_rotationwith|None->Fn.const[]|Somef->finletbasename,dirname=(* make dirname absolute, because cwd may change *)matchFilename.is_absolutebasenamewith|true->Filename.basenamebasename,return(Filename.dirnamebasename)|false->basename,Sys.getcwd()inletlog_files=Tail.create()inlett_deferred=dirname>>|fundirname->letfilename=make_filename~dirname~basename(Id.create?time_source(Rotation.zonerotation))in{basename;dirname;rotation;format;writer=open_writer~filename~perm;filename;last_size=0;last_messages=0;last_time=now~time_source;log_files;log_on_rotation;perm}inletfirst_rotate_scheduled=reffalseinletclose()=let%bindt=t_deferredinclose_writertinletflush()=let%bindt=t_deferredinifLazy.is_valt.writerthenforcet.writer>>=Writer.flushedelsereturn()in(create~close~flush~rotate:(fun()->t_deferred>>=rotate~time_source)(funmsgs->t_deferred>>=funt->ifnot!first_rotate_scheduledthen(first_rotate_scheduled:=true;rotatet~time_source>>=fun()->writet~time_sourcemsgs)elsewritet~time_sourcemsgs),log_files);;endmoduleNumbered=Make(structtypet=intletcreate?time_source:__=0letrotate_one=(+)1letto_string_opt=function|0->None|x->Some(Int.to_stringx);;letcmp_newest_first=Int.ascendingletof_string_opt=function|None->Some0|Somes->(trySome(Int.of_strings)with|_->None);;end)moduleTimestamped=Make(structtypet=Time.tletcreate?time_source_zone=now~time_sourceletrotate_one=Fn.idletto_string_optts=Some(Time.to_filename_string~zone:(forceTime.Zone.local)ts);;letcmp_newest_first=Time.descendingletof_string_opt=function|None->None|Somes->(trySome(Time.of_filename_string~zone:(forceTime.Zone.local)s)with|_->None);;end)moduleDated=Make(structtypet=Date.tletcreate?time_sourcezone=Date.of_time(now~time_source)~zoneletrotate_one=Fn.idletto_string_optdate=Some(Date.to_stringdate)letcmp_newest_first=Date.descendingletof_string_opt=function|None->None|Somestr->Option.try_with(fun()->Date.of_stringstr);;end)letcreate?perm?time_sourceformat~basename(rotation:Rotation.t)=matchrotation.naming_schemewith|`Numbered->Numbered.createformat~basenamerotation?perm?time_source|`Timestamped->Timestamped.createformat~basenamerotation?perm?time_source|`Dated->Dated.createformat~basenamerotation?perm?time_source|`User_definedid->letmoduleId=(valid:Rotation.Id_intf)inletmoduleUser_defined=Make(Id)inUser_defined.createformat~basenamerotation?perm?time_source;;endletrotating_file?perm?time_source?log_on_rotationformat~basenamerotation=fst(Rotating_file.createformat~basename~log_on_rotationrotation?perm?time_source);;letrotating_file_with_tail?perm?time_source?log_on_rotationformat~basenamerotation=Rotating_file.createformat~basename~log_on_rotationrotation?perm?time_source;;letfile=File.createletwriter=Log_writer.createletstdout=letmake=Memo.general(funformat->Log_writer.createformat(Lazy.forceWriter.stdout))infun?(format=`Text)()->makeformat;;letstderr=letmake=Memo.general(funformat->Log_writer.createformat(Lazy.forceWriter.stderr))infun?(format=`Text)()->makeformat;;moduleFor_testing=structletcreate~map_output=letstdout=forceWriter.stdoutinletwe_flush_after_each_message_is_processed()=Deferred.unitincreate~flush:we_flush_after_each_message_is_processed(funqueue->Queue.iterqueue~f:(funmessage->map_output(Message.messagemessage)|>print_endline);Writer.flushedstdout);;endend(* A log is a pipe that can take one of four messages.
| Msg (level, msg) -> write the message to the current output if the level is
appropriate
| New_output f -> set the output function for future messages to f
| Flush i -> used to get around the current odd design of Pipe flushing. Sends an
ivar that the reading side fills in after it has finished handling
all previous messages.
| Rotate -> inform the output handlers to rotate exactly now
The f delivered by New_output must not hold on to any resources that normal garbage
collection won't clean up. When New_output is delivered to the pipe the current
write function will be discarded without notification. If this proves to be a
resource problem (too many syscalls for instance) then we could add an on_discard
function to writers that we call when a new writer appears.
*)moduleUpdate=structtypet=|MsgofMessage.t|New_outputofOutput.t|FlushofunitIvar.t|RotateofunitIvar.t[@@derivingsexp_of]letto_stringt=Sexp.to_string(sexp_of_tt)endletdefault_time_source=ifam_running_inline_testthenSynchronous_time_source.read_only(Synchronous_time_source.create~now:Time_ns.epoch())elseSynchronous_time_source.wall_clock();;typet={updates:Update.tPipe.Writer.t;mutableon_error:[`Raise|`CallofError.t->unit];mutablecurrent_level:Level.t;mutableoutput_is_disabled:bool;mutablecurrent_output:Output.tlist;mutablecurrent_time_source:Synchronous_time_source.t;mutabletransform:(Message.t->Message.t)option}letequalt1t2=Pipe.equalt1.updatest2.updateslethasht=Pipe.hasht.updatesletsexp_of_t_t=Sexp.Atom"<opaque>"letpush_updatetupdate=ifnot(Pipe.is_closedt.updates)thenPipe.write_without_pushbackt.updatesupdateelsefailwithf"Log: can't process %s because this log has been closed"(Update.to_stringupdate)();;letflushedt=Deferred.create(funi->push_updatet(Flushi))letrotatet=Deferred.create(funrotated->push_updatet(Rotaterotated))letis_closedt=Pipe.is_closedt.updatesmoduleFlush_at_exit_or_gc:sigvaladd_log:t->unitvalclose:t->unitDeferred.tend=structmoduleWeak_table=Caml.Weak.Make(structtypez=ttypet=zletequal=equallethash=hashend)(* contains all logs we want to flush at shutdown *)letflush_bag=lazy(Bag.create())(* contains all currently live logs. *)letlive_logs=lazy(Weak_table.create1)(* [flush] adds a flush deferred to the flush_bag *)letflusht=ifnot(is_closedt)then(letflush_bag=Lazy.forceflush_baginletflushed=flushedtinlettag=Bag.addflush_bagflushedinuponflushed(fun()->Bag.removeflush_bagtag);flushed)elsereturn();;letcloset=ifnot(is_closedt)then((* this will cause the log to flush its outputs, but because they may have been
reused it does not close them, they'll be closed automatically when they fall out
of scope. *)Pipe.write_without_pushbackt.updates(New_output(Output.combine[]));letfinished=flushedtinPipe.closet.updates;finished)elsereturn();;letfinish_at_shutdown=lazy(Shutdown.at_shutdown(fun()->letlive_logs=Lazy.forcelive_logsinletflush_bag=Lazy.forceflush_baginWeak_table.iter(funlog->don't_wait_for(flushlog))live_logs;Deferred.all_unit(Bag.to_listflush_bag)));;letadd_loglog=letlive_logs=Lazy.forcelive_logsinLazy.forcefinish_at_shutdown;Weak_table.removelive_logslog;Weak_table.addlive_logslog;(* If we fall out of scope just close and flush normally. Without this we risk being
finalized and removed from the weak table before the the shutdown handler runs, but
also before we get all of logs out of the door. *)Gc.add_finalizer_exnlog(funlog->don't_wait_for(closelog));;endletclose=Flush_at_exit_or_gc.closeletcreate_log_processor~output=letbatch_size=100inletoutput=ref(Output.combineoutput)inletmsgs=Queue.create()inletoutput_message_queuef=ifQueue.lengthmsgs=0thenf()elseOutput.write!outputmsgs>>=fun()->Queue.clearmsgs;f()infun(updates:Update.tQueue.t)->letrecloopyield_every=letyield_every=yield_every-1inifyield_every=0then(* this introduces a yield point so that other async jobs have a chance to run
under circumstances when large batches of logs are delivered in bursts. *)Scheduler.yield()>>=fun()->loopbatch_sizeelse(matchQueue.dequeueupdateswith|None->output_message_queue(fun_->return())|Someupdate->(matchupdatewith|Rotatei->output_message_queue(fun()->Output.rotate!output>>=fun()->Ivar.filli();loopyield_every)|Flushi->output_message_queue(fun()->Output.flush!output>>=fun()->Ivar.filli();loopyield_every)|Msgmsg->Queue.enqueuemsgsmsg;loopyield_every|New_outputo->output_message_queue(fun()->(* we don't close the output because we may re-use it. We rely on the
finalizer on the output to call close once it falls out of scope. *)Output.flush!output>>=fun()->output:=o;loopyield_every)))inloopbatch_size;;letprocess_log_redirecting_all_errorstroutput=Monitor.try_with~run:`Schedule~rest:`Log(fun()->letprocess_log=create_log_processor~outputinPipe.iter'r~f:process_log)>>|function|Ok()->()|Errore->(matcht.on_errorwith|`Raise->raisee|`Callf->f(Error.of_exne));;letcreate_internal~level~output~on_error~time_source~transform:t=(* this has no optional args so that we make sure to update/consider all internal call
sites if the signature changes *)letr,w=Pipe.create()inlettime_source=matchtime_sourcewith|Sometime_source->time_source|None->default_time_sourceinlett={updates=w;on_error;current_level=level;output_is_disabled=List.is_emptyoutput;current_output=output;current_time_source=time_source;transform}inFlush_at_exit_or_gc.add_logt;don't_wait_for(process_log_redirecting_all_errorstroutput);t;;moduleFor_external_use_only=struct(* a more convenient interface for use externally *)letcreate~level~output~on_error?time_source?transform():t=create_internal~level~output~on_error~time_source~transform;;endletset_outputtoutputs=t.output_is_disabled<-List.is_emptyoutputs;t.current_output<-outputs;push_updatet(New_output(Output.combineoutputs));;letget_outputt=t.current_outputletget_on_errort=t.on_errorletset_on_errorthandler=t.on_error<-handlerletlevelt=t.current_levelletset_leveltlevel=t.current_level<-levelletget_time_sourcet=t.current_time_sourceletset_time_sourcettime_source=t.current_time_source<-time_sourceletget_transformt=t.transformletset_transformtf=t.transform<-fletcopyt=create_internal~level:(levelt)~output:(get_outputt)~on_error:(get_on_errort)~time_source:(Some(get_time_sourcet))~transform:(get_transformt);;(* would_log is broken out and tested separately for every sending function to avoid the
overhead of message allocation when we are just going to drop the message. *)letwould_logtmsg_level=letoutput_or_transform_is_enabled=(nott.output_is_disabled)||Option.is_somet.transforminoutput_or_transform_is_enabled&&Level.as_or_more_verbose_than~log_level:(levelt)~msg_level;;letpush_messagetmsg=(* We want to call [transform], even if we don't end up pushing the message to an
output. This allows for someone to listen to all messages that would theoretically
be logged by this log (respecting level), and then maybe log them somewhere else. *)letmsg=matcht.transformwith|None->msg|Somef->fmsginifnott.output_is_disabledthenpush_updatet(Msgmsg);;letmessagetmsg=ifwould_logt(Message.levelmsg)thenpush_messagetmsgletcreate_messaget?level?time?tagsmsg=lettime_source=get_time_sourcetinMessage.create?level?time~time_source?tagsmsg;;letsexp?level?time?tagstsexp=ifwould_logtlevelthenpush_messaget(create_messaget?level?time?tags(`Sexpsexp));;letstring?level?time?tagsts=ifwould_logtlevelthenpush_messaget(create_messaget?level?time?tags(`Strings));;letprintf?level?time?tagstfmt=ifwould_logtlevelthenksprintf(funmsg->push_messaget(create_messaget?level?time?tags(`Stringmsg)))fmtelseifprintf()fmt;;letadd_uuid_to_tagstags=letuuid=matcham_test_runnerwith|true->Uuid.Stable.V1.for_testing|false->Uuid_unix.create()in("Log.surround_id",Uuid.to_stringuuid)::tags;;letsurround_s_gen?(tags=[])~try_with~map_return~(log_sexp:?tags:(string*string)list->Sexp.t->unit)~fmsg=lettags=add_uuid_to_tagstagsinlog_sexp~tags[%message"Enter"~_:(msg:Sexp.t)];map_return(try_withf)~f:(function|Okx->log_sexp~tags[%message"Exit"~_:(msg:Sexp.t)];x|Errorexn->log_sexp~tags[%message"Raised while "~_:(msg:Sexp.t)(exn:exn)];Exn.reraiseexn(sprintf!"%{sexp:Sexp.t}"msg));;letsurroundf_gen?(tags=[])~try_with~map_return~(log_string:?tags:(string*string)list->string->unit)=ksprintf(funmsgf->lettags=add_uuid_to_tagstagsinlog_string~tags("Enter "^msg);map_return(try_withf)~f:(function|Okx->log_string~tags("Exit "^msg);x|Errorexn->log_string~tags("Raised while "^msg^":"^Exn.to_stringexn);Exn.reraiseexnmsg));;letsurround_s?level?time?tagstmsgf=surround_s_gen?tags~try_with:(Monitor.try_with~run:`Schedule~rest:`Log)~map_return:Deferred.map~log_sexp:(fun?tagss->sexp?tags?level?timets)~fmsg;;letsurroundf?level?time?tagstfmt=surroundf_gen?tags~try_with:(Monitor.try_with~run:`Schedule~rest:`Log)~map_return:Deferred.map~log_string:(fun?tags->string?tags?level?timet)fmt;;letset_level_via_param_helper~f=letopenCommand.Paraminmap(flag"log-level"(optionalLevel.arg)~doc:"LEVEL The log level")~f:(Option.iter~f);;letset_level_via_paramlog=set_level_via_param_helper~f:(set_levellog)letset_level_via_param_lazylog=set_level_via_param_helper~f:(funlevel->set_level(Lazy.forcelog)level);;letraw?time?tagstfmt=printf?time?tagstfmtletdebug?time?tagstfmt=printf~level:`Debug?time?tagstfmtletinfo?time?tagstfmt=printf~level:`Info?time?tagstfmtleterror?time?tagstfmt=printf~level:`Error?time?tagstfmtletraw_s?time?tagstthe_sexp=sexp?time?tagstthe_sexpletdebug_s?time?tagstthe_sexp=sexp~level:`Debug?time?tagstthe_sexpletinfo_s?time?tagstthe_sexp=sexp~level:`Info?time?tagstthe_sexpleterror_s?time?tagstthe_sexp=sexp~level:`Error?time?tagstthe_sexplet%bench_module"unused log messages"=(modulestructlet(log:t)=create_internal~level:`Info~output:[Output.file`Text~filename:"/dev/null"]~on_error:`Raise~time_source:None~transform:None;;let%bench"unused printf"=debuglog"blah"let%bench"unused printf w/subst"=debuglog"%s""blah"let%bench"unused string"=stringlog~level:`Debug"blah"let%bench"used printf"=infolog"blah"end);;moduletypeGlobal_intf=sigvallog:tLazy.tvallevel:unit->Level.tvalset_level:Level.t->unitvalset_output:Output.tlist->unitvalget_output:unit->Output.tlistvalset_on_error:[`Raise|`CallofError.t->unit]->unitvalget_time_source:unit->Synchronous_time_source.tvalset_time_source:Synchronous_time_source.t->unitvalget_transform:unit->(Message.t->Message.t)optionvalset_transform:(Message.t->Message.t)option->unitvalwould_log:Level.toption->boolvalset_level_via_param:unit->unitCommand.Param.tvalraw:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avalinfo:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avalerror:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avaldebug:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avalflushed:unit->unitDeferred.tvalrotate:unit->unitDeferred.tvalprintf:?level:Level.t->?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avalraw_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalinfo_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalerror_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvaldebug_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalsexp:?level:Level.t->?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalstring:?level:Level.t->?time:Time.t->?tags:(string*string)list->string->unitvalmessage:Message.t->unitvalsurround_s:?level:Level.t->?time:Time.t->?tags:(string*string)list->Sexp.t->(unit->'aDeferred.t)->'aDeferred.tvalsurroundf:?level:Level.t->?time:Time.t->?tags:(string*string)list->('a,unit,string,(unit->'bDeferred.t)->'bDeferred.t)format4->'amoduleFor_testing:sigvaluse_test_output:?map_output:(string->string)->unit->unitendendmoduleMake_global():Global_intf=structletsend_errors_to_top_level_monitore=lete=tryError.raiseewith|e->einMonitor.send_exnMonitor.main~backtrace:`Gete;;letlog=lazy(create_internal~level:`Info~output:[Output.stderr()]~on_error:(`Callsend_errors_to_top_level_monitor)~time_source:None~transform:None);;letlevel()=level(Lazy.forcelog)letset_levellevel=set_level(Lazy.forcelog)levelletset_outputoutput=set_output(Lazy.forcelog)outputletget_output()=get_output(Lazy.forcelog)letset_on_errorhandler=set_on_error(Lazy.forcelog)handlerletget_time_source()=get_time_source(Lazy.forcelog)letset_time_sourcetime_source=set_time_source(Lazy.forcelog)time_sourceletget_transform()=get_transform(Lazy.forcelog)letset_transformtransform=set_transform(Lazy.forcelog)transformletwould_loglevel=would_log(Lazy.forcelog)levelletraw?time?tagsk=raw?time?tags(Lazy.forcelog)kletinfo?time?tagsk=info?time?tags(Lazy.forcelog)kleterror?time?tagsk=error?time?tags(Lazy.forcelog)kletdebug?time?tagsk=debug?time?tags(Lazy.forcelog)kletraw_s?time?tagsthe_sexp=sexp?time?tags(Lazy.forcelog)the_sexpletdebug_s?time?tagsthe_sexp=sexp~level:`Debug?time?tags(Lazy.forcelog)the_sexp;;letinfo_s?time?tagsthe_sexp=sexp~level:`Info?time?tags(Lazy.forcelog)the_sexp;;leterror_s?time?tagsthe_sexp=sexp~level:`Error?time?tags(Lazy.forcelog)the_sexp;;letflushed()=flushed(Lazy.forcelog)letrotate()=rotate(Lazy.forcelog)letprintf?level?time?tagsk=printf?level?time?tags(Lazy.forcelog)kletsexp?level?time?tagss=sexp?level?time?tags(Lazy.forcelog)sletstring?level?time?tagss=string?level?time?tags(Lazy.forcelog)sletmessagemsg=message(Lazy.forcelog)msgletsurround_s?level?time?tagsmsgf=surround_s?level?time?tags(Lazy.forcelog)msgf;;letsurroundf?level?time?tagsfmt=surroundf?level?time?tags(Lazy.forcelog)fmtletset_level_via_param()=set_level_via_param_lazylogmoduleFor_testing=structletuse_test_output?(map_output=Fn.id)()=set_output[Output.For_testing.create~map_output];;endendmoduleBlocking:sigmoduleOutput:sigtypetvalcreate:(Message.t->unit)->tvalstdout:tvalstderr:tendvallevel:unit->Level.tvalset_level:Level.t->unitvalset_output:Output.t->unitvalset_time_source:Synchronous_time_source.t->unitvalset_transform:(Message.t->Message.t)option->unitvalraw_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalinfo_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalerror_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvaldebug_s:?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalraw:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avalinfo:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avalerror:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avaldebug:?time:Time.t->?tags:(string*string)list->('a,unit,string,unit)format4->'avalsexp:?level:Level.t->?time:Time.t->?tags:(string*string)list->Sexp.t->unitvalsurround_s:?level:Level.t->?time:Time.t->?tags:(string*string)list->Sexp.t->(unit->'a)->'avalsurroundf:?level:Level.t->?time:Time.t->?tags:(string*string)list->('a,unit,string,(unit->'b)->'b)format4->'aend=structmoduleOutput=structtypet=Message.t->unitletcreate=Fn.idletwriteprintmsg=print(Message.to_write_only_textmsg)letstdout=write(Core.Printf.printf"%s\n%!")letstderr=write(Core.Printf.eprintf"%s\n%!")endletlevel:Level.tref=ref`Infoletwrite=refOutput.stderrlettime_source=refdefault_time_sourcelettransform=refNoneletset_levell=level:=lletlevel()=!levelletset_outputoutput=write:=outputletset_time_sourcets=time_source:=tsletset_transformf=transform:=fletwritemsg=ifScheduler.is_running()thenfailwith"Log.Global.Blocking function called after scheduler started";letmsg=match!transformwith|None->msg|Somef->fmsgin!writemsg;;letwould_logmsg_level=(* we don't need to test for empty output here because the interface only allows one
Output.t and ensures that it is always set to something. *)Level.as_or_more_verbose_than~log_level:(level())~msg_level;;letcreate_message?level?time?tagsmsg=lettime_source=!time_sourceinMessage.create?level?time~time_source?tagsmsg;;letgen?level:msg_level?time?tagsk=ksprintf(funmsg->ifwould_logmsg_levelthen(letmsg=`Stringmsginwrite(create_message?level:msg_level?time?tagsmsg)))k;;letstring?level?time?tagss=ifwould_loglevelthenwrite(create_message?level?time?tags(`Strings));;letraw?time?tagsk=gen?time?tagskletdebug?time?tagsk=gen~level:`Debug?time?tagskletinfo?time?tagsk=gen~level:`Info?time?tagskleterror?time?tagsk=gen~level:`Error?time?tagskletsexp?level?time?tagssexp=ifwould_loglevelthenwrite(create_message?level?time?tags(`Sexpsexp));;letraw_s?time?tagsthe_sexp=sexp?time?tagsthe_sexpletdebug_s?time?tagsthe_sexp=sexp~level:`Debug?time?tagsthe_sexpletinfo_s?time?tagsthe_sexp=sexp~level:`Info?time?tagsthe_sexpleterror_s?time?tagsthe_sexp=sexp~level:`Error?time?tagsthe_sexpletsurround_s?level?time?tagsmsgf=surround_s_gen?tags~try_with:Result.try_with~map_return:(funx~f->fx)~log_sexp:(sexp?level?time)~fmsg;;letsurroundf?level?time?tagsfmt=surroundf_gen?tags~try_with:Result.try_with~map_return:(funx~f->fx)~log_string:(string?level?time)fmt;;end(* Programs that want simplistic single-channel logging can open this module. It provides
a global logging facility to a single output type at a single level. *)moduleGlobal=Make_global()moduleReader=structletread_from_readerformatr~pipe_w=matchformatwith|`Sexp|`Sexp_hum->letsexp_pipe=Reader.read_sexpsrinPipe.transfersexp_pipepipe_w~f:Message.t_of_sexp>>|fun()->Pipe.closepipe_w|`Bin_prot->letrecloop()=Reader.read_bin_protrMessage.bin_reader_t>>=function|`Eof->Pipe.closepipe_w;return()|`Okmsg->Pipe.writepipe_wmsg>>=loopinloop();;letpipe_of_readerformatreader=Pipe.create_reader~close_on_exception:false(funpipe_w->read_from_readerformatreader~pipe_w);;letpipeformatfilename=Pipe.create_reader~close_on_exception:false(funpipe_w->Reader.with_filefilename~f:(funreader->read_from_readerformatreader~pipe_w));;moduleExpert=structletread_oneformatreader=matchformatwith|`Sexp|`Sexp_hum->let%mapsexp=Reader.read_sexpreaderinReader.Read_result.mapsexp~f:Message.t_of_sexp|`Bin_prot->Reader.read_bin_protreaderMessage.bin_reader_t;;endendmoduleFor_testing=structletcreate_output=Output.For_testing.createletcreate~map_outputlevel=letoutput=[create_output~map_output]increate_internal~output~level~on_error:`Raise~time_source:None~transform:None;;endmodulePrivate=structmoduleMessage=MessageendincludeFor_external_use_only