PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/local/nagios/venv/lib/python3.13/site-packages/supervisor
Viewing File: /usr/local/nagios/venv/lib/python3.13/site-packages/supervisor/dispatchers.py
import errno from supervisor.medusa.asynchat_25 import find_prefix_at_end from supervisor.medusa.asyncore_25 import compact_traceback from supervisor.compat import as_string from supervisor.events import notify from supervisor.events import EventRejectedEvent from supervisor.events import ProcessLogStderrEvent from supervisor.events import ProcessLogStdoutEvent from supervisor.states import EventListenerStates from supervisor.states import getEventListenerStateDescription from supervisor import loggers class PDispatcher: """ Asyncore dispatcher for mainloop, representing a process channel (stdin, stdout, or stderr). This class is abstract. """ closed = False # True if close() has been called def __init__(self, process, channel, fd): self.process = process # process which "owns" this dispatcher self.channel = channel # 'stderr' or 'stdout' self.fd = fd self.closed = False # True if close() has been called def __repr__(self): return '<%s at %s for %s (%s)>' % (self.__class__.__name__, id(self), self.process, self.channel) def readable(self): raise NotImplementedError def writable(self): raise NotImplementedError def handle_read_event(self): raise NotImplementedError def handle_write_event(self): raise NotImplementedError def handle_error(self): nil, t, v, tbinfo = compact_traceback() self.process.config.options.logger.critical( 'uncaptured python exception, closing channel %s (%s:%s %s)' % ( repr(self), t, v, tbinfo ) ) self.close() def close(self): if not self.closed: self.process.config.options.logger.debug( 'fd %s closed, stopped monitoring %s' % (self.fd, self)) self.closed = True def flush(self): pass class POutputDispatcher(PDispatcher): """ Dispatcher for one channel (stdout or stderr) of one process. Serves several purposes: - capture output sent within <!--XSUPERVISOR:BEGIN--> and <!--XSUPERVISOR:END--> tags and signal a ProcessCommunicationEvent by calling notify(event). - route the output to the appropriate log handlers as specified in the config. """ childlog = None # the current logger (normallog or capturelog) normallog = None # the "normal" (non-capture) logger capturelog = None # the logger used while we're in capturemode capturemode = False # are we capturing process event data output_buffer = b'' # data waiting to be logged def __init__(self, process, event_type, fd): """ Initialize the dispatcher. `event_type` should be one of ProcessLogStdoutEvent or ProcessLogStderrEvent """ self.process = process self.event_type = event_type self.fd = fd self.channel = self.event_type.channel self._init_normallog() self._init_capturelog() self.childlog = self.normallog # all code below is purely for minor speedups begintoken = self.event_type.BEGIN_TOKEN endtoken = self.event_type.END_TOKEN self.begintoken_data = (begintoken, len(begintoken)) self.endtoken_data = (endtoken, len(endtoken)) self.mainlog_level = loggers.LevelsByName.DEBG config = self.process.config self.log_to_mainlog = config.options.loglevel <= self.mainlog_level self.stdout_events_enabled = config.stdout_events_enabled self.stderr_events_enabled = config.stderr_events_enabled def _init_normallog(self): """ Configure the "normal" (non-capture) log for this channel of this process. Sets self.normallog if logging is enabled. """ config = self.process.config channel = self.channel logfile = getattr(config, '%s_logfile' % channel) maxbytes = getattr(config, '%s_logfile_maxbytes' % channel) backups = getattr(config, '%s_logfile_backups' % channel) to_syslog = getattr(config, '%s_syslog' % channel) if logfile or to_syslog: self.normallog = config.options.getLogger() if logfile: loggers.handle_file( self.normallog, filename=logfile, fmt='%(message)s', rotating=not not maxbytes, # optimization maxbytes=maxbytes, backups=backups ) if to_syslog: loggers.handle_syslog( self.normallog, fmt=config.name + ' %(message)s' ) def _init_capturelog(self): """ Configure the capture log for this process. This log is used to temporarily capture output when special output is detected. Sets self.capturelog if capturing is enabled. """ capture_maxbytes = getattr(self.process.config, '%s_capture_maxbytes' % self.channel) if capture_maxbytes: self.capturelog = self.process.config.options.getLogger() loggers.handle_boundIO( self.capturelog, fmt='%(message)s', maxbytes=capture_maxbytes, ) def removelogs(self): for log in (self.normallog, self.capturelog): if log is not None: for handler in log.handlers: handler.remove() handler.reopen() def reopenlogs(self): for log in (self.normallog, self.capturelog): if log is not None: for handler in log.handlers: handler.reopen() def _log(self, data): if data: config = self.process.config if config.options.strip_ansi: data = stripEscapes(data) if self.childlog: self.childlog.info(data) if self.log_to_mainlog: if not isinstance(data, bytes): text = data else: try: text = data.decode('utf-8') except UnicodeDecodeError: text = 'Undecodable: %r' % data msg = '%(name)r %(channel)s output:\n%(data)s' config.options.logger.log( self.mainlog_level, msg, name=config.name, channel=self.channel, data=text) if self.channel == 'stdout': if self.stdout_events_enabled: notify( ProcessLogStdoutEvent(self.process, self.process.pid, data) ) else: # channel == stderr if self.stderr_events_enabled: notify( ProcessLogStderrEvent(self.process, self.process.pid, data) ) def record_output(self): if self.capturelog is None: # shortcut trying to find capture data data = self.output_buffer self.output_buffer = b'' self._log(data) return if self.capturemode: token, tokenlen = self.endtoken_data else: token, tokenlen = self.begintoken_data if len(self.output_buffer) <= tokenlen: return # not enough data data = self.output_buffer self.output_buffer = b'' try: before, after = data.split(token, 1) except ValueError: after = None index = find_prefix_at_end(data, token) if index: self.output_buffer = self.output_buffer + data[-index:] data = data[:-index] self._log(data) else: self._log(before) self.toggle_capturemode() self.output_buffer = after if after: self.record_output() def toggle_capturemode(self): self.capturemode = not self.capturemode if self.capturelog is not None: if self.capturemode: self.childlog = self.capturelog else: for handler in self.capturelog.handlers: handler.flush() data = self.capturelog.getvalue() channel = self.channel procname = self.process.config.name event = self.event_type(self.process, self.process.pid, data) notify(event) msg = "%(procname)r %(channel)s emitted a comm event" self.process.config.options.logger.debug(msg, procname=procname, channel=channel) for handler in self.capturelog.handlers: handler.remove() handler.reopen() self.childlog = self.normallog def writable(self): return False def readable(self): if self.closed: return False return True def handle_read_event(self): data = self.process.config.options.readfd(self.fd) self.output_buffer += data self.record_output() if not data: # if we get no data back from the pipe, it means that the # child process has ended. See # mail.python.org/pipermail/python-dev/2004-August/046850.html self.close() class PEventListenerDispatcher(PDispatcher): """ An output dispatcher that monitors and changes a process' listener_state """ childlog = None # the logger state_buffer = b'' # data waiting to be reviewed for state changes READY_FOR_EVENTS_TOKEN = b'READY\n' RESULT_TOKEN_START = b'RESULT ' READY_FOR_EVENTS_LEN = len(READY_FOR_EVENTS_TOKEN) RESULT_TOKEN_START_LEN = len(RESULT_TOKEN_START) def __init__(self, process, channel, fd): PDispatcher.__init__(self, process, channel, fd) # the initial state of our listener is ACKNOWLEDGED; this is a # "busy" state that implies we're awaiting a READY_FOR_EVENTS_TOKEN self.process.listener_state = EventListenerStates.ACKNOWLEDGED self.process.event = None self.result = b'' self.resultlen = None logfile = getattr(process.config, '%s_logfile' % channel) if logfile: maxbytes = getattr(process.config, '%s_logfile_maxbytes' % channel) backups = getattr(process.config, '%s_logfile_backups' % channel) self.childlog = process.config.options.getLogger() loggers.handle_file( self.childlog, logfile, '%(message)s', rotating=not not maxbytes, # optimization maxbytes=maxbytes, backups=backups, ) def removelogs(self): if self.childlog is not None: for handler in self.childlog.handlers: handler.remove() handler.reopen() def reopenlogs(self): if self.childlog is not None: for handler in self.childlog.handlers: handler.reopen() def writable(self): return False def readable(self): if self.closed: return False return True def handle_read_event(self): data = self.process.config.options.readfd(self.fd) if data: self.state_buffer += data procname = self.process.config.name msg = '%r %s output:\n%s' % (procname, self.channel, data) self.process.config.options.logger.debug(msg) if self.childlog: if self.process.config.options.strip_ansi: data = stripEscapes(data) self.childlog.info(data) else: # if we get no data back from the pipe, it means that the # child process has ended. See # mail.python.org/pipermail/python-dev/2004-August/046850.html self.close() self.handle_listener_state_change() def handle_listener_state_change(self): data = self.state_buffer if not data: return process = self.process procname = process.config.name state = process.listener_state if state == EventListenerStates.UNKNOWN: # this is a fatal state self.state_buffer = b'' return if state == EventListenerStates.ACKNOWLEDGED: if len(data) < self.READY_FOR_EVENTS_LEN: # not enough info to make a decision return elif data.startswith(self.READY_FOR_EVENTS_TOKEN): self._change_listener_state(EventListenerStates.READY) tokenlen = self.READY_FOR_EVENTS_LEN self.state_buffer = self.state_buffer[tokenlen:] process.event = None else: self._change_listener_state(EventListenerStates.UNKNOWN) self.state_buffer = b'' process.event = None if self.state_buffer: # keep going til its too short self.handle_listener_state_change() else: return elif state == EventListenerStates.READY: # the process sent some spurious data, be strict about it self._change_listener_state(EventListenerStates.UNKNOWN) self.state_buffer = b'' process.event = None return elif state == EventListenerStates.BUSY: if self.resultlen is None: # we haven't begun gathering result data yet pos = data.find(b'\n') if pos == -1: # we can't make a determination yet, we dont have a full # results line return result_line = self.state_buffer[:pos] self.state_buffer = self.state_buffer[pos+1:] # rid LF resultlen = result_line[self.RESULT_TOKEN_START_LEN:] try: self.resultlen = int(resultlen) except ValueError: try: result_line = as_string(result_line) except UnicodeDecodeError: result_line = 'Undecodable: %r' % result_line process.config.options.logger.warn( '%s: bad result line: \'%s\'' % (procname, result_line) ) self._change_listener_state(EventListenerStates.UNKNOWN) self.state_buffer = b'' notify(EventRejectedEvent(process, process.event)) process.event = None return else: needed = self.resultlen - len(self.result) if needed: self.result += self.state_buffer[:needed] self.state_buffer = self.state_buffer[needed:] needed = self.resultlen - len(self.result) if not needed: self.handle_result(self.result) self.process.event = None self.result = b'' self.resultlen = None if self.state_buffer: # keep going til its too short self.handle_listener_state_change() def handle_result(self, result): process = self.process procname = process.config.name logger = process.config.options.logger try: self.process.group.config.result_handler(process.event, result) logger.debug('%s: event was processed' % procname) self._change_listener_state(EventListenerStates.ACKNOWLEDGED) except RejectEvent: logger.warn('%s: event was rejected' % procname) self._change_listener_state(EventListenerStates.ACKNOWLEDGED) notify(EventRejectedEvent(process, process.event)) except: logger.warn('%s: event caused an error' % procname) self._change_listener_state(EventListenerStates.UNKNOWN) notify(EventRejectedEvent(process, process.event)) def _change_listener_state(self, new_state): process = self.process procname = process.config.name old_state = process.listener_state msg = '%s: %s -> %s' % ( procname, getEventListenerStateDescription(old_state), getEventListenerStateDescription(new_state) ) process.config.options.logger.debug(msg) process.listener_state = new_state if new_state == EventListenerStates.UNKNOWN: msg = ('%s: has entered the UNKNOWN state and will no longer ' 'receive events, this usually indicates the process ' 'violated the eventlistener protocol' % procname) process.config.options.logger.warn(msg) class PInputDispatcher(PDispatcher): """ Input (stdin) dispatcher """ def __init__(self, process, channel, fd): PDispatcher.__init__(self, process, channel, fd) self.input_buffer = b'' def writable(self): if self.input_buffer and not self.closed: return True return False def readable(self): return False def flush(self): # other code depends on this raising EPIPE if the pipe is closed sent = self.process.config.options.write(self.fd, self.input_buffer) self.input_buffer = self.input_buffer[sent:] def handle_write_event(self): if self.input_buffer: try: self.flush() except OSError as why: if why.args[0] == errno.EPIPE: self.input_buffer = b'' self.close() else: raise ANSI_ESCAPE_BEGIN = b'\x1b[' ANSI_TERMINATORS = (b'H', b'f', b'A', b'B', b'C', b'D', b'R', b's', b'u', b'J', b'K', b'h', b'l', b'p', b'm') def stripEscapes(s): """ Remove all ANSI color escapes from the given string. """ result = b'' show = 1 i = 0 L = len(s) while i < L: if show == 0 and s[i:i + 1] in ANSI_TERMINATORS: show = 1 elif show: n = s.find(ANSI_ESCAPE_BEGIN, i) if n == -1: return result + s[i:] else: result = result + s[i:n] i = n show = 0 i += 1 return result class RejectEvent(Exception): """ The exception type expected by a dispatcher when a handler wants to reject an event """ def default_handler(event, response): if response != b'OK': raise RejectEvent(response)