From 33098e292921047cd3a85f289a3c7966b11675ba Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Thu, 16 Jan 2020 12:16:37 -0500 Subject: [PATCH 01/21] chore: add missing semver type dependency (#702) --- packages/opentelemetry-plugin-dns/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opentelemetry-plugin-dns/package.json b/packages/opentelemetry-plugin-dns/package.json index 7faf1a2e82..428b6dc127 100644 --- a/packages/opentelemetry-plugin-dns/package.json +++ b/packages/opentelemetry-plugin-dns/package.json @@ -45,6 +45,7 @@ "@opentelemetry/tracing": "^0.3.2", "@types/mocha": "^5.2.7", "@types/node": "^12.7.12", + "@types/semver": "^6.2.0", "@types/shimmer": "^1.0.1", "@types/sinon": "^7.5.0", "codecov": "^3.6.1", From 0d73dd711309e0b6e580b9803359f8090e929835 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Thu, 16 Jan 2020 15:41:30 -0500 Subject: [PATCH 02/21] chore: fix compilation (#705) --- .../opentelemetry-plugin-pg-pool/test/pg-pool.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts index f2f3ebb862..69edba1677 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts @@ -186,6 +186,12 @@ describe('pg-pool@2.x', () => { if (err) { return done(err); } + if (!release) { + throw new Error('Did not receive release function'); + } + if (!client) { + throw new Error('No client received'); + } release(); assert.ok(client); runCallbackTest(parentSpan, pgPoolattributes, events, okStatus, 1, 0); From 82828b7fa98f4cb15afca12dac6aa935c240a452 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Thu, 16 Jan 2020 16:22:59 -0500 Subject: [PATCH 03/21] feat: stackdriver trace exporter (#648) * feat: stackdriver trace exporter * Update packages/opentelemetry-exporter-stackdriver-trace/README.md Co-Authored-By: Mayur Kale * chore: undo changes to basic tracer example * chore: remove dependent type from sdt export * chore: make properties readonly * chore: remove throw * chore: document unused types * chore: remove unused type * fix: lint * feat: infer google application credentials * chore: move stackdriver trace to its own example * chore: missing service name is actually fine * chore: add link to stackdriver trace example * test: add tests for transformer * test: add tests for stackdriver trace export * test: speed up tests * chore: update agent label version * chore: update test * fix: lint * fix: lint * chore: update example * chore: remove service name * chore: fix tests * chore: remove serviceName from readme * chore: fix lint * chore: review comments * chore: review comments * chore: add screenshot of stackdriver trace Co-authored-by: Mayur Kale --- .circleci/config.yml | 1 + README.md | 1 + examples/basic-tracer-node/multi_exporter.js | 7 +- examples/stackdriver-trace/.gitignore | 1 + examples/stackdriver-trace/README.md | 37 +++ examples/stackdriver-trace/images/trace.png | Bin 0 -> 267108 bytes examples/stackdriver-trace/index.js | 64 +++++ examples/stackdriver-trace/package.json | 37 +++ .../.npmignore | 4 + .../LICENSE | 201 ++++++++++++++ .../README.md | 92 +++++++ .../package.json | 69 +++++ .../src/external-types.ts | 51 ++++ .../src/index.ts | 17 ++ .../src/trace.ts | 145 ++++++++++ .../src/transform.ts | 130 +++++++++ .../src/types.ts | 142 ++++++++++ .../src/version.ts | 18 ++ .../test/exporter.test.ts | 227 ++++++++++++++++ .../test/transform.test.ts | 255 ++++++++++++++++++ .../tsconfig.json | 11 + .../tslint.json | 4 + 22 files changed, 1510 insertions(+), 4 deletions(-) create mode 100644 examples/stackdriver-trace/.gitignore create mode 100644 examples/stackdriver-trace/README.md create mode 100644 examples/stackdriver-trace/images/trace.png create mode 100644 examples/stackdriver-trace/index.js create mode 100644 examples/stackdriver-trace/package.json create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/.npmignore create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/LICENSE create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/README.md create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/package.json create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/src/external-types.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/src/index.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/src/trace.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/src/transform.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/src/types.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/src/version.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/test/exporter.test.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/test/transform.test.ts create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/tsconfig.json create mode 100644 packages/opentelemetry-exporter-stackdriver-trace/tslint.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b04413452..2f7a3a4598 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -73,6 +73,7 @@ cache_2: &cache_2 - packages/opentelemetry-plugin-mysql/node_modules - packages/opentelemetry-exporter-collector/node_modules - packages/opentelemetry-plugin-xml-http-request/node_modules + - packages/opentelemetry-exporter-stackdriver-trace/node_modules node_unit_tests: &node_unit_tests steps: diff --git a/README.md b/README.md index aba1d45d39..1cc87cc19b 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ OpenTelemetry is vendor-agnostic and can upload data to any backend with various #### Trace Exporters - [@opentelemetry/exporter-jaeger](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-jaeger) - [@opentelemetry/exporter-zipkin](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-zipkin) +- [@opentelemetry/exporter-stackdriver-trace](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-stackdriver-trace) - [@opentelemetry/exporter-collector](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-collector) #### Metric Exporters diff --git a/examples/basic-tracer-node/multi_exporter.js b/examples/basic-tracer-node/multi_exporter.js index 2d7723397e..309daf4311 100644 --- a/examples/basic-tracer-node/multi_exporter.js +++ b/examples/basic-tracer-node/multi_exporter.js @@ -17,10 +17,9 @@ const collectorExporter = new CollectorExporter({serviceName: 'basic-service'}); registry.addSpanProcessor(new BatchSpanProcessor(zipkinExporter, { bufferSize: 10 // This is added for example, default size is 100. })); - -// It is recommended to use SimpleSpanProcessor in case of Jaeger exporter as -// it's internal client already handles the spans with batching logic. -registry.addSpanProcessor(new SimpleSpanProcessor(jaegerExporter)); +tracer.addSpanProcessor(new BatchSpanProcessor(jaegerExporter), { + bufferSize: 10 +}); registry.addSpanProcessor(new SimpleSpanProcessor(collectorExporter)); diff --git a/examples/stackdriver-trace/.gitignore b/examples/stackdriver-trace/.gitignore new file mode 100644 index 0000000000..a5d657b71a --- /dev/null +++ b/examples/stackdriver-trace/.gitignore @@ -0,0 +1 @@ +service_account_key.json \ No newline at end of file diff --git a/examples/stackdriver-trace/README.md b/examples/stackdriver-trace/README.md new file mode 100644 index 0000000000..4a6e09dc7e --- /dev/null +++ b/examples/stackdriver-trace/README.md @@ -0,0 +1,37 @@ +# Overview + +This example shows how to use [@opentelemetry/tracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing) to instrument a simple Node.js application - e.g. a batch job - and export spans either to [Stackdriver Trace](https://cloud.google.com/trace/). + +## Installation + +```sh +$ # from this directory +$ npm install +``` + +## Authenticate + +If you are running in a GCP environment, the exporter will automatically authenticate as the service account of your environment. Please make sure that it has permission to access stackdriver trace. + +If you are not running in a GCP environment you will need to create a service account and save the service account key json in the root of this example named `service_account_key.json`. For more information, visit . + +## Run the Application + +```sh +$ # from this directory +$ npm start +``` + +## View traces + +https://console.cloud.google.com/traces/list?project=your-project-id + +

+ +## Useful links +- For more information on OpenTelemetry, visit: +- For more information on tracing, visit: + +## LICENSE + +Apache License 2.0 diff --git a/examples/stackdriver-trace/images/trace.png b/examples/stackdriver-trace/images/trace.png new file mode 100644 index 0000000000000000000000000000000000000000..66af4af9161c4c7bf80c8072f6492e54b8106895 GIT binary patch literal 267108 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 L)qqW?o`|e>+{4`Eir%Eyg z{y978!#{y}|9Jlc z)!VB3?m=fO+23_!8YwBn>WcTov+!|AyMgM74xXIkDc(rOk~YSjRyyrLFcTLBg#C6L z2!1WZst$1F&VcW5YuSE&U>F?y_rv&y6aQz%D5nNOEsw35Un^)sTn&^Q2Rut1m_N~; zo}dcZX_r0jrtA%R!0y_5QFVInuOv;bf!yC^7IX!=YyScjT_I7W1gI)$^*={48!Z5S zAHtqj@d#$;i z`E>lI0FPLN9D0rP+I23nL8z#+SUhW%nzkQy7$dhrxsz1K4!;rxt=JK|x2=_j^yp;1IgCCnz8$d|nR3mYL2i$pb>As#S-muVY8gaEeE@B&#k!_qtZ@UMzL3eDs4$+^z>TL`jSZl}s4u z%&yO9suy#Jf-+SLK$^HF5y{q@iIi|(Z9}WJ zYRBP+!>I+Km+Y6DRzWDUg=wb5i3}ho8i{p@y>+$MOw1O#U&F%>07`fSjy(DHE(N;4 z`$gk*)odQQD=ZHf8WpVgg*a?Z8U`QnMQX3M^Nx04I1;;EZzG+!QO@%*|0}r!CS6|J z2&<(}URdAczb(f2u6keje@aDz)^Md~R=Y8|-av^tgZE zo5KfuEItUC<}?`3Z?0Q`o$q6}oSEo1|1fn!FtkvX z=d;}SLuk>ca3jiA?XkHt(hY>Dg-9C$dvlxH`-urpBPSRPD*NY`5Y0IjzZ&i~GvH?TF2% zI+($lntSY^D7_P*TIAr;=<*s!ff=gc2<6sJ0AiVzdvDd2iztBH2AHMHL}3N|t>j^_ z8B@-HzvUI4q}@6hfzpC+AvDWCCXiAJok5lE-IlzypwoYKiGJO~#8!%R(&A;=d#`jf zuu}seu+z2kkiaNE{;Uo4&LjLhVgp=&J7iGy=B%MLQc*|7mR&Tow&oe12b|>X-?t&+ zJYpdoE#sx_QKE!vBQ~lA{KKg^p-oM!(xWKJ0Ij@ibW=u2cBKaB(Dl{n=(%`sFg^`p z{iW>xie^v}Ag%+J^qO0)l`QVA;37!&7=rYXBE_Ic;^yqO_{C!tP>Q5kbhGhM=j{91_?iasLfn``q%NPBhAHt=x@ z_$}m`bkfGJz>o#}ry;`!+0(cu?D*AjZ%cK}hk90LS*OTh9_kIcQ)qUO>h11(Ixu^D zX+Lem6M51H%+;#H*t`kME+@kP>ZjV>mo;rI3t8Q|@eC~oZkv+`+FZe!iqu2?F91BU z>R+@SS*!ouKKkn3?O_Mm*IxVd@g@0m=?! z%MCrLA@PG@QwVf+I(Bly`I4fJiiCB);<2VqUW zpkh_rr-xwYu-1Wk`~u@uLM?j`szeP?$03t_r*E%6TKl*a_+;?-fAGAgETqg7J~k8O zsFh>rb)`o3uyN^3K|8%zcN5a{v7?oIY;~%wW|>U&AP??Khcq1bY{?H4+9Q}&j9e*l z#XeAfqnK4#S1+&frp$ZrQ0Ux`!~0&LDVmDojs*uy7WPWVsX_bDQw6Y9PsWEF&exRi z?2A86qxT42wof)8*y3yA*0!ip84!9So1@TDJbYR$MBBZYvcehYPr% zxU11VO7m_qb_1|&sQ!}6vXc{+S)jU-Es;`cXUtzj!#S5d+{=WKs%_O-g7Kt*Yb|&- zf2rP(X)R6M<-aEHd0^bZ*UI0Ec#7lbwEtWKq`@Vld(V;zbb`Y>eCnh17(T$&SX9|2 zzd#wGa>~JZnW-PP;9*o4ehQ7cx|X&!;5e|m9L=3GN*!U^c;`4dU60(Q7H=E?e+po7 zKk&hPD6uWgB`ForDZ;N*O)zAK^7F5wkkQ0_kAMu`K$nz!#SblloP^MK6L^gZ1jugX z{OmjgMo5T!N38?Fg8`4zA~Y#?Dvpne>E_{sFyY>nWV;R1mQd+0GV!v8(hVQEl*Oga z5HuF`Aff(rmfoV?# zk?A_kG4m+y7Sd-PP(rI~^C5{{w`o5{tCZ#mv4isR!z~yGQqt;2f+0u*oa3+!VYk3> z0;86~cM6pfh`D-rCvO5&=~KP=_Afz*ZIg(V;9>SLpbB&Atwk;8X&(;y?SR9cW6~skUoG+=z#zeNqRZ-SuCss;PJX;T#M^JM zl$yW_SSaAKsZ_iy9b&8}3uB^ep8&2}7|Eb2IJ%;d*)0I5=h zvTL8#-ixh3|3SQiAYgyr{#LwiE|6s7%uABKp5pdi2`#a~NZxyuXK4otAhW-~HZQuN z|&?~d4lm!$9}Lf-ni}(6@zc-3p?G}r(z~__l55n&x0ekvrQq&4{|t;ZxFZL1{fBI z1UAQj*VPFf0O~McAVM(5ZT#00e+wfI9nk<&L{;p=jM*{#WN{0kk*>1F%8{gyT zefKW$v_%CiZ~vHek>rN`rg;{w!@|eoBIlh@KB~r%-iEk$AMy5`afxSJ@|SPQ<05*A z$M>W$cRYiQEXqiE3Lc)*vMy7XK=JEue~6R9Dq1T`KiWiZl-ueOenZKKG%@_BZTw+M zBnU7DQ?!Iz%&_UMT9ZwPEk$Nh?lYjo~5AqcVb+#(L*khul^!<8`#hP+J z7AKtZaEp@T{>pdZt#v&Qt)&r|CwIvpO6Daz%J)aHMh7s469E}W*Rf0bmj9u>4j^>f zo_^jQD%iZtkx7K?%Ag~VW>>421L04nDH^!P|McCjJ8&4y^)6Tg6C&rZE)0hS6(;BB zSg7WR$~*ARr>$yH1Q2I}r@4-aCtpF4itPl1_J=*>QM>Od02ek9VX&b6nOovwL6mfD z1~D?a;044}X)hqxePRF_$V9PBqlA>o2gt))m8yh#n`&I3Twr$sCh9_Qcr*rijp?6R z?Nb!cK9*XLy>w^f!aP3jO?=e;Nnjdt(@d3?enYXlI@Q-5+PhQ~$WCBW>B6@2+go+s zLOtvhtV82Ox%zKRbKvM=AUU{4x>JvUI8q**TW-LZK7O#=X7-OWh@lggf;+Uek~TOX zTpCFZJCW{AX^U8eR%v(?^Gapfz@P8#Ne?&Q@S=S``s3{y4x8u9G30)w$o+ntd)9{} zB82&Xx-LZbERVv{W4;3|bk|RQokJc)eia_YcbY5@B`ZIr_wOgkP=M;Fo_K3k4s^fe zfvJds@f5!aYroTwj`Q^2!2-B0xF z8)P@Qy?$%JafFF}=X&h*P+-eF`5%TEhl;GmnNXKdp~KxHjd8nyQap?t51xgDh1q-K|+lH zMG>_NaKjnFH?t^SuSB?qLRbVv5%HNL%##k8x@~c4+(i4o{lhcFKS4?jFPL=C`BJjZN^mBfXT6Y`Yr`CC` zrext|GiEjseTv`S2K91fC;0BtG&S5z!&S#d@UdkFUiod(_s7q_e_VqS_-hCQT~!cy zi+WxHysGO;WwJ8r5qOA_K1&z1pO2Pxk}L>)br^52zi9Kx*_|2ZgKWZY?^sTQjTOD# zuq&f@xiIM+*OhEZ{OUICOOcyz*L2NKbV{S>$cd=9qgUUe_#(y>URuWCL?^1ul`^DVwEol>^3%=x98_gygG z1*bo7nZJ_vhhD^1TBjEZyGm)60R?|`axBs&?`}?yTf4}l-6Fo%q_-}pD3xbvuol|p zyFuJ86aH1taxCgrYRqK~;fLAB!cII-B z`|nRG16$>C=kZ_SEq>T(ib&~X{Fe#tNc#+5=*Emcaztc53HIBsBT16YC!6`&@Kmk& zw`3~gXh1!FuciKa8+v>WU3>cn2P;k-wheD64W;UVR8%LOrYhT*e-YdOy zv~mn(G5)O%&jP*AV&6XC5a2P~m_!&9RgjR<=AT~)ZAS0|zU0m2;^96rJy_K*a!G~f z1n3bxFhMqX*)Uv}1|unE(*6oTu`h3o-&cl{i9TQNs9*TqcsRj{1j5gX6@T6of^*nK ziAVg7xGECHP%CptfuUdZ&tGYOV{DXUh+`Zs?T0{L0dkDwENv$%2a1 z|M8|yu2A%gjk(zJ*W#*lFc9m09i;!^;kXBPZ5H*p zimn->=V zv|^5Kq(N``D}x*Zy`D$mQKMhNYS)vZ?F^U3>uDJ|iIO*Dza)HrEn0FXbEYGb#k%BS zRuOk?=6qwTS%Q| zK%J_Oe^dHNg=dft89J!r{vs1dFtHzU7IPZxK?98uQgws^=WVh3+k55exxG`kN;z@U zyP3xQv^|>9Tr)$2&LeF!xygHldmmN`ChZjCRqXS&+bI*|mkfHz;$@Eu_FJ!**p7Yg zkvlHy;99R~d&w>##CS~AVAjGxPBWPJ#%iD3Y{vNU?pFGWjXUk*DJdFb@)^3m1q;oO zY*QWQ%gnzAl|IP-dYXH(q%Ah9`}<(}{MJB!-?w)*Y_m+txqWK3m(2CmKG1VpH^_Vp zWSyQayRoMkKl?PJ{z9KL^_{Cl^-3yWd^FdT?Bnd2cS(EHEY_nd4DOoA6D{qlj~|XR zeJArpR`x0u&Quo)ctz^_dSO(_QWeeEzQOilCQqgBDD<|klYDvg{=N4Ryf83Zr9wqr zq<+$6cWI-4kF6*$hjFdGua}K6X~`!vFEPp;ezJRx1l=Cc~zmiikQ=&wdqgXYf9+>Z^ z9gQm_aqGdlmmb7-l=QA@5?!gQDNoL1f1P>imC@+5cO^UJYhPsa3jT{o1C+=L~QG|pIOKA_19e*J*S zE=+2Vn)tqZFZzrDyV+*h8HNZc(X>Ph4;RS<5j@*155Yq3vqr6qH5ow#S7fH(fYpr; zY}s0GMc!yXW?3Ohlb$O`dLx+9AX`XBTzgrweFLM)3U_x8-5^VD>bABY(}|_Zb9nZ6 zX&uUiRw}tH_9e8eCT=B*>XENkSweEGhhLlPEN0{9MDVU?F4_e-T<$q4e{aHevU(KL z^!qlO8F8dgP_Yr7!|o&;bjoZh9XJ<${;j)BDU$pArvt(h1J2w@U%wId14~H~3tK?u z%Q^nHy#JFsonAs^*{d<^H0Q^nPeMuvz44T_`K#105{bCe1qCeh9+_`Fct{?9{z0-J zR+Fza1}+P!GpRvCTy0t-a7t_SZa+c42TX)oy1dFkUo!4MNh6Ls2va;vb51pPAYb>-IuG*#ZwH%7PZ zn+EIdbYx6Pm&iQTYrPP^y+CYP6EpYzj+Y_nfdBR@SB0SM!rYFe@lFj>qhjYBzc#G7 z%z}z9Ke}G;a7g>5MSI+*?#N(ys%&O1rPgN)2+NqdePrzF_rU*ElLUi-E1#shw{xI%Nn$}($Sg38|0CIZ zj|$ZAN%F}b!|RyAWfb{C^A~TvlJ&VBHCWv=|FkOLqnny^qEb;v!p@`I22+CNiwwhz zLZOqVZMWQ$ZhLh-VehHwXJkL}jj;5Buq$p>S5U&IVsBr7&m#U5aTb;)Ps|^}co|W&Vdj%KPmpld{wBDv_ zKD{)_S9+VEdt-P_mCUw2p90(Kx_wl@>G_mC0uz3Niu^6NUdx`@%RTmV*&Zht)#fO2 z<_Pe|U(y8lu-v7nvS`@xnslEjv|P%YTa4t6)bqaAVndf(lV+j1swm4!)?SdV*8foM zk!=K@Ydt{>1I}Ee2)|*{BE9@IT`dI63_6ml&n2IOi9k(>{653H^(6fJK_c5-yGhH>V zRVROSxgh&-8%L^oyLg&O=L|jB{meZ33)_1c{l&g7v;CR2Tk>SA&0ZfTrlG-7Tw{Lx zX_$NFJ$agQ;8_{-CuJkdrdiV}W}&OIom5iCvu?)sD`nqpj;NU^47C20<%wv3)Y3e2JnpY^neAy8y@#=oOKzs7}S^ zb+>?kDU;gIoU%gmb=gs@Udv;>A35HAukS)$cZG|NW{uW;jom#}DX`TeDUuD{c{AOM zF8R!h%(n7cOv&El)5>E??XOw5D1#b3GV}{n@@)8DTf~w&s^(oCcjSKZ#ZR}P=xQ6G zh0jRU-qV3DLi<{~g2#ciL4%XSmmW3!sL(q)zS-Gh);39erpmKJH^8@lc0_*B@%|ia zQm%h*DdiJ`R@3%?Tk z|4zHlw^gDWvdtX7VuOScc!Q z%JSugW$|gDpnkJfSNu|rT!Gv2lqfANNQ)7Z@om-Nj{D5OcLJvo$Hy1?Zbl6X54gmW zcNDDI20xii4NM&hxvANge&&p|{!&I`fwfMK)ow87KxUD2OnaH!d#dT)qxj{a2^92C zw+5(~6dnqGlxNqew<#h=Rt1q`4v^^43Zwl^i&&xAziWZlU^9jmr?9>VcE{zd}k(N%h zex5%?EjgrGi^S{VgL+UU+q!s=tK2*v7dqg%zDeva;V5 zc*rT@{F`}O2O*g~7)_L09)Py2I_{l8E_>o7m`A@Ccg;lQ*G=BPoy= z10T-Rsuf!tt#~_9_MK~SD=C^&;9S#D3O?h}8nN?LVLCfI?G5_td-SY^+oYGKxzzm> zEG~_+J6f#O-5z&jv*r`9)FbIgRPs}@#uUnWKc7=VqQg~vJ%pnf?_!`qy9IFp-(ufh zOvS1JpeZ{&Y{NJTO)tn~j5XHU#WcSCkmF3eD>$D$yDDw^4ewafr0j7y7@p0|-&A^RHm8|% z+M8i7)@W?Ueq*>7AAgR(nosb4(|c)~f*hq(^}4f>>x)kXYH~GCsit03k8I$m zhtQhD+whsB+byp%y^rP}=q0sQW{_}Pus3{t^~E7@w^H&EICzilt}5ugX}r?WX|2b_9O5@F5ihN@pFeL%m% zhxx=v893KFN1v@9n2q+Zts1#I6S?sii2a6$hV9 z{82#^dYJH1$VkXZE{&dXn%bL3u{yKmNK&5Dw6huJ3PiB2mS&l153qC<7!(9^<__pL zi%XP6Fnis8ay~xBx?p!r#{WC1W}#?9m1Md(xyLN+uWrhqG?3?k0o&Bi$g>y@H6?$j z@Z9FtEB9&4V+ZsqllyUgzDT$2nvm}`U+FmW>C1*^$ntSQ8(ncI_>DE{wxrbCN=`}Cx~lL=qRIKyU!tCtt7UmjI?#U=BGWW3$E{I8 z{O%lge7DGT@3xUq!{J$jpJ4)}@@E%{#2+soR5*6hh3B#|E!k5hu3pUHmvh*tx+>4~ zrKs!nZ0X%G=3M%>QD;2qxmBnIB7AKUznvba^69vlr@lG+8rUtAQfK=h=qtd)y?x z*S~dD@L0+!oAu1IQ)XWpHZ(r&A z;LB8Vko-x`vd>&J*m$&hBaNu#9@FZB+nP9}*$j@R#9fP%I872puN!uG!8gj7wZDkeuZi0YKvaS=IgUn!aZ+2S36a7~+Mg-;L^2b;L7C&9c z3B-j|_7=bL>y-yCWH`8xw@Uh^`T!1w1R8Xiqv_;RBYdtaZ_3Kuv3n(lb2UBef){SF zZxSo5xAi$2fe*Kp1>;(cXz&>asGSRYC@`b>E6bP0Ycp+?AG>`_Y<$h!2U=$n&rE%? z_pBG2;%R)AX-p4e=k`*J%n7@<_2H4)I(wu)ChjrF-cqj{icQtMqkbaVGL3bFJ5fSR zvez<*=1`i7ru!Qrx=8K7>gK2lGOl!`TYGtY>9*}?R;lCFl=OK5r&0va*vzJ4Y6<$f zUWzj%-!yL*5c0q6-8EmWdSP_9yk+E@n`4CTi&aE}q^T6yEf$~JFy4Kbrj+scIJI)h z$KJr<%Q-(P<`-Aj4HtLj>NFLTSp93b&@qBGE~(m+rB#@-EQ!h5s1W4$_S9}|nj+qa zwJUBROQ40zS*5N#`xhdF&Gkg8cklR471BP<$!B^K?(|8_zX1%ua>x~aVa{0zRvORn z*TVx_?N10Xe~qhd$petPx1vYxK{}l029l|GE&J*8uUGEtFHdX`JRIJI^F+gNbfGuE z^HxE_ZY2NP^I3!M3dO!(RuPJ6bp1$8{hX#FahJR_x78P7@~y&$Ss7At+mD>N4f%~n zJBy7p%ss|MvGN7k*(|m{ow6_RQ=AP8on_OE?X^`sD>X6YTf?AXtcU2ch7cysrl%#> z7`t6%278h^a(U^J6yADWF_UF9Lbo+9woUg{4&pX(JF)bEdd;q9SMsyMImRSardozX zF-Eq{od>odms@tM_F`LvKk9Xr_V*XA?yTC0Fg6sl)}Wbg``&*ySk2wB=l4|UO3WqI zFPUQTejQ5v?IT>K9vKdcpW0I$$1g}G=*#Hz*+0+ta4^*KxAQSi5bzN~z;}!7L=$kZj2E+>y76Fp@XaZCru@_=6{q1$5aU)i6Qy%f#^qd< zO^(%+UZhVQJQxj>(s=qqZ#E_bt$x4aVUT;{Kw7DPJTjSNS9@Qu<1yg){><1rPasBS zht8WY!zIJTfi&)#DgJYHn-WWfkea*NCum_rg(xpE868^sbPvw)g}b)u(pkr%^oxXu zh3akR(mbLWRlnG)o_{c_)xHrID)@a*^_*WI6 z6KB@!Ksep;hSDf^YWbsJGVhCX4Ox@p1QrxQ%IzQ6Ostad4}atMVe0taOL71YH$~ua zN_FV%g%^lrCo1ZxTqAe<9M(>;WHUR3C%^ZdNWP$vG3GOAEoVoVGm;qgScIUl)u>JJ zTD$J|l%g$ONbJBoU{X#L-L!voptoSJ7`1Px-t=aN0us@tsJ!3{A>{AQKK_mqBjs;b zX8&)`?t;O+mHs`3<-o?Exq<5Wq=Rt{)sgb+<%f8wF1Jn9Piw5a>Qyn|x7_1-BFqTxkVGAMy5j|;RiKP(CVXI)6{hapcST^OS zVO0oiy98;xW)pi&duFM3+J$PM|5sEf@S58IgMu^no~`V4<@LL%{l52`BkBgT61-U^ zcFb4D&mr2Q%7;8pwMR^@)=$Gsu=<6EuW!e(dW&Xq$=_H~4OubiS@fSw>ZA>?%vVz? z@Mqm?>qEZ1zK+4k9_S zvkl)q8)ZCm&qU?Fr9X5y*ZLn<3h%h_YXcCrFycYRGUuHC=|Li0QbM2W+5W?Wyosbn zU+W&ZWCneRh)ht6e|omD)_C^}dE@&R^EaSWoK@97OQ_!MX{k938OmBJ%EJ4p?LtQg zgl(P*t0G2>`r*2X^^;CsN}14HRMqA}`*7S(EP#J_V%W&LKMSDxD`C5iI2!)1vHmQ) zgEwI5cX}VCY1VlGMo%DtW|bE|96EKmEW=H;?L^}Gn&N)uT`G07l2qB5-b>`E`kHD0 zojZ1NH`Cp*gl4T?=k!=o1vlZpO40jIDnq&^^vkJG<2DErT0xm|VfoR?f2Zz$7BYh& zMMHkCXAXuR!Ck}2vxMU^{E!2iRZeH%Kd0GMTpp_L*J;P73ekKg|fswg2;>9)*! zI`0QZMR#W)@c%HUb2-KTuEFa;;aa-fr(4IYvmaUueZ0zF`-=K(>k;RoEdkR}Ix?LW zty5Drl5D@S<`5~o;BA{3R4Wfck2g@kx&F@_`QL=uui%W(S;J|#8vvb3rdnv{1xMMF zBpLL^3j(ma<;)*iP0$dRkz)X%+t0FmKPM0;4`pWhNY1g@Vj@3gD|{_JJYeem;l z|G2yt2Pl#CGSK^{F*pIn;N2M@$Xf`-%c>X<&WD3i){cE`gw*%%!Ob5-V-F) ztZhD-#3O z+urUZ{MXw4BK31Y`1(isD?w2N`hcGDzMlv(V@dBkbgI}_Twf;fcNQ%Hr$YEv*6og; zZ7nCSz;0f_W&4l3F4_xZE40yxEnx9SU&;J~qJPP%J5=e00;0mg4!*>W03Lmw{`h%} z0+eeJzN-eX0g4>cQH-z;5_mtU^;2L_WZ(onXk~*|hx6YGANx2$Zk{K9W#7I+lb$Q# zu`~%Zy$Ne4YLS-4 zJs|mWX&{&J@}oX2N_T?=|U|M{;@KZZnvU+_Me*t@wdj^m8Ft z+>H!@iyhF~bCLD7|Ic0hdpTFAVSmYjeBU1QgE=X}8RVnq)i{2@DNg-9aQtwUD;`Y* zp9>Ch5P1$9qDy2&F1HXc>&z&)L;^yJYI7)%bQwgPC}W0^-I_i1>7 z_wZ&Ki9T)|N&vC-;L}6zl%t5~@L*#!hN3?X7bi#d-{~Rdtpe_rtQRYx$7q%ZfJH{& z;Am5r+9~UPe(9eZ@;8@|FoATx6yRWAy%xOd{IsG($^>;QG`vsT_r)KaMpZ~X`Hn1W z386LUpeT+b{cM-^4jbD=)CzD;!ZS&;z4%y9NB^<7NAd=^5KU+J6w$55M0wEOWLK}2 zsGN?(kKX&v<hS6=fca8 zhR}iGu}0QTe_Hvg2)2JPr?sSE-Yg5Xbc|;&q$?$1Vz|nWE=bVY5ff%wJrGEbv3w58 z8SF2Lt2147Cd&^YE$IziW*a@q^`YGK2q;RHKaE^{YpKfj5As=tA;GsyN)oCA8KVJv zt@Pb9KsdeJRcN1=(l~2V2*WSpak5{o!AbJZwyp#mBuszdg*%8RaN%cjM5LY0!3eAze78LY29zTQ5JRm{3q_E*%AfRrCP{dV2%w|L*r8f47RRf znXJyp>j2ULt!iPf$~YI}qpyc12a1a13n2M|9Gv;|tSuWLsWMnhN$Yf{#}?Fgyn8J@ z)&7MB&g@yr=t^k8u9;{Gj|7GeyFxfGbe9Es(bD9^0vY((%_knwPmB0olTn8`K~3eZ zR1FP)K#Vf9xwIR_I$$RSN|OwdxodJSSN;~9fBdaX0mLk!%CHXfhM^my)_(sM$rZHV zT>(?xi^iu=uQZO?7|K?Jm2(93>Fg#W48b6*E{<(%<2VLQ3&%vStK?rCHeduWN5)iF z_ePw`bcY?A*oP@$4PgMemzfTOREQo`KdqXx%x?pUbfU6P=P(g74uiW}#o z*fRg>j{3(6Nw8x;PxC|?ny`VDyBOTEuT-$l9F8t}Pvmi53+0JAVdH$_#{B$kL=suo z4cHIN;cqDA1m7Gi(xPW#f`VDM)f15UYGS3L)nQbt>6pSbxkTF0YF}@FEUo!o>daqC zKGy>0JCNC~&fI)_VQmG}z(*#--cEa8=_D0p0v3HL*a%;Mn+-BE8Yt`wuLUH55deEx zes}@k?kx^sC2Mm50;8sY{X$_sL|Z}6?gIirg7Q8n#a|ZgKocCKekDT3HUUWc=O%Y0bP#atIr#A1;kfY*5QazlL}TbH0S;3<UkkWmas$c-Z||$;c5H)m`0$CLN)sF#cJM5X8+r>H3i7 z0-XXGGH7_dZW>fp1fV7|!0eyx&Zj#skiqxW)cP{!?B)X7r}*sVF@iB;0BP*Ypiynr zN|!6~g!5X-Z^<*%79K|8`a>>PT_^{ax$`MMo)Sc5#Erc`db}0h?t97W=RkTWOKnih zMR^ifEjA+tHQM)#`*8G{)&q>hbnPZyIFVvHjoNai^9s4J&yP+PFdj{V&Ph4|F0+V0 zSO&qYCM^~LQ?ftvsc_QZhf*L28nChLVHMNR;Rl$Iw5#l@8pAkB)^J1x-?T5Mwdq!8 z`@7fLDpudo^_t#oHv{tIf7zp-t|6h(VMJy2A0k4jZxS+4lDuK~IS70a$qeYolHK!% zkgrah1QX$@EpQz%*e7fcUWDkj-9wD?v=d7oTc&@UwwkCUgW2jec?X!7T!c}Oql>)CF~@tbU|c%1$P_W&AavK7=C+6lZhzJvb-=aO}N69aI&QcMR?iWuwkE9jQEvFwcN(Jr}1JoW_J?JO@&__qNl(;MMf^hYOy;hojyK zhmo6x= zIc#T7*cJmK$b+w06c9$nQvIP}oaCpYiWvP;aXOS}q1m0dwd1Bv>|b^b`RsH97xuVR zVB8BF3MXvjL$Sc9*ZwyLPmEf@gPLB}K!kLxq$$9IQ9aFkEoC}h58=8dQxA(_ueLwb zZ=`;$-=H>bP#m1vJKe@8hdJgv;-?%savL!$sUG*$@KAosMev+nJq|akf-iYaARiuu zn+hQxE@GfO8XW5u8#pa!G&cI97gPXxo?5)Zz>6>&ua}IKBo_){&nQsQx+a@I1=Gbh zroqo{O#X{${^7t5^1xC~>!*vyrNYGjWnqKj4Ai$}%HTx(4AihWLCF@koNq-ZPbXAcOXvUoa?h|5MAWLSGZ8>YpP_VN31(y=oZbBt(mz07}fDh^7tpGv3a zfnPa-L#I%dj?5D{^vE64i>POiA&XL;KZ#cL7$w49(qAWc@QZybdn!1x zk41 z`$6X&QXa?iOrm%AQO_E>0-w%sTB7*y1OcMA(B!TfkA!w`WLTL_zabh(wNZ*xsp9pT>K}K#^Z^^*3HKVZW(`4PE)V)vbNiFFSvrHiKtR+ z?IYJwKn^5_fxbA0-rq%W8Cec&vlr`OJe2LfcL?kP8qWweN*;u+gCqTJPq?!&yUh$C98dk=cy}p4uBJqeyQvMhSxTC&Hfucm4NK@FXHEv31?Tr@A584u z4(!*Hs2J7v|624cvYP zFVgu-@)RtHi@5R+XCt4+ljnjzspiF?A>5T~5wENd&aPyZp|Wk@e`(%lN5Pdx(;+0vl-e zq#mxSKmTK{A=sX|^)l-DN{G*tF`8qDJTe^?!yC_)gG>sNC?T|mJ?r4`pZ+5@)Yl?t zv)E(N&p|)Z?`TDtZHn1AY>e?UlX8OQ@cb@q!u5}D_f;7_;*aSFUq{sY2_$|`Hgvd- ztlA+wAxQAq5PYP5y4P71FaefL6OTNYZ-7XDI;6(pkFm}mU}U>G29fnKz_Bx1 z+b}-k{pQUHTQ(of{JkF!jLB~)`VR^lXJTgX9&#Ey3v_%Qt+{Wn4mo`VLLSKqBSR_p zA4p2Vs7IU;2#I{PPs#i*)PKlHB2ffNbp0r@aU!tJMYUbc#)-me&>(%r^YzFBM^d&y z%t#W{ZSO%K_{x0A)C4inok$!aDrNQ=`EU=3LrAl6nzJY`{9!;4t_TKUEOVh-czQM4 zYyDgB6=R^OlcEHS%9KbUv9+^Syh$5MDfR=+`mfTWD0EBW*>0`N7l9B|)&pEtge4XX zC4TF<*$=Fcj8^|ql*>_)t^h~3rhm`Thb?}HJmI+AXIxa)`GgL_3x+D!Pb1q;i131r z?sX(D2H&6z9`EMr$k4BBfO^CmZ*ZS)rKCvlp0wKgZodJi23lyldp|ouptJ!Ve57Qg zuouWN8YhFZFoqMHwN(knSW-dCMaZ?q>;W3D$3y1#c=> zZ-;cvo|GT`#MO>8`+Eqk25ycP%%6p-kLdH(l`%KMR0Extc5o0E;!aSj^{p7`C3;lkX|*DW+;RkmoGnEy`0A z(ufiD(~#!1vpP>QI%qr((W;txoN6)40kuw9dBF7?sq(H!oB~)*bqKYnIhz9fz}-e+q}R+2wrwrf1CrSK&Wv>%RJiK-7#Gl z;-{90^U0__n*`u!44?4~TEKr4awlRVk}$gF!gB-BBE?E_Q~UD}5c_U2Ix-}nl2pG2 z%qVZPT(u?@r9*!*#83Wk`T_Y?oI(R)T)DX&=e0+eiFV8V>hcBJVJQTFI4b1JZh)JW zQlRQdy7lzls?vt~U~7g%rfQDeo!tB%+48ExuO3M5Y{-TA2))5W>iua31tP@lcjod* z3-C=wosM5lj^AAAP`#g|QZNGG#iPWf|)jAD0R!cHnTS7k9T&JXoSdWaWu$&{{cqH&EIcTBRV)$|PFZPefeUr(^+h;2)ELhYll@j97EJ z{nbd6-SpM%d&I8Zz_%~xvQ3Q}&Ie*Ql{7xR%&t1;m>nU=xDAbC`AuXDK4FBf^H*~4 zWy%X0i($6mr9)j^T*vVS@8g$+f}7ZI*t5fou0i`jX7Ftj@z-ZuJb@nF{*k0^tqJli zD(%UFkm}S}o9nZD!zaikUtp5~C*FSZX&JVdXuQ*&=yqyNJDXonv+1lN6VGE&m~Unx z4SiU%tHO*X6)u3qaVy-(9}a@5hQb_0g$FTf-sHWjHn-Q_DF>7*IDg0rj}Y@^I#yR52Te zoNrwxGdI@QtEbmk{Nzs#wa-+eaS9|(RA$6-nb0O>7p{if95nH9u4aNiQyp;gNo}fo zZZjYXF(#3wGwC#!)joxTkO1wDxk=pb0j1bSAIMS!ZD8PCs|2dn+L@~YCKt)380Gw6 zIe=#KHGKAV$7q40G8y+^22psjIkIjWP>rb}LFk{ZLn_#`0a=+y`>oYQI8!lb7I-#G z@SRCgsA21L`+GTaQ(a^J>BR3u%v7KoXqB?X+2J8VJ;Had^Nc9wDLlb_IOlM4J|7f^Ru#(PU5j zILKwDDzSM&*JY&Rf1akk>!$#$i zn2wdcH5bSf-8mPB-_NT@#3!<^;1@}nj?;`7Wj$;6nCyD!?ocE(l`(Tg$~ir4Z394L zk(O16b00SY)HAezO-zsh!dU=*)i9g~pDxIX&uSPf@`0(!hs$m~IpRm|11$duZE6Ax z6Jn?G;Xy-8n;FPqRsoI`9fYQqJy%V4HZ7=S69QuI0^@5yE<-cVJ+3)Xc_d@P4$5C# zt1UO&Uz?hL0AO|LFA8n7ig4Dg3%|!2pd;Ud%Deh>`~>xcCKbD*)BHI3-E3QD!V>dP_2fD3O4p4i$b8`J4ue+B6)~T9YG#5W z3TW=sNa_e2UHVp;8_8IgOI=f#^@4m;aP2Z(bwFd)-SUrK(A(=JtNmL ze3S}w2{P5Zh;qC(^_@OD)nHiI+Ez(UDwA*H!=j{rC5iLaLf^Q@!F*T36wG&ZFmtLK z^@BZz`TQQ*=}HQS&oi^+837-{2Qu0_V$m`W=MWgD`qC>_vd$^X)}>L5%u6)d;>t2Os;wDCXke7aM-rm0Id*L*u&SxmsT*Smm7J=ipP*Y)VLXae(dtaL*0irWWq>=b6LnG%p@*y9I z+~Nyzkg^kev+v@!ad__o7X8~-gewr>GNgcak_2LywNZ}5dBfjM7s(lalQ@2~q?X+tl$-<_i_FrrD4r$(t z{-9a7mW{_yYoQckE{#WHZn{}K(pYrD8>bPt+ACF7|qkz!Dx8TJL8rZINr1B zkJ0v_Gv0kKSy8~kNSi*df$xG+SHX%@;LUA3DW&N)oyMNkN~XCA<51Okj27AuMkS_3 zM!Qvi$+d-^K2|{v@G`6!yIsytb)`i^?uep$v0Xr~3=gXyf5mUSl0`Ok>{H_A0lX>cu!7|;;0c0DPc)rt)s@_Sr ztsC&jT!?1FM_Mc*%BRC;WV6k`pP6h=h>$m2tj4xZl~lJ<-*IKr44uhYtojHr66wIm{RAb7l4t-lGn^2QBiVY`{NKG6XfH9{&C!f%87S8^k`|6{q>YXZ$)R$`HGE zU(@lDTh#O$ei#9yNF(a=hvBtKe`0!EJ#)WKG&v~n(144_;@ole?p)!PVNrDMcY^l< zf@+BWp1;;_8`TS)NA~WOLu(|4gphf^iX6d?{}}d`PO}Cwdt@N-hLPJ1nFc-W?;O17 zRd35fJ=y21eD9hFL`N@Ao*RKYGDCR9#2ab^NZOtN%X@jQPZ83$SHT(ZLtt)u2CRm$ ztVew-d72S`ZvpM5OBOE!!5MARYiv?Zr!zOl4}ml$4GcDg24)r7!PEs(HcE>bwcsP7 zK*N4hfP_k0q{#<8;zIO1j(*DqJXk1QKLnr7JKl9iB}+V)_tODn{~u{@9u9RIz7Myk zP}WGvzDp6=W#459F_u9@60#@5P_mA)%f7FL5wdTUeaXJBk$ufFh8e@}9(um-_jupm zfA2qz=X2CE)iL+yzRv4B&+EK!k_u)-nLJ6&MJa%8{ ztNjqMLmI!_0ftDUC`?ixOiaD!*))%k`U6qh^zv7~wbrS;r4=!Bl{2o{)PXnFlbEVp zxcz(R(0o@rtIldXtiM**@_RG!mluvX-60N@N?O6!N|y6D#8(lTyBzz`MbgHLwAtC= zd&yt>3u-)e18Q~9vJXxSKCAVYk61o%fFG{)Us}OAdt9%_JHWF{wmv_P@^oBSN#Ij! zjKvz&#Eo1JM8gOm$ZDmSTNC-kW1sFRGpvYvMYO62Fi3dbQ(vFash_DcydycHv|2wZ z^qT_WeK33Fvr*X>v2pyZ49CKT`^0|%?(^3&qjgUTH9dWEf@@PasHMgs=Nn$8J)8aM z?|gLNm<33Ez(GPyT`sNqDpT%pGrNZHV)#V5vAUT*F^b+X2;84+LCdy#Bo`SMz zaSq53ZhudvG%W!Vm#IkVmggCyKze8BE&G;nYaqGze>JxpeK}d_qyyrLY@1i^q%V6q8vEDQjh#YG|{-DIgEhr+k}mQ%`n5ngC%TK^*W1{O?4|L$1l zH?eGm@AUJ}6>?!;Je~i@5tfJ|G2LYC8MJ#9Tdee^9gG`aOT}eFachyTub^Fa zTLO0PCLxu6w$agymMXbz=f1~Xn)ubmfg9HsWUU2vjLvGqU}yA6GxghB^Ta=w=ZlWd zhVm3I8|5GmetbBZWq__oIF-#;i|o+;dIvR|e9v=FJnU0NtJ{M_SJ(JWR+^9i$c5x; z5x#pgJ*iRu?sx(!DA@XvL?i=%vyz}-1~VZ&f78e}r$0)4PeZWLpfisbcl#2}rTr7` zCbLU>uJ3Cw_6ca@;stGVWHJlaC#!VvtNmVsKC^nP2WL>fcpXR`wgl1Je_A`6g0KJL zb`t~)>qF(XKBkaONbMUZK(hpM1N_d-4M8Tw7wH!!e7MPv@8caH#>yFe+(5JdByTbAI#b*-0p-vu$e{n}mqyOFNm7u!%=1bMD3_BBHP5YZI4GqRR*_yZg zn;?$O#Los_93?z)807Desp7*Q18JnK>)nHstg zYa>6d`bBWXvVbQmlBL-`!^dx{FXID|*S8fhLt?5s&p)~4me=L9J;x>OaBdqyb}dPX7l-vCZ{*A zujU!(POHhz`=1!B|A*WL|D_6s^V!=Izq?;5xH96#^MhI|Gb~m;DrPJ60Z%UcO`FS~ z6F!eC5Tt3<-Q?TULdJ#K3SJwcHcxrPzOM0j9BdQss5QjDj=+y6nes_meG-bsSKx&Z zp{vmJP}=@4Yl*L`F6m<0bKJH%K|QSr)t)9z{ov&pV%Zh>E83X7p zv%}o|~(#g*PVpbAM&gzQjq7^vJ@{5^>_jFH?FBt7Bd1 z@!uWz=?O4Ju>+!@65xwHseGV@qCCBPZWsJktoJ&c2TLM$A$QYIv`;@nt4fxa`S5!M zuHC%(U+fP6z zP%F2KK&N4alY3I78w?d4n~ejk2rkgzd!Tn_-0Y{kuACugd!mhFZBKh3W$@rNp1_0W z?d1&5zK+NB5>>)@O!fbvLFd5K5BsZE!Sysi3~1V9MXEzfdW0-gzi{D2KcnAn5XXfC zp6@y}W4Cp(y(hS^%ZPXM`R%~LKOb*&FH1JRP1~fv7uGd)SsYCUBU4)q4yVD6d~ zTyYVR)GrMc=fMO>dlv;go>1VZEm^E?}N!hs99l7ayFRN@qW)E zZgf_ng!*iD=G%1u)pav$EB`xyGg)Ab*Yg2jpd6?aHqfpxZVPej2>z zz|S8%7zjR~!D|YBwP(yPvrmZgyZ>vQ``=cxBMC;=kFN#N&0KWTw*2%WdA2TU6Jv>y zA6BP({iG2wg?9>Tw1P@{Vj6hWVq2PjVPdD>=&D8M&uX{OYAC+v!jJDyDAoU=Rb}5j zQ7S$FfaG*!EHTtIgE?+zC_yE&9Jld|ezZ=N1Lf0x7n^^gjQozDN?`{XLjqo}=T$h0 zZ#gL{`{Q#Oy68tV>c!%}H*&{LLTi%>AseSL7jZ>ht0|M*vV0 zv~>&YhrwP37b~zh<@q+a4PdDeon6h#bH&oR58-3V&yy=0T7??ZeZ}NrXg_?60%<(e* zjbB}gcrOey9(L|%bX~$fm@}VMFN4V(=&4wQ|MuR!^-kLWFL3tLAHL2YwB7FqwM(!j z`2kot)a7Cd{LRjCAdukfqX{d)(r>56r~AWK|0y8+`g1=IXR?)d+pcW5J&B+hC)*oc zYMQMRD6xf?BDHJI>(sxrjI(I2EiQSza;?<+EvVr&(DPL2l>zEftFg%l?+S;BN}Xwj zk)i2@X)FVnx|RAGTNuwTdAk$jml%c2(ci2(SQ1+~wy@;-pWdcK++}0ctUpr`(9d1h zsb4|P-u2_P-G6{K z^#?UtrU@)CVJ-qoC|m=nNXo%VwN^hKSJxQOUBaSMMI2J_cVEFo@w325u<(sxqK*c* z2JMs{d4OdcR)DRYPQ){OV1tYizK9hVmbOeIStIcat?dS4`28){>Fb{Ra#!|)Y;?og zH^s$MwH~E_spvx%hThc$h^MrGLtHKt`wbLT(1?T@qJZZ{Ek`|mQse<(t0O)_&Jc z6Ks6W2K)b<#7QNXfpUZ~Z>FH2SPTcIzw*>zwKf;WyZ>%km4~4D2)6EwxJdr@6tqx- zB(>A#bhqo)OOXv0>X(8KSYOiNykpfFj}b)hWhFF-uL|hLF6=x{47Kb}?{RI9I$^F_ zAr`Qks+3#QTDJ;?9JRmXu#3EFC{St#$EO8Q_U%g#XDoZ<3%;=CGk#zj+Z|24+hPzR z0A?Di{&Xh+r}16HvqTeh=(_SCI6|rGJgS}xuj+M`1&acftNp;r$Qx9E3L0G zsHgF|zC3t)^$}*RXc0#R*J20u<48^|hTYkh!&vPfE)X`T2Cx}&`ruHIB+@wGlq{AS z!+?MEC;VRXTIpOywpbu?iqc0VdtF`ToIC+5Bb^t!ZdEzX`dW5=(g5Z_X3$dYV~?}9 zSQSn3XEK+1Qbk&aU6%RrTNwM$ugX3?OQ0-$#h)xOTCt0xUcW~*j;e89Kpa-#-O_!B zcMVHafIs(FPpAw=h=hB&hYptuvKN(ogqKSRij!SWfPW`$>BfZ>#y#0R00-k!HH@S{ z|L+VM^G>jo^2UU2tASLx+rSLXo^|%|Kx#5ZHUlGDVAL?-&>-2e+x$evr^PlA2S(vC zq|H#HcpK)Sc~LbEY&*Dt6Jto^hGX^?y@P&9!ANmT2DtlAl2d$+J@88Dx4>SwX39ew z3^keziYkkm&V>DXrw)@QaAs6=ziTfIPf^)glgaTr@^+Lq^TIwwKU=A-$@G5v)w`$F zt>5+-fG+hKu)n2!@;wF{cuYq)Zykj8cxEhugF}5pM(Uq5piyW_d%i6)rQxlM@Xbd) zWJ9W{5&abJ;{LtT-){jfFR4$H+4vEj7XZrhZiBUzdQr;`Ha@Bk%pgL({Z6r8v%-8O z-Iw~@pH(W)TV3*WwsoQGyvxyLiGOX{11uWmQZ=cY_7#5Db}=_|ru{E3sw?BNC78tS zAGd5chHoI$Z%rzZZcIEc>I@glhufKq{fjm6^n||WfxQK(AK`~lu*7FEMZ*fTWg8BR zBO_hK?`Jxe8F~^mB%zrduCLY)Q?*DG3B*w=1Il1KmKB1GR{5_T0{K`S?%!olw2TTV)`bY;)=WCd}!PEBcGl$a`=r`WxoOjXv8TVAVjd<%zt32`H#U|`6DFHCqwM$iBzE%w^2|-efv$lXGj~<(WZT^JZvP}-S z@p^O8=6I#&D{vntlD{vT8d(E?#S&1}^}_5gPAFrmH)a~lKJl8|!H_qgv!ovkq544o zHXx6~JPa%!15`~Nnf<2_Y90=}DH7P8#wLX3L6Ql%r&#YT$xq%~_I4b&b3;IYPcV5o zn8yr0c(#foEO8v>wrvCOlsZS91Q>WqA?xg zNppubiaBOHX)XiKe8(f2<8Y5CSbM#L@TgGRC)O~*SJIDxCEB1nMe5aJr=E?S!w7I~ zhjwJFTzn|UT7BHE*7Im;aSFs1e5hV_fq8nlkL@24_l38&RfyCa@5Vn=t4b7cP>H2s z5P25|a$ER7qjn?v1F|>5bogE>`)&QY4GM$yh}^V6n_)$KxMdhFlyRIjvs&o>8b$-7 z;ymq=+Pcpe=Ecc69dtydKSPwy2ARI{u-w>fB<3vn>G(wVv(>*0CFYpbve#nD0j92X z_*|^Ljfs)tu!wq!w3F@OKAp*n958i~^kpAmR21uEE}7JUOtt>K+X&&|ZWbFU=(E zNylSa8&(E#J^cg)zik8Sy$T>;fB8Hv*ug0KCkU}Brxp(@qA|09jA{`q5vm>{;2HUUxS<0l5U;@KPKg1E_IiDOljF_F9>d7BI+4<(iWO3&jSGmQ1AE zEr11T|2YmE_Ph+!Kr+Viku`OIlz+VtnofO@?n79NY8pBi|PRIAi6`b;J zGE8lNmiMiFm&b7GT*CWl&+}^&p2{d~c(G)~^9F|RCg>13aeIGdl+s5c6!+>lw$PBh zcTn6{!DPS+TPXchK^Ut^k9NU+>q?{QESeGL^;pvx3P#YM_I_w4?_C`1h~;DKJ(T|( zE+un>@1}kDO=6|qK-1+efw&>pZ|852=z$3J`{Ru(*lf&ksyC&F9S6OlEaT1|>RdV2 zDM85gc|+iGy%hi2qBjF)4=9D`VF|a@X!?Kwvb?)c(bd497Tj`rO>Thx^R75^hUw6i zJ9qF_|Q@Kg>B+EQWU5B!Kn zoD6azJu`Xe+#+yzXxzemg}|j+Jo{(l_ioXVOw)-uH$8J<`aumpR+evirw&fk@)k@v zj6rCYyRKm}R|aVNI8X>vv0kU8&~SHpM%sx4xr|0${$`P|chT)4Zi=;-{EuI9P;WJ;(w#i=Rd z8Q`xTB2VBN-N#;X0@iLM+6m01W#TO(w?&TmvFutYa+{R{LVngAgZ!IO2A6n`Jv6&h zv3dei5henb9kjVx&s3JdEr*K1XV>_-l5S2dMnJWKTx={%rQ4NHzJBph4|`>xbuV9D zS_z^QNIt~OiqgH|n%Ci&;`+uN_l?z-(9q{}-|00*smUV>ys9{>-crU&?tq79(^uxt zoi)a6??B^BndYK)T3rah(841}9nw=9e>edi!260-mH@44H@I8BRR7Bp)xGmJB)QN! z*ifx|b}H|yk4oueAHaP*R;y*TQ3{60cH8%>CxOknJyE2nY~@q^>Xm52E$$frHT%J; zxwqX#WQQxBs?$;4_s79=*Gsqx3Xo!*HG#DI6og;v@J>DvZu$a;EMb{ ztB&lQ+0>r=MgiHCL5f$dS&5_SwB!8|=C1u@BLiRoC0Y+PFoZfkho5b+8^EB!t~}PK zQKN1TI7#2}dHFQgQJ~HrfJIxabTZ2EZ$NYP$NTI3aTkEj_j_L94AW;uKtfQ%;jPRb zEs*}+MOwDJdlB=y3JZR^bl~2Lqm~e%;yIc5Nsb>a)9fv9M=+qzC`vyafcFPZq0-%b zbX|W?lhC|aZb|sIDGm!hW=JQis|owrU;M1~h4P2u(AndJaJPZqUDvf9nq$OC!e01% z&U|B_YsauWvNm8XRPZ%0z}HnU1Q6N@jW#|7Kc3|K^wld$6Y6tLK~g^wS@xGho+abW zBkjPz%~TUFATBThW>>i&)M*pZG5ez;@s(0(+$p#wZ&88{sLVaPjMBU98XZM_VDw z<;F|s&BQ{~u&p|n((#rySlUL>rb>;DZ0{ZSI!2&ypRML7p?!VMdsWN;AjKUY>22Y? z_c9^JcY9TRt_+@os_WXVE_yRA_i*9FGFcl|w&-E0E3a+)`z-fpr#R6wonR{?D@a{; zo#{GVm;w5Kf^@tFVMkRMiBeWd=B&&hR zkU!By7GW4Iaju+r#jEC2qsGRRa-QC_T!35k*RgfRkL1W4;Ac7n(u2u*D%&(o9QDzY%a|(+A?jzm5)f ze;JWvEX!x9*I#mNN(Q^r6Fy?nD!s?+FY8P)UUr|+m5wwrbJ4O1Gd^>7KJ z-~)JS6LoFK!(F<*!K*^x2lWhZU*XWPn*8(OELIobg>EzwEP^1R{H&A)%!FG4n`9qH zip*yG9!!$`1smex`p8|Ay@T>Li+b)GMukSFS-JN2CI(2j?opFCtx%abDk{hJmx$Y> zY58ncX#rwR=*MjneBy*E*vC96(kjZBS`9dEyxGvQJ3aH^@(Z%*C@kS4K-`iX#9zfp zI)LelMaP%JK=BGoZLOA-M}Q5q!d8Mz;xBrc$lObr6Zke+OXoXddp`72YdD2zY|fJS z$cBOWZqEIs^9wS@t zFwSgUSreTu+TM6y7na+(IU_y151MT-i)SA8JJ);@5J$#=4~nLx<-wk~XZ0hJ#2)vW zz!0hf7JHZ#hyy#z=fNIb0qeg*-n7E0_sxCBkm=MUMgOIC$T*%3RqQjL+K-cXKr8CqX2MG2_r352o=L7457|9sC<$`G z-FoJb2BkhI89c4ma-HU)ktdzrtwNO_9ZPZi1UpH|(*NbfL+6QE|L-(sA+YW-D5RapKBa znJMOerlye=`if?uZLijPg_1=dX07oTcm+gKN9_yUi^9BrCtMELW9Uc_%0FE4hN4S0 zca3P^z53EZ(6tP*S8MqVs>(@yE>mDv=rQ8Jq=?Mm#Tx}qcu)<1;UJFUdBeh#$THyt zJPI(>1NOiN2?*>yFtxUW;$W>`<}fq;tpfWxc?7GaOxmw1{myJ=?W*N?iB$;h^8y&s z`2%dJWQh$HQ*o8E->xq-Qd(a1m@W-_D;Kzk&Jw zWiywIV-2mR9yMJGv#W#bExq-YUaiYPTUlso9v}B_(cr)jTU0;Rbb>o)1=JM&;c-oj zq`jf%RzBJ2TGqS!^vv>{Bxr+7DI3u)GwgB?HT{qZ=h{L2CiN83zDSzKwI8d-StpFE z%9;S_q6@nW{cu;RD|K2ZI*dyrmUhoz6J3IGF#sl}QZwv1oR||J1+cZTZa&Et*iuzs zx4V|?npJ=&WIeX{HkmJ?W!{xP0&6MP=V&+g-0Z;$)sn`f=}ZUU6TnDT{?$gdQNfO_qTBYEu- z{h=QZZ)Iy7T*I{jFJ*#a?X zJVLJ7?)j~&B{?K=-QLRc zXlA{NrQQ#%2Cm+z2kbB3(i6&@yFmW-9D$DVxmmT3*a?aERoZ%vn5I`1q( zfdq3;QMLOopoZXVmQL|RjLt|BdgFy!QLsTbk^h-e;W)-#Nn0FjOE<_@@2*j!%(N^0 zB^=Q!>w#ZH8WOY(nw{1A{0q)O>IHO`H`WYJz&_D&(A#Vn5z^BOEjiOZiF}Yx*Gt{r z7;+u@V~$MBaqXd)OK0Dc2)m=(7&yIH(3%DsY_a`oSXRzg``34^6<7&vqd(*DYnI0W zx5x?@kD0JV{Px#xo4}yP)#IM#CJAPsmzcg(%=7HLr5g-U)y>r`0~rUs$&_JnbIEV? zxFYm!4t+OcT|Y@Z-s2o-kkeTX^6KEu72SS(RC^!xp1>HvHLqKR$tf!!R92(hRawsx znQo1u3#X2dU2x7Dp4k0kt43Pmd_jxy>+&r@^PhfgwK43#W>pqY*UphR3P`oo%45{EHw!V4N%*Z)Rw1@wFmmn|x=C5l#w=|HUZ27G z6zOX8A9P*H2I59`9^>q;!N%HjvZQBuO|ZdaZSrnHa)IZIhy^ z)O*|y=Lfk96-7&BDebB@z(THhEloi2L3pND{gWU^c2}j?gV}I-QdK(vs;4Rn2|WyK z!sMNIvm7wDzhmv%H5onEi;MNW#jYtL5uz-fdMd=Jt%ogH)|qCRMy0Af&=U zSk_oBu9b%iE64o_y;X+_5~@oyJkae3;XQOHfq;LG#a7JU=`K#F%`z^$$ZR%gr1f&3 z-C$2XvbtkT=2wJW8O_%rb0!`)S#4gm zsynY@_C35rL&og;TI((U`&(e5to(}n7IQe|1t-r>k+1ql&R$I!ZGx}XinAF4r=Ok` zeQy75_I3G+(?2SW>w;m0wGA~g&+M3FH6a{`U!vwJ%u(0Ha~Hob5<~W0A4_ajvcevD zAzdPqPHG+5SCR^dAdxNPi>h%MGYkuKseB7C&)3UriEBp(i9gt3f4k?a>ocxUCHxHH z-hMq4z@pq1KRwe(`OEI>kIO>Sro75qw)YJnU%=!OfJG;=nEMAIuqx5a$ZJi&}K3?oFT#ATPn zA$^R%#H=!a3%4!Ay2!VBPl@2gxjJA^s~2^u0XEYy5cp}7sjmIa>&XQ%&sIe~b1R~> zy`(k>CxbSNYbe=-+IZ~y@)Txs*SLU(iIGT(MG{7-hm^m8x2<*9;28;oZo2f)kEVLb zOOTGplgfnM?2ZCmWKX-aoiR&ZAOoz;irRVO?_?HE4)e*?RL7L-cQkxo+c$FcMq;jt z^lTl)@keclG3EZsr5rU%ZPfzp7do!lw#cd$J-(KfWxrmI)~&DulzjSpPU5RomLP80 zPXro>r@2e9ehAgiqn_-wBD+KoTA1|w8bsdSv5omdcm~&_jfo0(IrgE?VUCJ(=2J9k zsy&)G+(Wz3`!rQbZqAC&bqwK{MON{Zs=Lh3pyYH)?;owEB6%j5n4=^8+%VV#g3}X9 z{#FJ?!~|8{ntYS|TJOjpS>`xy@UN7`s8Z<^X1__wE5-W1_x(!2J zG@_#FlN=Y=^0F$hkI2dE%cOashH%u7iRBHHwgbDv(3hC@bUjU&GusoL31><@qt|0r zwqm#{Uva696$3tH`8sy9?mUDkQ|o@Ed}UJ3!sF^LfzDB|ur?{KS^4RhMaaGN-|xwg z{1+5UwApCbouXZk2g~*|lwm=ok&B9RR42EWfI^)7K4-D?=62-xPWMcLuA&Q3lmbNp zIx9=%vZUw!ha|@#X@0^PItKCWMNh2Pa#B1WrA#fO$ds$@{Cbhdb|C4l`4kv)9^Zzf zxJUC^&$w#&{iMcuR}E-NRAJR99^`-^7NGM&M-?lI@@O%2y68~4-|XHywrUZ#seb(6 zR5ZE;G%ATfSa_Q3&+~g z+vUz1htX^t1Gvvz!k8LP_2y}w*}8bG%Kly~vsnYJ#rwwMKzQ`Izj>LV&OWKIPbdmx zlcJ9KAa$Y3*kc^7Aev7=7mmH5G>>z?(ahF-ED=q`E!LL-aO(;Trxy!2e|tQ-&Hx2=bNt7 zt55v3UjRW`NP3qp{)nir9ScytCGpg5N%SP`&-llcEK8nZvz*y@p8hf^-7*3}ai6U$ zyVhB3m{Y7iK(n{HT$CFl6Gyc&3j{-VL+5f**32e)(Rp-fLc&ac>vY#Q`}2VDwd%t1 z@t4(J(QMIiz<2063A$Lc&D|5L;-U5Tdu2|X-1r9#=Yp!j$^+zx%RbvKn+^=;{;kS?7*q81 zCoxjgH58(BbF4|v;E37$qk^m&<)qCu)9B1v%)>-iQ)JdY$j~ZRvAb?fDP@fweMU9` zy20y-#HS!ZzPC^1j!Cv{oTizl#S*!euaGM>V3 zkv1u{K?eIWa`9O-^TJsU;t*SXi+k+9s+zhno~>6e#hAs8idsUuBs0rZPme4BIfA_B z{dhihCgwL2HsOyu<;Cboc|RWRc*5%Q=Lxeu$xNnXmuI?OJ9|6A0|96uqeGlx7N!RP z&&0>y5hp%9@g9VhQz`8>S|{_C1wNF7ZN&||vMLO0?Duqh_Fh|W5#DxyI(*$6FUv11 z?G_gz;H)<_`AIF7)D4UwdJsQfJ$=LGR9bt+H^0vh82^d^eu{pr?am+L6i3Hl{OmQJR8 z1A`w686T9}dPeFR3(^i8gmR$K53de`&cvFMjVrieIJ8O1oOouBp@GpWi*6`(pm1~h zzN%eL8;@G`R&&GPXS$y;&QoSaJ(NXU>{E#y5QR(tGgN9eHx3X<;Hv)OcUevKC-u|_ zWd&C|w>T9MH48MtrEF8|SA(DNHkJ6sR^^{ij)*AyWsOd4vf8FXQY$h>JZ4FebNbNW zqsQqQ8Wxi4b-N@O%)KJ$dQi10w1Gu4y@f1(E2)vg<$i=x$@&vQY;G-lf2?)P+RQZ7p4*QHKgy-efO+)7O_Ov_R9RsV~dbIs{Lya(N+64&0y)o!jA_ZMRr~9&~~kA>a#89Nf^M9(hAoPhQheFH48w zu;?PaTuOmCvszZD>QVs<_)*)-Lny-}31H4$Ny$vkv-AG$CxAJ`)`}XZh+Ksa+}NZ}1n`Jnq}}Il-EF?zD57glwazh#`VVurs>3 z=IOB%L{?{yaUqa&+TngZfC&y?PV#QZHvbVX~SB^eB=ytS7|0~YC zt0TTjbBB~j(PlNaNvXOO^)UL4+J(>eSEeCV8Ta4FY8oKnMAXgFkUQTTs=8x)HEbu9 zbAm#&${67DX>V?qtQ{&u8#N-%WjAO_^7(YjPQKCw9F4 zj*1`Hj{a(UHyN@(8K)FmOA^7W`{Lg03wbO@@h6si)%CR~V2Dc`o3ZXdqZ^8@&jj@O zMk;*S#?Z;1F)&QMt_0*rL3(U0M5!{8hRpw?I%p$VFD^L)Fuh1N`;FL_l!JQ5mwoWGdG!{23F%Tiqt6(vdFC@Ue-N7NpCCSfu>{P$E5uj{T~}AyWIr z?c;=PLOvMJ5gs$rHKGJM91GjddjR&kdrbF2%>8y|NNCkGg)ZrV>5I%Z!d6BfF9XSO zGS3G^lOg*;LA>G9p`rXZbwD#4Sc5I!(R% zc$>;O_$p+cfKS=kgh!o{s-4Kxz9l)Q5tcpbU^lA<-{KP^rorkA@tRbNT0eK{cQHfD zFTXAD5@Y7+{{=d#m!MM$qeJQ~>T+eYaY7^R%b;UYiqdsU9adUE z>3$4=ZQlK*I04MPRe2^Id7!zpZmvqxzh2+3y+$EUd56aYL*aA5DVsrGAiFKq-di zR1C(Cahm`%wXHklB*mY5=S^|uzFSWcZ1uAu(fZFIS#fvN5Co#DnB9AMzLZ+^;|*{% ziA$%5Jny=x`|UxtR}TS%xkou2Uns~{jpXgV`q3M?PX|0AFDRSWmG|c{ZxX=OxwbI} zj$XcP_uI7O-o%~aZlI7Vtgq@-p3( zs*LLSJX4+t<@AG2v*Wp>E%+rdc;}d&W=cZm{lm8V#;>Zf>0Nte%_FYsv}v0;%s~9k zi5;PV!(ym?Y4UC^JorttbFe7Xd_9vq7oN=WE{IM+zF12P%fTP)lkd$NxEp}Ksu+Ea z`tSlc=NrNqt|9^U;6XteaA?>0Zm}PCAsiQg7w9WMzqNUNzHC%EsUXs+#WP$;X?9i)-`x%5X z+3FT-obIV}J$F-!?3vN2tG|HV5U_5%8{^*C4QpeEy0VELly-rrf~^%G&V&~ALe~(B ztjs|QYHB{%iJ2t&7)K|d`~=6Ed2WWF|KSnL{;5suGiA}kgWvCwP>Kul z2Wz?}6cD*963MJrq=~K{O=QM!Ucuz1z|?BG-x+6INv)Nc6KOd7m<+IxRL!uyHrtUy z2ff7WZ_mOc%k`5j1mvt&>0gij*yA|#Qg!(44RmvpnBKnP#}V>yR=H*0^k;>ON}(SP zBNwXyLwxvU{vHLLI5E9ubbzYi>ta5kH*L z!?sEvo5Q-f{DsG?sJ1C~#MTN*5eOx{m$PbIS+;F6V*bSV<4NcPw|C^yzh^;fGL#i0 zvn{;mz75*3r4m$E54F=HmL@zaGqh{bth1-#E2LPgqmva2Oi_~?t?yV*9Aofp8FZI@ODU>lEo^Tcv-xdY z?g1bff(Mcz&(eDS{V(gcY|o1Qop#cF~4>vPglAKy@fpJ-hk=M&vV-8~>UM9h+IdRT9qwb~les{CnR_6BSsn=-00Y!HGlkuzI zCURJ2!}Pk@YBcpMqGe;Ga3Am5=h-X7eM#lx?*GW5Z9~G->`$4#>)k*8>6}u;`RLj| z-y{g7b_#QQ0iL}79e7ZwlMXvyJtKs$8AX#kzN0{}^QdR_IQqAIANM>#&Nsl}4k5b8 z9TX#isG1d2Lw`FHYN@5mSF*5tbC{9(Y!pp{VDyTP&ifja{Mhktp!CrXQqkXUS-Lfm zMs_o7agkgKYr3iw_d>;2xo9Ns9TGP$1la0ZtNDFda^UCoQUdnzAFA-J3DBR7010xD z7Z|)-6EiBP5{RjhZ8=iAT@N&msygoE&wDKf^u3Q{iKQMda-?4fsnkJYWpM>XB&3&E z{X<7a9>GE}0u3b1oVJB!pRQ{hmN@3C$iZISIm~m+UQQ%%qHN(_Fvj9Nl^P%?V`uX^M$z&3x%;1ql;j~~8R?#9H>u3eEN@0LGAnt3073H}7iJ}&dV;Ry`OvRwi zqr(E217GFu3pCA4NkzR84|qX&q++Bg^V9ZVdkR9NtJX#UjJE^0r(^;$e^G3|RkyPK zXMNLJa~OW8yJjiH6I#xqm#4MO#veWo(P6u)`2O!_DEw-2`Ja)!5z?(zz}1YBN*_6z zQQ1=$ri0kyO;OkSn8p&u3Ic8*R;i*hHdV7L<5)~9>RSydk&|A#zj);kEq8`3QlhE+ z<n(#S~h|>O+}0n#l;fUUq1257F6t_iK4Z}kmV;uAa1%GL>-TavNNnG z^~foXwZ|f;0|CK?%39WO4uz<#8uxp+El>}L;3PAx-=9p#&M^dw)drOyZHM%DXOo(g zuF~+j>g`TB9F7y8)JIe*v!r4;6m_E729Ft!rPd*iBtTD zDerQ*ZB0+To^(>KFk1&0kh;t`6f3(iJaW0S+@m;kTvd&-PYO&&BUOH~E56mmY&iHB zTQ?;=j!Sglm7|DgGKSU1W;lR8+hPz6M9dksyJ~8b>o8L56p!a0RgCNF!z^hk?$sy# zkC3&({MM0O(nUK)GT3K3)R8^86LcivOa7*yysj{lS6HvC+>(5UhkfS>sWi`d>WlKc zJR|>MpkwZr5ZOCpfQUKqw5I-S2fidBXlDn2W7!@YtQ6@F<<9*ve|Ev_hKcFXy)8<0 z718u)Hr|^cQ|2S1^mNSH$6nY2*El&&kA+rxy&kb8I*3VkAQ;bh{pRIIxY!-^*38U8 zVdWg+HSm{>aPjRkJqksob1D3ZzL-tN-N=>;Bf24l+tZEuo(Y_p+Ql97T(y(Vor)fA z8*i+Kd$|oZ-fV7HR0e8(a%Mq=W>trVnUb3^x-51T-+op3!_BH}NdJN2W#UX^Q}Qut zf47oL*+I(;uQM#<7a_7{>9hWXrgk)Jx7@Y|Y2TX|Jd636Oe!YBp}x@3_!THfFZ#S} zC%08@r^`F_8{^8RjVfNz6AAu2rm0Q^l;wJU95->R8c*SqXL-(oI$5+tSU}CEE&tj# zw@d-yMPqC%!~+mB_yS#HOl~>ufqE;*8xB={k`~Q|1X|d!73C0DJHO!)tS@#)ZpoFt zwVCxJH?$8G)xs#gACk<%>oFVF{kf-9^+Fgvcjb9cN+Cx9Fjv0@4FVhSd8q$rE55L$ zsCL|Nm;CcniW?J<;mGxm5zwkg-O9Y_q@p8%KxV33GyN9%jUlp|hpi~Cp8HPFF?Tx$ z&-jOwqHGnwcx02@5@4g`UvL@kaj&~?q0|qL*qY2Lx&H-%%yUv*;^u?SxCNX3RJpC1 zK^0s>wcS2ZaDO}=sn=CUDRq0U+{oVlb2?fc=`A#PIS~F~E3iioXOiFG5T(Owd_$@t zilu2~3nuw&?}Pq02%51Eq<^hatx#B7jk%%J-#=4Q(lfe|KIzM>(BfVw(# z`dg8Jm{}kY<5|QihKp$&A`Ju{`lV~Ufy)-U%LTlF3Su0Kk6^4d(_P5Hd-&%H;d>ydp>C=dQ9Vjz+kY4!41BYkp@h-6P_S zQg>Xs3QK+Y6A?SD?pRo3ZIT%K_rBXhb=MuB*D_t|^3>E9o=yR5VWrIN6;;0)6ukHX z1Uc#5;$3ARWlt1m^?;Vcy|=pW5z zpfzmo*#%P|_o_kE>5aw}i(yCV4( z$Jn%0MPEy%t{UY6i-=Zt$O>t86?vqaY*qM`Q#>L5iwIv51 zY~pqAg@W2j8DB9X4%Z@EvlFlWU6lfT1M^F%LuvNua8IDEtfvPuAduOfOuWAl#6DAf zE*2k!hJ&m}#BaDr8Vj1TOUANI=kcr@(xguOtxB<^S$+Ayl!2JUvWCh$rKTFbWsU>Cg`|q{=Nj8o<1BRL@Nogj{+}?1 zMzKef$U%_A2GjjUnM&2J+4D-$nEh)4@CL#wNA-C=&sZnB>F*sm#(PMut|lHofAWn> zvN48g+XkFqcVUPXW%&ns3E^tr4sa{RE{|?4$2)D5jZ`-}D{AhJSX}*&L1+YWvyEtD z$v5)jK;x5Ok{LnmPgjMtGYwDX>Vm&I8JIsUpG344{dY;CpNKpsf3+q1uw`1}20W?7Cn%0sw)8qgS3sDH+ z_@Sv)n$d*HntzVRV)IjWfG%|)EcfGm&wzgS38fCkQ{Zm?mUlWliq)sy+NvM*N83Ac zm5a}7bKy%=^h^;DPcJYin(7MPKS3+hKK%SqZU@ND)0n>bfgjm&|3VN#$s73(1(O3d9&iK6xA8x2 zR$n?hZYoUSD8|U^0SY%xp~(|waIB~`#pMI=g6lhQj;KXHU&Etr8R}D$LaAdjA1D9t z?bR}rwwPCZIii=@+Ax?bQYX@YnF@IX(qwsouj_oB{O56PPnd0u!g-NN$M?2I+SjN& z8aer(!4f9e9mjk^0%zV6bi)a~)d-|gz;^g<&7V*Ss02ceC2-aY?{w-*JO~q8yh-yz z4kfSrQZY*mw>NM-k(+H!?dGg}cNNIhFB%#K-nR`uuG!ldJyiz;go;p%GEkMYz*%Lu zRz;NMyec#IhszX73%yn8rC&lvjBM}GtjLm9P||3Cfa@_{ZfC#Y@A}5}3#BjZg^I+r zC}4AI$`)}`uP5&HIqCY+p%lBZsuU1c=HQEa!|p`qjwaS)id?AtEkSHsDPZQgdH9Da z9D+aI>%K;ha8bnkM^_?)HAJQcgI~{W-~fk%2U|7kSwZ}?6~pTQyV)wZ^b#Q=(}EOX zjb6*-`DG8i4jh*(k!6!$I?kw%bPm_%meh=W)HQ$YZr`V*FnGU__8`fWF+wmVde3X9 z8tu@Bc5_9h@QI7?*6Pqph5>nKpLXP`KhKxUHag0P!?t@Jx-|&OG>_=aI<1`=?ZI>G zj6$kd^ZHOY?DxAX>)>T@3j{DWCq!kw^GMkBFDYxdpRJ*2NUwDN}X0)!XG~j}paeC$HPtV=GyLC8#d(sr!!$;53dv=RFL&oOdsHgALk=a(8-D=h%R( zKdc5Ob!T5?$*VsbJf9%{4}1Up&vpO)kK?69rOZSr*(;Tm8O6hl%&Z4mLPqu;5wcQt zipa=}>`lqYj${^D*@;jTKF95GcfQ`wv-3Q^f5G<`my5*Xe!q=lT#xHn;WHG8k;np@M4C$bS$inUzOaRF z+jP|nJ}P&}jwejt+Nh9ntq_~ijz!HZ5Q$rxt!Cx7<)uXFa%N2TyaB+<(Cn$bI8Jxr zJ7t_kLrC^i&!udEL$@!ODS6*<8~rgM=aaa?q7lk>i|3w-p~T1T)Z4?WN~QDj&iBv% zP}H9RVTE1KonL%OetRHLkj40lc=JAeE|=ZYRaCZTx~j|CJ_x(8Hirri2|uP0#+hx{ zd3JrUES)$-#Uxjf2law7MZX_0-Fj6T@y6;^E7R(%8!iS@8zvxsd$L2)y+3Q_sJ>!) z`ozm@kIRasv;MUu3i1~uwNyq9zHk@6Za36?%$F!GSy7<7Qf+o!u~e?0P=LI? zGumWvZeukPxuyM=3rgxM&eq}D9azNgPE$PdX*USSVlHANz?!g04=n+JR}+yyU(VR= z+bXk5A|#$|e*ibZnZjoE=5xl{`Rxazm_P&x8K;#REjkHaoXm)byGLU^JU@OTMZZMN zS26fRPq^gIZs`?>P*-%|&G_vTQ2R(XMt1FMua%4P2&Jdd8vP(+kqaz3k*9 zdA0sk7ZT_ov`DYl$@zGOO980};-$~xbOL2*Ge3s%<#Cfg0_gPqQQ=N|XP{XX06cp_ z@l$FJ2FQ73zJ2O~`g9E|JC-59~oC)^}C{(DPfg9td$lTFY0V~j#01i}<~rph)Tt{s4e zrP;zBgM0!4?>PUo`$Wen*q)tG`it1csFWOdXX6;Xp!uVjaPyBR?j5}lk*m?FV(<7x zVsrI@KBxd3s1cdTx?bt}BV=QDrX_Fiv8}!LfCJGfAxr(~O_t|^K}YzT&)ADxf%W!Q zRV63mD8}^uhY_q2~Lv(k+Y!`Q5O`H9T|KlF&o0V?n69y?W9&P zY;8-=*G?NcqmsGmgB|d|d9c6fS*thrtI3?eTFpTUWV;6jtY^*vSd!o+8BY20Yx(?a zUNeuarx^#M3dl{HLx5ZgkF#u3TP|)hZX9$al6%+y`Z6H z@DR856@YkOLLcj3l=ig%M!f_P!rzo+aNmjG3^UNv%D0%Bp%>4PAv*5y^Vy5iFOQ4H z!W1u$exv2v`mgWdOeNc`p)c1au}Dag!Ar8+Q%?CXg${NEeBrS)g{Jn?qQ}|=r8~{f z$-Kby+(x7+hdmZGk1oN2{FOid-|m4O5kZGwhl)Bi6W)|_5l@4iMHMlxtj&k`-~Phb zec=p6ShsQH8G;Kr2M2QciLDI5Dp4Sy@&rD;R`z(xDg*;HHRJ^ddaMWOP30AG|KYvm4p;Px2FfS__%OHA$hregs5RsgkfpBiYoy z%+ntqqH-BNq$x1EeunYk@~OcYy5r;6`q0Drd^pn{Oo7%X1#R2fe!g>PeLkEAyo>&O z3p@Ig8X{g<1AIPQkyRIHk7LXKkM#;?V5&>&1&M<#1!t4B->W zpkRt)?rE5bOr7D25`X znWnM!HJbmi?GIT99!?Kr`+{BIcJa-?bQU|jyeL|`urJ=Z*xy};zdPL?!bkYKz}tCg zJQcIgg@|xSm)RhWkq~XePQelxb_$*rO8*6+|M_YUVK$alCog5L2LnD}`!E@HoF86; z{W>9BXEH^HEgvJ?Yo?knYJ^``5rp%c_blZq!B&|_Z!Mp7t`!I2U;a2~u#h)=plTGM zOohJe|3vVE+liUU-mV>NB#~moe0c_ny7scgoj@)Ij8<>{-dGJkL?=zyyLDx1aLo@1 zZ~c#h#-OqXPJ#WlTNyyKQo=n28Dhel1hS#5DCqbZV{GkM&~+cl;$3S{`b^jX2%Ll z+DkH^(soxn-BxqLSKLCrzdzmhGY2bpiG{Iu0)$Ute+27yMMDGIdMda^DNZ(%*#1#n z#0=YObtN3(KmT)!G)Q3qHTgbF9W{Xuy2PgGqB#YhtO0kwiW?ak>+VU+%GSagj7SR+ zjFQ)%KO)$+fOhu(D;WM`6>F%wCBrG+!ZlLb`yfc`B>Gs<<^5uHaF98k9xdBVcf%pBb+g?hGE9hxdgL)44Lg1GZ|zQ62i95 z;Mj-8-v7A2DiUy)rW;<&8bfF#dW~nBR|_g`^`$3`Qo{yFFl&A5I?a!57K*NjwKXvQ zNEfVcio>nR!-R83bspO+@^B^1|Jp3f5u0ow0A5sx9^fsXu#e2299^p1VJmE9G!@{8 zr%<-PBpmU>aEpDF*xw*atCE75R*k6|quzfkAa?ZLO2K(QCZwd}W0k@&Wotp4`~>c? zUHiexOl4#sN=1zCt6~vhEW{|vKGzA)`xaS?L&o>dZT$!fSbhfEApiHzs|!b-0e_X0 zp94iigUR1YoIb#^sTHcXzf%AI8NB`d^kbjmo%$F%Gyl9uX>_nX88~;oQmGX<;E8vxHf= z7e=xzyRh%bx7>&it_J)0KiBi&8Tde6!K;zopHPTt`u(+2-uVQePo4VWc`d$`fBdZ4 zUs?d`o95{$y4v@a%eq|O{j%t&7@QES3y61H3~>KBe6KqZw+}^;3f;b@@1OUP%O1T7 zvLd;Nrg3AgQ{@c_-eoGqq4{KSuRss}x;PMpo$fm8zIr}-WRd~Fw zK8&7-lsa3CZ|&w0`(F%tWky`(U#m=)k2w{SdgdYcEUxkxy?gRIk1>YiO$wcrJ@B{%Y1zRce6QgBkZ_NK6Av1LyZN> zLc=>nCt_`H@n_0$BTY7UP<`g9mb!NU5xR7s&*xSb*OKJA2V$UrHXkaP4wTJgn;o$z z2t8Roo98Qd6`GDqM^WuyrZ@Nch%@2chK z^{;H2gW=(!;ij)Mn^zEk&SV2TkY1px7zDLJrF`z?n*+A1@REh7`!mD#gQiv=$VcjL zfY@LYs~>xz)i{)X91^KX;ir@!{q6^? zxf}(~rr&E6KVTGUw;sPn&lS{zt3R5A`O%EKU{6K9#Hb=Cn`Q?WNFn}eJ>v2uh+qdT zJ;c6=5V!t|1yb1$ced``t=h6@`F!MBbmU^u-jchClW`?^Lx)=D67C+z0~%**(MKCa zR7N-J2-m(=>rc*OI1<8@U6Od2eyAQOhL)gLbmhgOno)#q$s%gO1+~TrAF@DBN*Qry zpjpgysb9KZRs~H^eNdV)JqWdaX5!G^8_?tsFg-RvBCNo@1T>!xs@8EJK?Nzl7t1Q> zROrYZQmsmS-4ZV`WE_+DBF*#OQ5(642}ejiMb&rG$A=}3rI~|*PEkFb+0R?)pA?qf zgU};lK!E011C+Z8@|Fa^_eAg7dmoEHzQ9@GJ!@o#vBW9hJBoyd7>XRjgeAnbiXjIG z#v6swe>d7-k05QQw_fLgh}h$!&_1FMk_<{Uk+1LW82}2yuqEQm zJHxEaTrC6V6d8>$n4qBxgAeqbg>k#eB-4Glz-qjNd9J-MR|OCHRw1HaO^&-Sg~~6T z@M_M;%$~0Q38$enjgKNUt8%4Z5GGed$InZJUZUELGey`2kg(cN<bzjT_b^PZb#(BXVP zr0_c&u|6<2N5&B;9+*Mf9NnO4yVqgBf+lj69@m02Rq;bwdUMfde&)I9#r=S_8o2H8 z{RjX0jiFlyZsFbXsCOLT!5^VFiL0j&H10YBa5WyFhaKlI>`PMyxBjLM$ZowE z(fxH$eCc7FZJRKUXN?a@H(Yc&du8EqD#U)bcv0AG^PT(uP$BfKojkK(hMKD_Ex3z6+VCviXN+KBd9e$Z z9(pAWXec~t%4(HQV-n4xK_gE+%hkx) z09W7=Xl)E_{_1;bz->;uIccF>GP0*!d7^dhj=@BKf7}M!#ZW6?OjP9ocvo_Z25;qs zGz;Rd8CX4oL0&w(Ao>y1Qwq=#zLp}69|RxiLQjL1!M^g9ezQ3s=mhp_=mqXhba11h zG;|sXVCx5ufL{ua)H3L^tX#gVUJ$n9pxC?Jq;wH{BY_^0kSkVyf{}9B>B+>L8+0D7 zwdAtBpg@S<8G&dW#^o=h1;Lr$AE<`GpK~De@BmzyNdk_yDbpspw zL?Ps{O=xt#V}JF=@+rMr=_wzpoaRB8+fo8r%so!h2R+l~L~X|cjAJD&L=wywpdCW# zy_zf08O{AA#WZ4NcD-Q z`nOH^%X+pKq&Ur~PvwsH?Z=wMWk0-kUYL}uGH@^M&0h-}6(a;?a+DB0Jid9iieSJc z-BA{-81kL4r3^|~Fq>>D&_VMbF=p~z6uY)aeOO@x$VzLj_RKd{78QT1@jhA0!h5&N z8E;F3GiHmq4MeWYG8K=B%|9I7(;M>XpzSzCHoLrbLK;&)h~adNPmRJIa!@^o`bpV% zRr{cs@HUh+U0;YG&j}dub=3`|Z$fd;r^YAEY9XX{4@IRp5Q$Y+RAuRn;9j}e%{op~ zWXs`gnom{->sZLuhMkgsxPS`int*AUWr2cOVa7b*h%2x692eRSvKp#zD@#VMXMcE` zLmODcNd7Rfhh0}u4DeP(2Z+?l&2iqU8gwi;d)4*jZoPJy2gfWZ3egC>Zcr0Kyon=s z8RKi;OKdD0q+yY{mIS1o{?<7VOOJb(3<9PBi4UEQh#U}RiTouNpmfZANtK4QZo*e@ z;rj)Kajxm$TAtD{9}SAPwZ0sImie&nI`c~MQ5&6dcKjLQK^T8z)J*(M~k1io~crAmvYy!`46U}#t+-myD${EKQ z&HlbzW5e5XqLw$}Ti4${)!|lfo{HI{uY)gH{5a~^d-ts2bd$r!{M5PiX!RFHnd25; zWBVLtJ{b3To^xx6Bt5(mQK*-6*LgUQ#zXER@AdlqG|xss_xh!BoJi@~*+HkIpr_Wr z6Dqk*LabY4KDBbT--Lu&ffD#^Oc**$@GThg93xKnl2Tng*f=&8|DCEyf5@q z_;a?4eGf*hephn`dLHdLcx-m%RM{lsfam7Al0=s{RJ2unVvyoM&bBvar<2OlJb@im z*d9{!j6sSB7<{T>3^#zf81fn}lm$3XRl5)eX2!5@G99Kh$sU=T3Xj!~Z?Eg=Z~~|I zQiLhD)|U&cjdjOuptQmu%06R#{>YaWt(o;DaQ6E5ovQe}mSd(+Fj6ORtps&!=b9MY zE}LRKYtR=48dgGH58C7d?I>mTgsI5SMettz+b%-|AyBLbGrVe$)kcP~T3`{XIyoV6 z>eS%6_ZK+xr!{7%B%rsJ(7k(j895nynQi`@#^!8rUyb*l{q%y22*`{zX&f|zLT#7z z-RDfkBsPDCudfHHo3Ayu>$2ljHw=Hw%uZXZHGG==`F#V5SusN!Lp>|SgQC4P^HsS@ z8!t2Cz7-!~8V*dfi3Q5{S3K^G$xu}#s%TcI81)yJ76E=QbKwwf2|3NUpI5+r+D)KZ z3RQ}<>6exm<+Mb1@qMlQuALBx1Ut zj4xqa@ZNbq!E^0>Jm@N4WwpMjFW;d?C4e(UJqzS=OGzK0nUzuXVucb;b)jX%XyN+t zXLYh60nuD^O%OqS&7+Pn0=XC4U+1P3wGFRDLiAE%a=J;2KMGP8MEYt>d;H+r2w$x_ z2UQGJ=cY=D&8zKugj65iR@ZBt17^-}URYwI&-Yb`P|IAN{Tzd&q5v%U!1&B{s0u2N zC3>KoC`4L8)wfO*QGD*n&v0gG#kp3c0t|$@#>+>N`Vcg_A1wSa=+P?J>$XRW@g{Gg2L`Fr&E98@-}jX-lX?>;SGy_+3139GXBvW6Dx znYTbd99sdU7vOYIkgys+dlI`y5ZCYJH$lS8LQtt^Ib}CFSXn;RzW^P4UG02rLT@xx zkKYE*t9LWTXe5>S7*|FkXuM1{xs6372L@7rG}2}=Bl6xBL+(G6xPy$>a-oxn1jlsi zQtSiKuR;xKMu8cL#4HNXiE0z7^6~Y^1KH#5U1(OTo^>6yepfi60F&q;mEgd~yr4;{ zrP%=DEEy6nx&>#Y)OJQdcy@+@g#{;B3RJB*7z1ZFR|V$2<2O4|P~sqaVB06hAqxmO zTrmAdp-Jh1 zAK%`a-ItIr;WesZa%ixhC@GUV7JIc-uj`O?%cHee((1A3x13rZ1sg;9=RCc)AOAE8 zY>Q&ln8$n!B2LFxAxXyVDM(oeI{oo_(BxwQv=Y_Y`yuqAgqVol{ipwN2=+@w**J+E zpjC;N<~(l17ZSy?FhGOPt3M~^0U|7E{^D0fDyCsYr@5anOi|A1nO(nWYN-h?g$81C zQ3T7q9j)VA`2J>ejaaJ+QdsuIz*;)w6F6L=A#q&_pLq}aHC7bpq%Nl>9d|uMxL))A zK7)~o9u|CzfLYWW1Vmp8w!(7VqyQC$WTba&H*S4iC7MUy92lHdqrF$|lHMvHmrxlJ z*!`mCLc`J9Og+}HA8i6Wi&*kr1_@b)Srsi}Bzq|Uket~SV}wUnEh7j^HKuiQf)8Op z{U6EMHX>g@0@a99SkvxWrH~}p$^-N+d2YC=J!lt+=cnp?82wne)}JlzL4Myy3RrMr zix14qKT1_ge?Gi1SzR;Tv9V{q>gwG#;a>W9(Vr(o*Wa}!@!}4JCP*ey;k5f)W?tyW z?Bsw}qV)-h&0Gjx+vZR_$usR+3uCsNlS!e+VSGl4To38RQs6<0RIKTsBbCuODD~Ru zoST++_vP~3_#Urx;n9U~nrx7nuYax_r#r(>+kLXAw`QCTN2h8>KF*~Sf9_M1p@(yE zJh0DC+AOFX0fZ!8bRFq3f;3S>)!H;yc!JavXvja<$;dO>^FWcOv=;Zck?j@9%&+zP zg8gr+R=*s{RCH2@ex9`jl#Xj^>5nI-{`4@-TuwC+45SE;P!^eDcAZX7CCg@hec~Xt zqF|W1>60EwV^QQC*bMDG1Ush!lo3>%*QnvVv7hiS|5$u7c7$f9jLVzh2mm+kK=d>j zJBgQD4YmF&S$yBjsnq_b_kI_43xxY{Z60-f@agQx1ue(kv|@_@mgp?|!KLVi0sm%Y zyVbj2W6fn}J9#p`L+CL8P2kfZEEht5W<#3C>ZS0=?ZzRTQD`qt{sI}!WI^8B|IGDz zZJ6_sl*Urka=UVpF}PbT*a}B1K&XbZ)9 zZQpUAcVr9P9uyP%gb;=Aq32=H%T36YGgXgCd`n~?Q3d-n?34>48@Ao$j<=&XVafG? z-SVl5<`I%_hK#*LcYy{PajNW}L4osJ zITa&`U)jR~FCl#gi_-C^jnn;WlLES29_u(?(vPp73Xn0RVz4nMEWO$pQad8TzR<~S zbM2PA6MKM=;iWTRI0bdHzw&=rCI(FuOxzO5$l*ifv$dPSb zneomu_vIbl+%ZhdUn<|Q)?0UsyA}6i%w1{@5IQm`8^>eEV9n|V#;v6yq-i?Znq)k>s#1TCm(56PH#eyiY4NeV5!lS-Oo zse{&^G}YvAq%E+lX~7vN3#6ZdCuh?yfs9l_CgprNFN=3aHQt+S{Yq*Yy8^^&xe$i6 z&P}Y5ow9DO8-)c9VXUu!C>+%Ydm!y^6T9*9ZuyAmbMB}^?^Oq+_AvS2 z0BXDV1;7a2=QwVS!>HzO;T$x%Qafj3?1dD7&$(EX`Z0#fbHr2ivr0+7d;kjt{|Sry zj}#N)%4lAsxO&=zqEQ_pX(A7|XfbKktS?Db2 z$W{cR@;w;P;IQ$|g5|I<5FC;!5E6-N_UAn`e=%05BfubnZNY~o;HSv#iUq&9>dlR> zD&!JWAhLaUxCy4~&bNiCypeU)fk40<^jL~OU8y7wf1ECQpy4p+Y+O7q;kJ`1fEVTQ@fJP$j&aaRP)i?_uKjDt zbMuF15jgb$z@SHeuD<0!od$qc-^}hu9VFJn9Y~EIWSH#6Z@B_2y{OdP9|iqkUahpo zvBTNRuXY^lJ)~!@I$8#DvgPt*h0v(+gl6>TUgW_jFHQ*sB;{xgom|PP8WX}yUZ*rv z!=LvhoWjhUA%Lgz+RmkDmZ-|WqQ@bTK_QQP7fPl(y;d-vQMxbCPw(}d!M8INxP z-4%7bD|Nr2=`K^*Q5CPz>-g%bTh9r%a-pm*#GIoO= zFZ-$BM-)apgP|ojMw%4p=6t-9Brxv`jFb}H_&I8^@q6|8TOPn&U!q3q51G*N&Ze-f zqxiOAk6*TH2acw69e*5>s*bA_#C1Jr@(!i%WC^6l4$zz13$WTuV4bw;WvWn_DL`Rz zkq*lomu}P0=7!^0p8FlL${h#YfO4EFe*>6tN6MjtEKd7oMn;AbAuszzKO5dV7S^1INNF7L!K?5Nrmg}SO6Y4b!)(>+mnY}yH&hnF)>S1 z5VKT#!~Jd#!5x~g0|X;QVuLZnl7Ux)m`MCtjBEdNg@g*TmmQLb%3k$%;e$%7iD6qa zu&Ew|aG9c%POB4tI%<|IO|c{~lzk+SF=tHCdblakcc|}W(1U!>%2gY?{>2vqz22uL zWEAQ$Yt;|aN*htQu*+MG zs*CjpKK<_a$v&DZMD`X5Q(T&N|GojWf=C!A*N;eTPagCybQngE^L8zeu*H(K#)L$| z^jI?}(dgCcIxl>e2|heLoyM)jdCv;hQwA~8ENBpi@En=fD|eWS^=IM92(^+8HL6t> z(&n0?&?#B^x)6K&+PeF%DWMU%TBQKRE z$l$5#?-ja15DpF+;9nW(!4DkrkDE!e?7^&@VX^x?o` z6wi@h7H;!fs0z%Qx`qPu%&y*eaGyX|yvj3=2!oNAb<<@^4;Xl^+xK|l0!YFc{bE5+ zvXC*@Y50K;b)a7!^tJh>mJ$n!+^!F%Rjraff(&ELlM<6Ut;grw%aC9r&xn6}c*$tR z_qHI$r$61QBb~;fu)s@VRg=ulb`8Ycj`;Nj`~tqGsdxokS<7s@yzHc+H&lV1gDx{C zQL>kKtu_A(AP%;t$UEmfew3$W4|TY}?DdT6o-Jn@e$Iwjt5j3HWfaNXbi)r3n#1bNBC8 z)j!>`PyXHzH7nP3)nhyq`ld&|pXcYTDtBSer!Puu^z=OH1fNe{x*E7ot0qtF>&%|) z2ND~-%se_4g&)j)sV}n|eP6cB1ZYuNI*hrA_z0Rlq`z5r8z_P=0k;z2&d#mA@|s9J z`5~T>O4Fdk>KUO1TxM^kXa7OdEH-{#^7qp9)Wg#sFLYiC^1XPi+N6V#?Q~Pmj|=@= z_Hjrzf>}^)i7LR_W_cbm*_Wu@G9ZdsBrwQZp(6*;nKb%eq4p67wT+|(Uiy(|^9LU{mI8(bR8%-DP#Eyl-f=m+Q^3ZPve3@*JYOi4XO%-<2!{ zasvlV-$?&D%rH?>)w2obhJXz822R)8H8P2TctdvEralOpIdp0eLAyzn85j?wV+w_CAT4gK4de)PB>d4B zl97i1$N!OG!HlKcKHuhlUNgexF-W1cG0Ef1%|vTMG2wG2B_6F!YiRHkz``a^EQ)m2 zM6~L}J7XbMJgBUpVY$CI--hxz#@;sB53y2xLW=+fi+u!GY@GGtFT#m^cNj|-`Rh4{ z>HgykzdZzJm?JLdFFtL|^8Ap5NOp1@0>I^MUu)+f0*=F%z^9iHcVf9kfC)jXDvj_# zkz(b+q;O~@m*GTcw&VHK&|Qk$B6_jpDA!{|s(*a6*Bba_H3>^2iG9Ng1k%ZkD0t@< zWqSIYD+E@LcdFkr474SO55m1l^Ob z{FF4v`ApTmJ4u{;2R?`DB9Z>G}u2kQxaA07GIe*F#`2ZKHf}<;S=(Kt{#*6_K=1jZ2TZ>7EDLD z-y`ro){L0?l;k0}Sb0KbONa-&ZRZHKUA4v$7>Vi6bBl7A;k{-F@&xK1t8YVHCR(

mZa+(YdBihNGfsG4fyO)hE=xgj#sKh>CJiUL(j6NU)M?gKU%?sTRJGB(8 zNM$$$Deb36oBtqI!G8SzIak<5!9CL7@t?%m8%VWewuM^!#UGqLIBCufv`5fM`-2Dw zv8~Q!g6Lln3j9Ea$O!R-DuMq2Ix%~Ym0AYr7gS^a)q2}Qy;1Q#fi?Iq=O&>8?tv+P zSDilbu!40(8mZSUMsO65Z$8Pr>5DEx+g{@}Obm4#@IJ0nhPqhvSC0zWN89jN=GCMK zyg=@`nn>UsHGy2d*pYK?PJ;pFpw))ZZNVY8?-k4J1p4PckJYxF)2#5BmNH6eJBAg) zuS_IjhQZ5)Kp0%6<7dN8XdRHTH9to1GN7BODG$~?@_kzZGPfGY1LpJZ(>es=rU1i6 zi-TH-KzA!-*EbQx0|1G3oB_!`7K171|9Dh~WeI=YR%E}J!R+CP%(A9|-W9~(L$KZ; zut}h|TMBL0C9I*B0^}ZY$N8^fuj(XO=CM3M^jzCsaWgYF0wiB4E&jqg5-%ojy@ z00NYZRR117l`br^f*e&jq3(Pt7^!#T&myk8d8PZ*W8%d!iuwr0)Pj zx-5WsVYvwrKmpXQ2j+^QBiv*2*HFVH`vybL^}^Lsyiug4IY>bSO}EC0_JAgttz!k$ zHp)Ffjao|($P2EJSXuyArdZ0KLCK$|a~c z@=3@(&L~>HTdT1YO=CpVy032c^I`5Sx{Tpg>{@F`qzB+E+d2T=o}&bRV}0Ip09yKQ zTzj7+d$9P!ni|Cuyn6@U9VDu<2LX^aACcaoqW^I?Ob})Bag^r=!%v<%nWwn6G+@25 z5D(h^%j8*tKlFhUa)NXP+m%fCMkh{q4Wj^^KsTjeFX5)Z6WIBPnDn6x&Vp8A9>Z(FaDz^HWQbNl8uCIb$G~#)(e;pA*h}FIz^xkk zK!ozs0vZkl8iw9TP*1gVD71GB)}sjZ{|5+Bt>V`GQ=#V9v9a-^T9jN-`#Jj=h+|5c zr+aeZDMOI9)YV9nO=wrjPYMxfV*Uo4i()7mxvRzu>0G}raBfkkX_c40sg`A8oW=_3W*L_tlP3lBUWP0Ix*@xz!5gtH_q zS{Va-rx;{*%DLnCpzQhb5f(4~X&b#*0XjluipEW_LNxQ6M@U==W=W+Lmh*$yuV8u< z;f$jQrwz|bfv(>NpafrT&2R4|n4|++3teylxJsFFKTnYRN}qt9;#0R4*!vYgzR6x6 zKN2~UQ$5J+u#k8^*_JsGm1<7rxA3Z3v*QDJzt75@wt25~78h)z=??j6mhD8FKp^!Q zT;gF8?`eg=pPeukitfXOVTJqNh6jONc8tc|X!6}N9@+0_VI+mF=4hzBhOGVnsRaN1 zBq^a}?)_rB^D+wYjP`DRgd*W&NtgoyJy}|JUB?2~H7LP-h^IHk_<1gHr-6*LgxaRS zY+r)+^>m2c63adT;SdtbmOhvoYooy52N7xvrLWywzd%!_S?WX13ohd2U&y?auX;v(&-Ls63M+VWmq1n-r;L*X74l*+@}J>DrwgE0^&U|k~*v$tZN zEgY3Ik>3ESSrL#4o0<2Mya!az3>B{AALas4*aug*0^-J@calLcMj)G-rcci24A?q? zhff*|V0&tUkbdVq^mY#k#0+PANumiS#aka!pboHZzx0S%BOu5bAoz1Ms<>t5i=qCt zsfcf=YxZLNpaozQ%6Lj6?m=gei!cv5S>&Gwg+@hW;F3m5tc-vl@Vw%*=?3s!Z$mzv z+pJ7?X@0PB4u~4o_Ov0qSSn%U_gak5c! zRN*ubTzO&2mne*=Hq)Qj+?d~tm8_*fg9i~m+Vijlb*XfyZbUb9AwCf>yR30&uNI8{ zyLr7nIH0K}4kXW;py)T9hq%HtjV#lWMWsQbI7+)iN(};ty<>$g^(g*@0E<;V9-Ma-a zD|5Odf60ygJupg`LJA#g!g90QYa*q&Z*1i;YVxRABb9LH^l*H{32AwK1{F~!h zAgdlq4AQ!TFOl7*!405tl8D{p=aA7*6)-jJPsQiJH3qlsbJTEv@<1s!&SiDRaFgzk z>@Vu{vUK9`J!H{fOBi~tMrIGMVX$B$^&B2N8Fa(rQ9l zc#c_eXFzSCa)2fruHg_=kn+=bM6uiS_r=*pq=e=pz0aL!3PL%LBP-TW%fJADR`+(o zVbUOi!^G0UcpbCs|0DIc6+U~sK-l&Q&{9j;nNal+XiF41OVb~?L@Y0YR1{T*z0_;J z`F9A4$Vk;N99Dqy&a}N9P5W$+=Lmij2I%lI3N@bioTvCOIRr7pEdedV;Xt~*Otn6Y z(TJ5r^;$+=Rfh+JeT&=o5T5JrRv28!mYA!`2XR7;Lfm;j)jlj4(hOp)LC2#bHIdMl z`WjR%Yvs04+9>tk26kcnk=wzEHM3ws5qa4$KxL9?xW(^?4(<&>zv@gMAQaKX)BHx{5GLGV{3c z3z>2fiBPEOUIEF`W+Qs{rH)Vri2n0H>NgUoTuwf$=jFk6zksSTYE;mJ0B#nM)E@~- zm+(SOxF@^17p8&Cp(-J=07fq$P!R-?55t!K$3OVJIs1vE;^OjV!$N!EmwS0EH%Qn0 zABt-zuyCf}$Sv}sUYPeaJvPb3tq|+%HGOvD1$$VfT&E#9qprEm+Ap{DwyBnnF|HE^ ze2#4~&T$Q+gTl=}V!w;G&zF8T`szG%3e9zdDUs+44;e$rh_{UY1gF#b-5*YtqS^Fl zr0!?K?Q8j-Cm%^|gYUj&Z(EOX90+;LgnsKcm!#)P&MsTRuMbjxbMgGr{kaJm(xs)? zM397$T9V9t=G%yu{`j-+p(aoxG10!j=FJswmkXLX!WPGyj~rir&XizcRp*(Zo*Kff zU)GgX1(P$6LD&86M5f_Mewg$k?DQ=G34wASq~(<^vzxm1Wz}!%ng8F@6El21u-%*M ztI0j-!pVW+oJyGOa}j#PXUDZ4FRbmE&+R&d-$$5YIStc;)owRDCH(GJ@&pOnOL{7l zgn_MH``*Q;42Fo4OTx>3Wcs|k(emPTB(&dOL~Y>j-vQwy0umqAzDWr3!#h(H2aJ|z zX21nSK$EqBLKL@Yw{iPZeK@!W%Y$uY+EXUF(m}hI_iD{f*ZilRz^u9H46$k=Q`t_C zgyduff?a-JvR8~+3hd|efx=kjjpZ0X*7+^^yUd{lJmw-W=URSwx&lLamfWbopzNp# zr$I%@?{88gpPNwch?&z;hb%67htlx=Y!k9{;KzL7rwbFd8S{68CireV~ZOmPXO9MJ!p3Cwrvrw%+UkSZoG%*?8T3+g>*4@v6)x^!% zDJjuEua7D7iPjw$q&K1o<4n#u-ot>>|ISpS05xM&d>?mV(!7)-iO(umtck_J8V}d z9Pmk2*EQg#DAAhLfxY6oyXRohq47}JW90NxP{O5OgfyuMX0gv%h)~YFrlwa)fx)cj zdho@T`W$*iSqmDJo2D@CViD*L{h8{Nc%3r4bjV^G&iS>?ekwoP7;5LXH~%j~guMc0 zmmc1PG3}kfk12Oie1lu;l^tMD`0)Z8979iwHfm&ua@paVpchR7C*YeueuD0B>uc&t zL;i}yBxmiUbY}a5Q1+xL!Q8${tpup?M_o>ptVT9p@NGpSD>#o`Sw`ZI%Jy5ec4xO=tEO3`0iCh zGuU5%5-%@^u9nC0C7*%eHqO9ipR;I-8L10uMp5DADH9$Rg$OH4!S9f51W~c8ylOG4 z1PRRB(I}BaGS)YBWY2b}DibLI2o;K&3d|Relho;KK-nN?F2#~XG0M(T@L8zCTz{WY z)_s^#6)wC1e}24?EuKF!6AfCwp4$ZKx(3jXW_pNCIhGyPtfY{mQ7hF*&HmCNgE=JqdJH9o5z2>3&&5kd5d80Y1 zRfoB86%qCQ6hKG!R4I7t@lM54N_lQ#=D#6pGXg7$Ca#uFXJodRC1;nrtuE%7zGt;{ za{qb4xGgqRgc>s(e^}U(ZQzk;?lff6@O)lAICu(whY4mSm+T_W_R)(N^<{)p8%SY9 zFv%ZzBt5^Z97^;coSkRHFk3lMnwDVOiua*s*R`sf`>|trAJ5C1t#s*^Up(e`_wuS# zjHsjKw;J>Fe_uVqWB6yl5~eNgk#L65oOAa{NJu(UFCBLkj=G6fl)G?OkGEtN*vWv% zsHJZEck#}dm1!pl_WlYOi&s2Z1XK7e3LHL+;O%Ebw{zzq@bNNMELP=tf)zx_szbD^ zf9RBf`2jIt)RscOSdA-1$iI1KGC^32t4aIkz2NP8!jn$ik^GH5J-*1{eWc4eO`~OA zG7sV9J1D_CJry^WK`%ud4}1EPTbP3IWJ^2YQjp^V1}iCNjL$jDK0-XLJdbsk?`zNs z@JO)=HVDx}!l3%7&Gvci4{wLQZQl#7!Sp>je0igCawnNpC792iv#J*_zXR_O(kL^W zNcb%^Zb{FmYrGDGwX}K$tFZ5gFDEiE#L3#PX4Yw6M^c>?7WH9&Y1DkE6hyNrBR2pS zO##WXfbM*vznN(4Sb$sIsrB*wp>qN!N=DwiCPMx*5BBI`I{RUn?Kzu_!ogHW_o82X z^@eE4_?V?wMhJ4US0E_qqiVqn2KLd^2Z!8va>_idSfIy?7kDhC0BY$Q*k;^Azkr_c zf4)ds`MNS>fJ^@@p5YXGbo)*sSOSu_HBH3uhjE7=y&>F}{`z7jJ#RBgDIhKHM)>v9C-IrS;KXb7{I|NiMtN_Rd7_ULlg zPtU*&xSl0ka&+gl3wJ&%T;xxh;PKvQu~m}sC0tP=IP<$CuTrCDzcT^%TCC@W#$m#} zzJq=KcDhK-|9!Fl`=r`ZSVp1h?TPNI=q>v_Vvld%Ndc3m<;`mzzeR63_=u0NBGLwT zHn%qx9V+y0Wo}>`TA0JYI&tXa-RM;Y2JQZ-51G++Q=K@#awv;N%f=-wf8rKj zdF?>T;9zlc-hu<0J483_g_iL1iS1|M4vZvb5SERH8p3q!!oumKLe`wABrNgEbI|hn zpQHZQiLgE)2@l(P@$XL@Jl%7lC|mCYwOM!gA^m%)Yh?$I_p_B(58YZhrT@%NOy&EC zbVGl&XSI2MfjqcP_Cm8BW$>=%eR()Q_-GnoQa7*?nXbfGWq}-2DYstH#p&)BT!6fg zG@KZR5jJ^mIO(!VfD`B*;`;6Lhp)FP|Iz|DuT$>u!o=jc)53__^NX~lpy}z>lzej- zvm(w77FT)6lk;Fx33VGsr}ag6%ZYUbq^w}`}^#SV}{+$wSj^cbOYu}mdH8FLIf z&Cwb;7(K$wKSLxO=X9UzX9-?I3tOX+jDdire_0_|;k{sxvrYVJBN28^4(4Os$+K7+AusN=tuU>di^b42@7| zC1UA6mQ6i;Alsz(`OVhH&s{c{ykgI}>ppc`l-H?nvQC^XzV$%?##3{{X}(l&vXolX z@oNNxGFqslBWySM5L(pbpn~!`q1^QCo4$G7T>{KqFs;vd^4YyC&GUwz7K6qR;qX;x zxBKei_=!EYy$uc@Asp2ag;yunb@0z@a|s9GDoujKK2eT6grLIT8#5fLC8k24{_`)( zz(WH;+T!lONHaSHV9e`)5H{;XGWmkTPSRP;tE(C)?yXheDaT2M2nA~%AMwCL8% zG0<0tnpN&qLX@+k~GeT7jiViMQB5O3(bN?Fw*c`IWslh85(EY#O(PW!PRaA zJqRYC6$vzSxR|YnKmOPmKgG@mnk#%EFpelXgr0Kfw!HqH0cnZsa{GU`Z~DkTeK=;Vbp+WrN7DLTOrN)JFW#jUVQw|?Yi!fO zJgk%YR#V===d0|;#GF(z;CSZ1$gXD<(9f}w*OsMUe)$;8&CCA^&bUDK^a2Ofb6>07 zT6P7L^e1Fe0R?D6;Ba+WTwQla57z zy9+8yZ#0JGU2iy$AFE-N0)b|p>krEga)l*)x5-(6OftK242Oby)v~nDfUOB|pOxgZ zoNl*Ejzle7`1cu_nat2o=9SSRLbw<^DM(7vB@{J;u@IbrED;=^zX}y0DYPoYPE2-H zW!lkyJu#{f)>BYBDBmK1u-OmC5r<&t}NG|w}h>Sef)AwX4&k7UeAk% zfdVt~LD_G17<}%tJrTrIJ$H7%=1b!#h{m=zx5lsKxh=J6=g&=S4xFInz45Yn^})|) z+XG3D2+BbN+Z-<>&*9Me|70ABE>9%AD;GTZV5G!76Jl1+jo)D`iib8^&qqHvF2Upa z<7*^hi;xUNt=i^^f=Sd7=0?fU3`Q)XWVY* z5z+&~Qs>)vTL=LM1DPCyw&$TV?k!-#DidNXJv~1cEXI5*ufu;BheWiWd&=T@O!!7u-o)FJj)GGRN2;{Bhuqdp zUwv-ska+Ocd+N=N@fzA9yD!{2DHrQzQaUKb$HVZ>bIT(6tC{1xWt*$yxO1zM;wXQu zM6EHQfaJ&n<#h}aV7?g|Ztc(N&!HtF&uQTrhgNp1I7NW{HvLV|5O0DMK1-8zeCH4RX;B34_S%OzkU%qiO2*fafjK6Z?+y)m4 z&!8w?@a8BeYRFYP{ThYL60oS2wob@>{Y6?ouw7#tB&o7plT!MQQ z3fdEOAK6x#Z224qol_^*)l00dPTb;?07@A=N3d{?$gksk9S5>7oAWt#+or^*AmJPT zU65#Ko8Ynu(HLRBslQ&uF6p1!{S6uFQzK#HaL)P#I;!6d{!23BLx%?%U_S3n6}`TS zo2FsWUnNGGhS(l7`%fH;jvLC}R}#Nl`{^{_c?G;?Wohz+YMINk$Ya|$Mh>f{XZ7hH?X*Alr&$8T~a zWaen)I8f=VB7Qmwrhg9fQ?Zk#AeEe4g6fWZrOWchC*fBgVIIsF)C&alE@$ISW$dgE zbhDx|7-bGfttlJ*vwkuKiBt;dB^XLlro`UQkNE|74A z+Pcy&7R|2!J&>o;R7J>gsH0&<$-1H~~HfGBtN4LmC=GvscfXKBF+fMRp{Jn|c ze&KdEj<8}28%Ftg)3}i&mw@fQ+6NxOz=UPmANh-iv6%5|EId8%?{`iD?i`PWE&6ge z!*?4|HVv^rNuI}Y1Fz*b^)D{j8BTFxy?GDDDhlrWUV3wu*9`BuT223?MbOx4e728| zcLPU%fBbs>iz)TH$@6jU#e4WJt|`g-X>-f7~eNw02Ql=TBVD9%$d>OCOi;OP?3sG@>`EcmqPs2 zS`o6%2Hfc5Rq(kn>vm)J0kH}UvAVYFWR_$ghtt<5d?krV6;Rg^zp4N1f-f^<@gs0p z8pGL?gk;{ZiBx{8*|!MY9*KJk>3c3vA)9*` z62cnZNiKDPG&}g1xGoZvwe@&$&t>wIrhNeoJv}ZJa7{Xze6q18o>sVlESO24mMll! zQS?F88fLd$n}3m%gNhxj5y=)GL7>RhE()q$GN?$betA>;Gypez5}GE4ad7quZ9 zdMAW*?Cop!k^R}TgQc1VPHS%O#=I`K%g?PH?E5v@;;2^@q2(&bH}lmqG4-R}1k=h& zFc~DY1+Q!(HUMaO(cT4jnGqP^s&U6wRaH!y(Va!JpRi{q^22U zKG^8AJEow+2dFu;vvbc{z_6%hLQRKxuZQ*ou^?k`;U^dS-CBXvT&!@&p0*1BUMSaJ zWMLL)_9V-ovcDjwuv(xdm`7Aru0z#q$>8Nz^2~~=>9^t#(T4$e6r(jE7+2h{yQ$QX z)wlYqvwHs)MlH0fV&h80cil$>Y}Ox@UVd7*W<%0W=FftFTF5r1EXQ>JG>-&(O8fxQ z;BP{H>+CzVYLuI{!`3P9yg-uu+kLUb&O~F-|KoS0dE`*;fp2O*>hr!2*sE3bw|15_n3xr6GgbU%v-Kuqx~HLx<+d}*OgS9#`rRNSTO7)z4l{2;IUnjNHl$@B~zhHIt|&o_%hDH}P%Ut--Y zKo79IM{T!6P5N;1hA*uhKRIdf&r5Beo1k^!n#{2dUW7A(sLM~9gJmDV%kg<%1doT4 z;@)@CK>U+N*bmnO`ojrv$5;E5)#r9*2&QYWg|y0+W>RL-${KJss?io zv+oSVa>c!ll!O}7;f7mx!EX*EHEK8XLJKCXFfO|AoWQNF(@*%hlas0^laszpnuv8L z_c%>=amdTb$x%sm@=ljQ{|l{mgAVUG}9u} zD7?J6)Mf3WKPY}tSm=yQ{{Fd`VlIhmFy?on8?R>~Sj@MR&-jb(l=O*f;Fr}i%y5$}n-ujrLEExQ{sg3Zn4I+d2cH-=x#q3*m?9Vjt zU+^&>3jFHElbo?}<0p^S%PW_Ps7~tebmS84(%S7v18cg<`eoDQ*)-{2lYtK= zLZY43{T^FfA6#!sy*Pi_eyn#mUWq-$X1wWU1T`u2fS0I3vCz2bmVXK|{tzKiUdgRF z33EoG&=jQzo!o1_CjPm^RD9BpXst>?=a4 zV1?TxPo7J752#BDjHW$zFJ8zxQ74D1@P3|g``7$Wr3vdg%n*SbpcgRu_W5<5c^~ft zN2>|HNM0*g#fkhuT@&ELB3W!7YvOEe&I$r5%*^s?vpuZyDgkb~qlF;(PPHyd!cF18 zZbR5D{USnK|L5Y9#Y56LlK)BH_5&bXp9CIi?A*T7+zY~>HgYYI15WB2uJ!`1o@T5n zRz|-c9#~wS*q37G`?#}e{zhh6`^-SN!9$jL<^PYiuMDVi+qza%6cs@f5R_6t5CN4& zIt2jY1X1bkR;0V6OuD5d6r@3=`=vxLi3~VeeV$lE9K;cY%`W8_q5Px?E&tSO zmhK%RNs?jCiyR%dy>E;0^85Pu5hwTbL6;)P`mX11Zjv8j zD81wL#BLZ$_Uk|+x*W6JTD#AzlJ-Jl^-qrITzr+hQ26;x>yP}ch?`(h%jVQ;Q8EEH zCpOBJU-9$RyRI4~mazn~$av0;W+u9KgM)~h@)m}iU!w==>&1v{ z5~Cr;D4`7RFMJI9(|vZ}e&?+`Ch-tKH939X0pNCeVp;3)h2YPr;>o{_Ru5_kv-5Oa z72fb&dNE%VFSAq0(D4E7nP0i`W@rM&U=Xd|QDl+Che=82Swum=GksdMVHlGkb z1z-(F@(W&Z2Gvn$2)k=Y4o{={xIp?BY)nNZc;0`%P$#vSnI|J3DmCaG@360`J zf(B&5o3k9uiV5zKC6CX;woe+RB$PHukPxTmpQ>X6Ru-EseewLVe2#ft0NtPJUq)H- zgoW1Ihk1o&Um7xdl%#x5G6%huvtfa25EN9}OV_0<`@pm*Nt|Cg%M`3>>}Go}D^Zwx ztr!D*wFTLfgUM@{=O8o>S;ytMq;p@^hG|q|&3dveR@|Tp9s|lVPj%53dzd1Mdw1N( zLY@+0kCPf0g>1BV+BfO(> zr?%qjH9-2^&C$JWVtCNbgx1M*kkOHyow4=hmSRWj>GAhbemjm5_;&YbrH{=A|G^iU z)3Rn%8(zEKh*&q8(+i-qpD%K_oKsOXz$9KiX!Rpni0hi!kWGB+iszRw{{D7cYax!6rw97((mfGT#Kq zWeuPRHu?$|I8crkS5`QTTOvk+vm;=v6uKQ&xQ%d^?IA`8Mvv=rsZsc;xsK`^GuCNr z!HDG``Hj@_WZ95TjQH29*+c*N<6BgmMlexWCFGYk;nu>-sF8h zF%xIv?GW%Ss5w~%yyQ6a5e5l3{*Gu@w@V2I;cS$}&bj;)#?qvNkA6fN8vt4TBLV@n z;2H70>ELFXM+~@4L^V@pGT(fySd;YGpC%mo{WajaXW+l`VmvW9!DQ{DTOp}nShu*e z2a0z4s8!USl4$n<2CfY~2tQ8omp6l04Ec+2Hd5|yA9>k*{S}va_bI&s;t?%$Q&3ha z5H7y0%* z4i}0^A~C>wf7I}_g=RkJT{?_m`y|OcjNwpW{y8px2d)uQs6pc_tLkpWjmVDOgmE)p3m z5L%*pj#PUc`hs7^@@ob^RgY%*f{uPrh?adKB zMLJuqjunH%dT;=KeHqe5%r0>TE+~DNj;*)4#a9SjD})P(SSl3jPOtS`>0Cp#8U|AUMV6OuFb zM~7Y1I1Bbo`TH0&6+8*y%zL+q`e=tv*2LJgL@~Z)gY}T&`NJ?9 zSuS=^tpI7uHK;FK#IANb%q%yRK}~+su49ihV4<1v&vfmdL<1+&u(3SjQVd*&%%hcA z)9JASx|+v3XEmf#+wYpz824Tpc9MFKH@h4GDD5hruOEN%xrs^eoV0VIKc&-scczYy zfBHE`Zjwb?>UHvTje<0?#Yj6&>PjoT*v411M{rvl6$APq-<2b<62s>%loY&{&TI&# z@uB-uQcN0+DgRsCx+4XIY7-{|c@X7Z07K)V%#CQ^pJj^3K4wvsZ;2B;cW`AV-k`xb zaI7gjR zBw7A`*1_<21N@`Owj(u>w_nooMYzzR!e`ar=DycU3$8c*1edaOEBQ^z+^-gpU>Rff zfsQ2M=jQrsSOf>}$;sA^Cy~L}Eh_Pcc|SBvdj@+ME64uv#p5Pu3SRN?0Ya%`B&fYuC-xKi@O2kfohK8a(oU-0bn{&#{|4ib-d z`|EGNWk&C(GF>6-xQ_+Aoq6+Nke|x{gfIUQ+NhZU+P2qYV9%AZoNSG00p2d$91KzL za(utyU?uq)!flPWk+zlC?;Sh5kI`o_vYCUx&aS zWV7J}2}uf!9AB}y*o<;fb+aSc2Rzh~fT|^|Pzn!>go6vsds<>c-@0p-B?_Mwc$oX* zG>-ywcQY){^i;cN+{PZh$3s$UaW}G@6>T2uL9M~wd1qW7h@F|F^SFF?X`ZSDW9k3C zQ*eJ0xdcz4*5I47ASNa#bp|0Iu2h$F{tT3dEC?3IAR7Cxm33_FN8~kRM@&cr158ZX z^0=)Kmie7Jj^4w$5N)XHVX;mk@uW{{I`+`sG2-4sujPSbPyXd{a~J6aa{tN8E5*ko ztQm^?gEK_;cod!hg4UrsW=ao1N0^NO_$$v0@@ z{*d|;72)PMN#{R~2>e^1T7c6pmJ6AYv}R}^teHA4Qd0oiLSVoyF1xhHEpXP<`2z0# z;D$Jqt7JoqeKe-SAf-4SuY&P6PyRrOxnB>TIfUNOfBz21fpZB!XL6e1;O``K7FUz_ z?4@YCLEiA;(UFS9jmg%*KDI7Q??P(`TbOw<2^{Gm_q__A>z^-S<$FqICG)??OW*-( zq7#3_uw_&e#lF9YAW#;4|K$HIwez2E7=X}wA-Fe7>x)j!hlwt1XWl-6J2m%8@DBd_ zskw(vja0qITPEz(m?*>^_pyilk~j2j{;kjk$AjV{McBW*7727>!&@fo&5kHQsQAZA zIDDPi+)5Tlfw{XsIFN7DJgu@0VQAyxKNXPf(MaZz&h`3w)Dd(hI1he*0uX&BU`Gbe zn27hnx*STdxrVI^Cmg=(wl)eFivPba;XYZj6$S2P*y~CpKw&aGRCxZM9T;!{K1%wX z=i9O1RA8=WASU)08V5h?`lV7JD)_oZZ4oUo3p|25sSK6&R7=J~X6$CzKPOg)hnjoD z9NXS+K^y!dYbB`|d#E0`T}UvPjgyjz)Ue?7hr2<8BLEzvYB$%RFEJbh@Yey#*HNM| zW;m41%Y_3zvAtwnfp%$$V#u^K(kRCOx+VZ6>&Hd;$@(KdH`s#&pDCo`junXK$Q+c9 z;XXfNm6#H2WwA$-i^Oy1N=a6<9`8e7McnZ|Z5K9Dvh( z7B28`$>(JCz|t{j72i{O)xL32BVPiz8^$8DFTRglVn`t-b?N_o_teb6QyCl*Q+h*F z8_HB`s|gzUxA4AOc@odFBMt5fJ%1=p$yNayqgRHH78e%aye7G~z+C{x<{iibQK>$n z^$p0Ne7h-Sr|%#PULl zw;hRbjX?DgplSo*@?jVXXo|`=Y>aY{O;i8CQw^#%;vf#mI-*5&|&R2-5V-jjS% zvVf649}CfCAIBcv9mH>N4}@l*BrYKn526*gLT9J~pdG5R0p`{rC|{dF3uC%WjCf$JOTmcNVDVNbNrnWBk0sU zOU~3A(y=d%vlLk(n}TIkTc?S&>@fQ>OFxnNfvmAzZm_$NRfrd|2)km~Fm|GJ3f#s~ zc7G2|r!asnqIxQ}3j^_vKPVhQFw$IxRJ3Ru!p-SXH)X2zRH(gvLl>rRmA6T0C+h;k zezh&F=Z1eh%RPH}j!10o-a^^idNSq4!NW`bhLdV=#@BtHXuT^Vy0H7~g9l$44?Ne} z3Z5_NxT>ra!tguRhF`eLlMYT8p?%9U6f%e{;e=T~w-LZjn8_jB30uo1IC)IH_?|`$ zF~q2vreC<1(KDHX&5<0^$P3$vulI!?=Mq4@ zGEa|EJSKg%xok?MeP76~8Wgt7x^LGG6p4biM%B_P)a+)qGBtSh>{>G0#N(ILA$;B@D?|m>o z*hji-yK~gk%a2-c+V_zX!QT&TKOVZeQr?x99!IM4`<;r~OxUpvf?Gr8ckXetQESe@ zH`Jy~Z?K0FkOId$NC2m1{^^5@^d44)BBOg;MAT_MG)G)cZdKT?=FF>pNVH~mrz4QW zW~`2GW3d$Uql-1ww)7#OpKk;wR(-!S_w>HLPQHI{xJn1w>`2jxnu`LBrBp)*^WCsz z!5Oev$}cgcm68r`JN^`$S{W%noC1op61m{;Bv04f^otG`h<2db&ks`$-hP(M>Sjj-$BR@0O@lx+}f*z!us3_tPp`)=q#GlXS& zl#V|Hc|8RBaP<3Dy_(w8E<8Wc8qn=EB3r2SWbw}}2%CzY0TM!Hs^X7tvAlg9{~I25 zy2SuhJM!$A7A%lsyYA0lw)N}Qd?tm9~37;iwCKU9a6>sLWS-%6y zR)U8A!F%x!JIl|#4R<%MODoht;A~BOFxBzVIK&&oUOqzBR1H;{#efw!JRFDp#yxsc zP!u>m1E8UPX!BD(^`lQ&eQf&(<~m3$*vHy^fIz!0iT5>!vaQWYg(jkU?c4KMZ=acU zFfEsdh9HB}CJ{_+t=YFmpgE#AFYJhcMX0@yUA(rnq^DfPg9U}(#@k|#S@_>Hzz;IL z)%=zXc*l9LS$5KaegK2E1kn|nQlq7zPIh|;CzY+Y#a(jbFO+_>t)VAV?r_-gZr@RJ zTj4sIzA(%jxibHih4N?zw4gX#dH8e0;6cx&=qjF*teTv!BX{0T02SVMS7X};|IyO+ zxn99d_;{#bGV<#IRu1X-@<038?S;jRdp;tY@{vyntpk=()ObNkRz@tivWzX}s`EZ; z7(xZF-G*kXBS+31#Po_j`EX!DPP=#FvPnq@+!tS*!L9(>hCk?OmB-fmy)DYoJXT>a z3g(?|D0&ck#h)Gx3}sR4-<*^Kt5ivvF}SvKDs}fApuE^N1-4XNqF$r#y`!z&vpWwy zv9GY~4CLbs_z6d2hLPjeYsK0pR1M+mR$2La_BRhv38HS;yq+nO(}-i?Q-ByWyWAw9 z3P<$tN$tMe+n!yS8lBlbLVI)}@&o`)75wx7c&MAKlt|sGh zCWti7Qv23H2b2P8SEGWbbjWg=I;5w~muspreRUD6^o-tVbZ3~)NmHBQhE3V|-Nin#+3?Y&j zZKF1$B8>&7jUBi_ZWXY0t@eYyv~j|vZFZ~L3OJ;bT%-g73(0maUJj>S}is(lRlFLNIZ80;}p z9V>ZCwvJ5+Pk%%P5}whROImG!fI|=M>+!b+U3I_|ZA!Xu_r8Kz%64FZa6z%T;-^PB zyuij7G=$XmpA1z4RQC2n%3u3z4_^97D2-%G)rYfSa>)wAv>u7|dp&|&mXn{byjAM( zF$_W+xs8Ys9_m}TiXAStdxvpwu>h$2e~f-gp2mJ=F)nI5sBAQ?9X!H0l9(JaX$MJD zBS-!XjRjNNDtID3GZc~zrqmG zQAP=r8>&dXccG*$ObaQDE%Mh@Q=YpMih9|~*fYoVb0}x?-d)wCF+ODqTMiiMRQ-zn z8LqCRWM;m7tSg^vIW+a=crt;+yxjp)mCP@G-<$mVS`_J`)WhiOpLQ}2og6yY0t-cO%7yZ0Lh3LEI8ZE zfNktT8Ki|3f2x{r3|n#X3x6{2t2M&v32uf}x|NR{ZciLKSPR&;)#Uq~?i8XI!!SuC ziD-UWZ?S~&EA)w1DeTmS-udVodHm2)P8XzA0}cT-{c1D{#nkT{{3esmWx&Dzc(T)4 z43M4K>W4W*PiBft9A|2+Szi;G&xjAmL9an|HL%HykjMdgS;BJHD^RRY?Lo(J-c0v; zuo$6(%az21X^662RQh2r4Cr#%QZ!n6*j-1hsY^4<4$L9cB_GZoO+4o_2+VBanJbKq z^yJ#@?)AX;T0WLIoRD~u%7Yn{=}F1f>Y{iS=cxM5g2u@x<3!a3;ryPG{AT&n!$ zCNnbDmnW9@6zm4ZhNs?Xq0*QY-=#<@`#!#N=DQvEnH{;wRnwd9r0)tF%ts{LvX@=z zG|0_U7+GJPOPj1??gnpKqGwe(liPe?hf-nH;n2O}pwQwfzpwz-u%FBLnkQmc6XIe< zWTSZYp2x5#tdzXw0V(N8{f{b2P)>C9YqMZELk}t>401MA+^~G&Cu+L-9GHwl8r9%F zD&L90WOB3uf89q!cwYMu>a?M@ZQpUw-TveW7kz$pYe{L*Nzg?yFYH?1hpIrr|5Yct zDupl3I@hStFm|`A|NhZz-oOf!r@*WwS&K_&b*V7OG0c(w_w7yI4dVQ#=62J6(*7p3 z*NUQcW5>)5NC`70-_P-zUX!8`GxB{4#Yw715WuWHI|7!VDS(;N>|mu)#F9kx3TXv`2& zXsoz`=(x~R@zF5R8Q!)bGAoYy18RjX%e8K?OO<0#T!i5rI2ENOj}LHWZg1%sH$~8aK*%<>HjYeOi87+-`7xjeFM>VjS5NBONIUNF|~NfJm{A|1EMniz^KCYDN} z`?c9I>tI{V!uv3BDN|;O;t#N(ov!9AJu9 zVxM4LBT24959n(s>H>-7Je1(!t`7y3G#Q`$bci#O$HN4S$g>;atB{Eq|A?(6-PNyw zge(lMA?o`cV_lupW~nuXuYiIS>u|86G_dyT2);>`X+ilk zUuvt(y9Xr!>Dp!aeVK_AE^S)pzmm-+-dCahRLuZ!C{UU6RAO+8dEE!TsY%)H*7T>% z$mGhYU~!^TxbzOWO%qhcssQ|)gQkZ9acN53e6feMc$9wPYfb#a#&}*QGvA^7gUV6* z{KPu!6?yBA6y

q_bSh?;T^WsWJ3f4v!KC-&`iLdyC!3iruP96y0kUwH z-OOET=KV>Hy}O$%y?s!E8cPkdRU4VfC(dV#+wVWNeJ-Z^wIE{8K-C^4o@(?Y7J`TD zM_pR8pg4bTB{5qgEVNho(v4&-ukY5UW< z?c~ubyfo!RC$;%N-EQ)89DG)SK3#g?;Q){z!knG_KI)M$f6ODd@4)2LfEUNM$l zfmm+}>~@m5?37a$H7&OII?A=7plkB+%Qci&RsW#5;)e1^D(5qtCIRZVIWx3vYDrmz zAQ_L23GFFpea`6@Clr5{DF zigY1tPpb794%{@!up40lY0^X`XcOkFqh0n?M;0E+o)mn&_h_u0!3C;B+4IKR?$pa2(_B z?jBcJDnCoA-L4c`xZ9a~D?vuw2DX7hVLgTReLwuL*CPkAt^I!ArMjT_@S`J<8Q;XF zenOjXSi6$hr9sGyrUSq-^mJNYLz3NVmRmVI8>=tKQsNftqZh zJgdI^k3(r3zo^}1*-Denoz)nnw^%?!IX_#0vy3tztOW}1Mff{jJ`J+75wcsL(QZEu zBM?B;pdkHo5;_$qLenLlk^BbMsu5W`6-)A${)VHu_5#1?&d0+jeDjqQ>_9hX%8yD_ zmf)xnS+fDQm|D_TIerbCV61=F+y&F5?I5ZPSnQqInL53ebUnx+n>bo|)#@QWqLybP zos@mFiBHgZQEfP!O|1FLl$-4DJYD-N;PwoJqFhPfsRATu@+aKGA7q4NyqGKzw`A$C zWY=F(ZeoG36p-8|3sx}~T*O3PEC?Z2y}@?o5{w)${qp8kQY(yAa)e!XupbdfU#Z&}8cZlN$NJ&-s#m%^t)lYP6P2642Yb zO|_26^L$0lB$r9M+8V&VfPs##_Tu>>D;qV`{9#)?*H`fQB+L{sP6g+{XAYa7a+yo; zu-pQdZw6?^LM@zipvaMPq-tk?SZSm?lS0Qzr6KxCgs3x`@cwTIiM|~A$kq$)C{O<; z@JDGrL{zRkd_MD%FWXg`@(1{8OI7XM9ZHw&9)WjNQys6-=pYXv&(gy1@I@*9(r-si zPqL`Waa#@D8?J8I0J)7U0x@p2@29iVi{)fWT`=Vr1P81u#w}+!a?X*M*0k`Y!kVta z&53d-&UU3zFeUw;?b^FfZoYCJ6FM!OZr`?rCVRkP$>V)moJ2(?A)RwJ>>>8jLz77JPq&Ln`$G(me zF0*#m{0bAQ9e6*y$>b=tfd%Lv^#l=m9iTH-zEf3{)Opn0Y#?c;s1)N-h~lgl*q^xw znR5>K^V~9?ONZyI4fNI=a%2~b&W3}9$`+Z25P%z^ps~~C8&K0p) z4fj)20uE(U0LJ%nuSREIv($5XMaw}M(&qgoOGa9u6yj-?B!ZSE#aH+{o-4F$cJlRq z0E(jdkWjbZ#)~I@(}}@uz3(oZV9$Xp9YUh9Mf zf`TIjxGF9mK}{E?n~S21%2bPAoHEeZ<3Ir4cWZzsni(G&O_Q(j8H*n zpE*hnaF4zOgs{0iA#oz&%8&d_YHmX|cIRbX(!dZT*6sT43Y45}2e<4yYP|4i?Z!x5 z6m_MQbMZ(i^-aH)7crCj55o-|BjBjax!>TWVzO}XGz>2^as;$$&ixssd2S|v{M^YL z0x8MAO!P(GAKB0yJL-jqL8BCs?Tk_|E(=4PqR`W)Ntwsxpi-NC=gQGSECz(uIB;Wu zFq>!KMP1(Q_xSN09@JY8o=O(xKGjDU^$>ZMQj%RIM?m194@-^os|o_1 zN2riMqVH-)*GVi2AhIT~YrGc=K&B|?R_%y6_~#`|Rj|M6su%&;WL(R^b^AUF?e@FS z-v8b;La*dtG-}MkV+e$VhHp9Xw#!4_beVlez$jK)S+bXm(L-@Q2Xv6V9>)deATP3% z@;PKUJocu{Wz&HJ&zmEG#Ds<_G8HUkRv2Bb>Cj9%q*Ztn{N)9Je{}ZcZghK%hu^m( zk7o~KVIf~Xe|n6P``)RgZBcPLN}JA0zP8h#AVj8*WeqPPmcTbIGBC8zB`BJccoB-< zs5?+B)%toG1}GNg?PyF&B^Lt6(m=DWyr`Qou?x2P<8Vi zp%w966j}oxrWClgSL~r-_M31=6Le(5VszI+mTV>F$A8u@&K4d;cbY`bqgJ!cX_HGW z=_c(-7|CeaLu&|IH8%PjKMm8{pg{tHEc){wkO&1Q0P!e9plnz_H9vVRDG?XhEu5eY zBthVCGJl$Q5B4|DiMV}wiLDwj>OstF^VQym1#--3#AD&M%j*ic$SW&SE;YiC2(RRG z*?#*#kgi)p495?;_xe)EEAriqmvh!81UMAasJC_#=^P%_m7%Hn3vv2G^nas?XtD(D?b z0kT-R%*IgXV+cN8;*j2kVx^FFPOx7>V)E9Lax0tg#AMD%@kvUbJSo|{m3AkUc#oe% z@{=pj;Ss5%HsrFZvsqQL(i!mF&ovP8W%FCwRn&Yo9}2_mfAROrc;Fq#l-yhf>^>0s znl#BSs_W-;M#5gwg_P<%Xbpd9(ID&5Wm1DvFq^O;>r!|;)J#YtX4Wp`LsRot{r^<9B3JNW*@IUz*U;EK+%1OrTY=o_|m!eSE9@1#UuGI1S`P* zo&l9hSr)h4T1a%=+Y%*Ww)3ZMZ1pE`e2r1hcheEI$(rel2qFh1E8(gRpVuZ=MM zZ=Vv)uyaob_Ag#?lA_qIBgd&hg1C9Yh3c}2C}-Z+1JtPy9gNS{2&stVf)>^BF;+*@QGZelVDrOzI zsf!Pv;2KNZn+FDY>R;qmxVS9BXGO&?0+w+w64dR?sCkidP7C?E0OnE&{SzM}3;{aP z&&p~G?4Q1NDRFRMFOi)Gi#S0Jv1XZwfy3XLFCQO~!d>B4=X|HDH~)JLYYua^Irltj{}^XMlXH4K>UjO!S<2A=+Gp5bMwv3`j|>^OI)cF zQhMV@p=1IK*xnmAOc-d7uwd(m{XT74s2mQw6d^_OJ*XVce~!dn9HfUuPU}v!xfxHtT#zBAK`+s6I_u}`+UZC5Djn+L3%`s?BCh1m_YNGlk^ z+y|T$Mg0%(>j(wRN2=1&LrRyfkYw2?5rrm<+uVaEgY;&2-tu|u=ndVi_9_)ad&@%+ zattx0Bn$#@A3*5dkXRQHcH5kUOjR+22}@f?9B`P6_RTG{g9GSC(nk(NCPFB8JUfw z9sftZ1#iEdIAZlnx=GB+#raZSm{YxwxxS2^cbe?lWI7snSm<#1=>Oac%j@ zIPCl3(x>Ep+*61-!3%vI0#I!rsS-R$|1r=QOtWdmBkdIYX5@gPl;iJq06h0POYeW* z;>r7m;?g|>Dcj!g4XRLp>eTdz8S1Z~CFTRgPhi1#9qxCe+^m@)l1g2A#EjD+Mx#rj zFIyYgLlIm3(XkIuH7;X&^g~n)ybVK0sFX8QS)s26QdI`EY;7BW$;y+2z(^POq|pY* z0r#t4W{r}iL(t%Sv}qICz)* z_<#S%|NO~A9kiMx=B$)yGjydSjMt9gmfQ?JTDB$i1E zb~VOc@?lphd^g#8)~bmD`v_-Y+oDQ(Y~gszUt9X`%?*DNk%i1E<>rguml?1P{^oM& zDfoc2he7D}AH^E+p|hyXrY0BjG^|m9)rm7>yMTKyf2=!b2^-D+^JM<|0O<21F~N5A zU5nx0z)ZSA!-{&VwC3u+po9180z9J})>eC@70844g+9T( zum0ME|2UX`eF~9#a6+HGbQrs!j7gkT9Q$T5N4lTLzF_b$?(daKyYI%Ay<&Zh^Emw< zQ&`+82(FTF{hPI(NDU|`@5ua|KGz2FWhXv5I#CE-ciBI8yg)c_YVina(DpDv@8c=| z1a2GA=7RIZk=b{k5QK+d1wAkSblk&QyYWg`a6(FKrT+27Ck>X@WV&K)5QK7ryf+Ea zjRF5xFTznsum|F`^QJaHh8HutPHv3sbFb1KEcfml2{;L zBhWUn9{=zwIp1l5%(g3up;0ESNn@*pIPxPnjcuIa&2kHb3jT6mp=ofsIJ6o2JPJV&8dN+K(`<j!{o2bC}83R?DnuEnKt#~>2 zm7cHWYfd(f?Yn9H`U9VCfwk2A`pEO~C)sovNA4F}ZHRGR?0hRq0bN=FI-jOnVeF*x zI!r5T-hkF^P0(x^hZ9URK^m6|wmv@sKxg0)!ltwFmw2cC)7Xk_M z0fBj#3;`3NLs2Az20y2oE8Pvq7Y^WiMzF040I^a%4A^8g3+`~+Z@w)AZI&~+d-d4C zZK4daUJ{D|sBOXCOv82Smvns?Ycwc=Zi4)atZ<&(CR^gX|2JRAryKSW3F!A10vCD( zhM?3(wDZ`Jew@A!2i`i-{YyM4v+@V8qGVazS$hI7A6e9iRP3n@vSP>Aw_<_a?BDDP zp(B{t#fW`ScEdYj+20Pt4!Q`xt|B2Jk*GdWuCW;F4 z$E{VY$5ZED8cvkVr1ZM6Tg%`TDE*kbqUu%*z3+}7i)r2f zhZ=OT&RrRec_v|PSmVAIxhXQ=dJ_WkPg#^+e+PNzZ}9G^2c5Fv%Iu;LvaSNn@Y#{n zSjZ3%W(z+;E(uM94+E*|bs$$xSR5(-28xkjXzgb0H7XDSsbM~d=_;C_Bbxzl*&}_P=n#2f@h^l zh9WsSx*j1Q#ZQGGjXhvXT+_$Lmk&tovE%o@x5fiFUcX!qepp|20x3DKl@J>kBoaf! z;5nc+TBn)>oAvOlz(UnKBG9a7rz?@@4dcK?b zNVg_`E=dEiu?(cJSs&^3?CGNs*?R@jA42tF0DgYMB;H7ijmBTFWBVJyd6|StMyhq{ zXvvyh&U{RV?Sae}&sC?vWW!WNB^eI8CYlE6MOXD(yTmMi>QD z@!||$$E${~6%R_J)!PaAH5y#QWqHi1d*B>e2w}SeA;q zPkkwM!e)M^dmV&AK2OuT6s2`)$ zL32*{xr43#yh*jlLu;V+&|E7>9AT`H&2#3A+C7Aa)^2PZ8331`1~3^<0V>=f;(fJt zf+5v9J@g9)v8b^TfC&-gsmgiZ)&pALJR_H6p;tX44Ils?1CWHAFmblO=sLJ$-D}u? zBpZz@?I;oI&WJD4v!2I81YbP>=awi(0T`wk%4aX|i&VM9H16r9S8sN8{I zQ6$a_f%clgSFk)mj&w*NXuO|HP(-%XFrM=Xh?rY^ic{lw+UhPN<>?m0`XF|_ssO3k zb%1u71nnVfl?5Emvs*;E3AtwSAulzGjyzB57tm`?T!6Q=d(kL5>i8AztlMf3l+wZ)+en-QA%q6Zy}*?@>oES@57e24HWV#iK}m$e>eh z_v5-P$p+~&=F!1}p}P{_YVsW9pvR>M4t?uzgN$Art+BW??zt9JN)t%yB}w-Q81R<47y&=>&0SGsuU6=Z*{7eMI>^nDtx% zgD2Eg&YV1KXAp*L%fS5XE+8l1rr@)ql>oNmNy$Nz*VxMAN%OB=D82KNEa`K*61QDp za7r53SL@h)ASG0SsY!H7HR9sjEh$A$sR4h37T=qI*xsGa871DW*Z2&sZ3PGbF z+igIdIR^6?0+1^g%s)9{v8o5Nc^@GNSR3YVz#x)|fffy^fxF6>O9Tn?jvV84xDRk02I$&378!CFT#9l`9Nz z_#b&RF6l~-Wd5rX2j^nIPkifpvGfj06Khc6<54?X4XNHvkv^`o%;5mI56t$KtLjeu zQp`UD!Ysn9w?ePw+qT~fTE7zLzh76@W%;P0X^`h>-8aV*^tNG{zo)fkS{NKVBQ03S zS^^?-gxUF8UiNlmex;4%o!m0H2m-)hNKYcc9qlqQ_HQuP7u|QFn-dr@1ag3A@{8~_ z{g-Q_dY6DI^K-xBWz2bW@kg}P#uJ4t|5uG5@g=7sO@;Xe4gu#B`OI~gAZA)fO6#QG z$_PVK68gWm>J*uUqR9yt&GpX#DYsIr&w-@>s3=8jYoZP69`Dv7TkY_mt}4EYXW4mnD!k?8Q=8{8 zyeqFV^|texD0^D7t2s*Z%{TEWI*J1ORNvY$IHr7mZev>Y?)T?F&)+L4f#=aSIm=eeT2-2CMGGd3gf#OPTe&mzyFve>f;>K{ zm%b?RbM8q>HkmXz)t!=@gOQSElSzPdGIoJ8=Wf9R8f%{JU7F9rC4vn@zE9{07s+Lr z&PqPHa@NU8?32Ypp!~{OUx9?-6JkRz5`t|>gRQyDwOyC0^6VDA)v{Io9=@EJUz0*T zYq~S*f~(ujd=oB0(nxufG)f*@NaBru$?i9B^Fkmy3Fb8I@JWlSyrta-CQhh(+-Uc$ z<04{h<(&%7zMuhZ^YN_y%LaAC$Vl~_$-1?Q1c(^pVGDAE3PZ+9`Yl3udnp)o9%bqQ zCHxk=-st)tX1~@fdUJ-?y2iFhqg%uu|FSPR`@DTc(Qg=xn-r?9nyF6L|A620BSow4 z#mo2E1dEM9j4luQnjjFmL`~EIuT>No$(fM!DQ|e1@cPtZ(-r8k%8`ganBAFs`+bvb<09X5|5HaWV!5~X6`%yUPW}}* z(enONtrrQXu@WWb;>gmk7bCvG@h;j||AhGe-b;Dtf_Bj6GuwY4X6K#Leyd<3Y>y=# zdP1(vmpfw~DUj>cr^ON*a#8L5+_lp}u?&qg`<3i$2(#rV24z>=N<^vUgLUy0wl#z3 zFBxgt;MEiUU6rZyaSwZuwx^8pP$OaJ~G z;dqj|fvx9DF42V|)ex<(pD}U@205<`7?R5E5hY3zC14Ry1m`{CTUxKV-P4}%yPIs>FOunT$%%6bf zwGWneo8iuHyh+=BnAOZse(0XPmR*Y>l@^VTC(CpKb#}0-6Cz?YmPfs5d{uSx(qOpB zb-;C-lb$+fnh8<*bzI=%cUmq7V`4Af z)@DlfBTt* z`aIeaTOzb?M;fNRz8lOX5isgsxif!ECWbz808DsQ^FUvoX7ah;YkZ~o^lydtmdaJr z?}^qc<{v)XExE#7>Y=gRJ{cNx?!jIs+jv#dvB9Ym%K6Un={-gPXEts}zgXz3yGK)D zP)$%a$osbUW_j)fHIqw8FmojcN}m(_?Zmzc*?S4Iekk(t@)wG@7O#4`)DA)2CyyPl~jh z52yiy$P80fUzX`+`TPd2u_*mE=xmhNo8&Tg%LKgx3H`MZha;#LMCv8=N@9^{7D)dL z#4dF6&s^r8$oEV1&jtXAR8f0Vm!&pEA-3QC!3l%*ZO#|TiDzN~kz~ku``b4%=Vp2k zy6uq`vLI8~a_Ft;k6?P4vjis?3wvgo;1U4V+gtQfWpSZtXSszjx++J8jRg*3@pwzr z{Z|vsqd+HYiLhn!n)r7Q@Zo)MXrLpW1DTfK~Uf`IP$Q=feQr?8Qr?- zoF@~5pWed8ClOKc)7J-;9kjB0k6;BTN7I_2i%fzgRh?ArEQ$8zt2>K#kt;il*DB3E zY^$4731nefGgL4vkkAqoR9e2s8Ie^d2NQ=&chf8i&!S@PYm|A!cR+uWZppbqwxR(t z1DKxMTu5JZj1hi zi5PpftU)9fS1CFVlAP{oV*hN9S1`t~X}^mY=N&M!+F!V5SLn#({KO9w537&s3oJ%w zk4d4r*d2G47!u+^YW=UVQ5WFNhlaNewF!8)y;e(Yz6n5Rbgw=Pk&&Wvb043LzPEr@ z;fum~L9YFtB_j+t z26Fj^8+b(l8ug9wHOS$Q_+O%ONc>-Yq;C@ulhcW>U%s=!E5r{U~_%C*9JtJ6n3^)EZceQKQhc z?};Q4>T0qMg<=|XdS}trO^XwnZ7KJhfjvKc#=Qavq+}Rw>@y2qPHsFbii=(Y<+CC zwa<38UL`$Ew^Mtj)lRzvv-;b&Uw?FXadaf#H!axGEzs8xD5{R6;k5`(X$|?>WNw}6 zw!4Ge-;kzmYwf$g&Cd~0=l>^l@6^|r4SE!l#4&}YOQ-jTW>;eDpmnSTcAZRD8nZp0M4)S6B z5F&%<46HkkKGY~Mc}p)Jr!3VxyiB_mST~1Pc|gr_xSW}4$f>q4<4aObh-E*1|m1JL@B!L`|+JTmc#0FZ7fvnN({@SkMpuh@G#TwIoZqiD58= z@JKV3@@ncFn{n?^DxG5%9V4EqrtPNho)B2}Q1=UEdk_&yBN4ZRiv1}-s#4@4JNC1X zoL89bHiG&VnjG~D=AmY1OquDfxhtAc2Ll|M_G>Q<3G5T0JgmGgmW)lC32L$=zG{4F zG};6NRg1hy5dqQwLNz@i+(u>rP157qbjNmDeR~gOn}o*C^yHi#fLp9Q_7R98u7I$x zpk=^TA23Da7+fHDcHZ(JxFnyrhbKGox4-5`v3*S4b`Ufwd}Y8C z!2sz*7#ClEZVUJbF-b0zIEySv#o#du8XN{P$LPBz#NR%&E%*Ez$_9_k<`bW;4TaRK5xOGf ztPMer5Om?zh0Pbh3|_uvX(tKFk8!9B3qkz>YBxD9YC=WR0AB(KT|ZA+ZyZb(Sn$TW z=sjvq!fG|SPGqCCb31`st>L)9K@1i_^yPn1Y3*QEPU7-joe!@zRQ>jN0`{^HBeAaa zIE-_xTC&;L`Gg_)Kz8foJix9BUxt~TjVngS|YMn@~#qTFgeCC-(femmOM}R2rgZD z&eEXEcct1B9#0@g?5nvQZ*H-`HUAQ@Fu6h-DJX^JTnalrBYC5PKiGmk=(^&7Q=g%DjDew>_1X=y0oaq2}SI zMeJzR(Cm*9iaW0T%iEZ4^}p8dujLeJf)wtV#tlC*nZ2*7e^S>Er1GJk;_fZ-u$I)q zb?Nhhpd@>Lw09qV#7KjdRV4#dNcjWm96VM-Ckef02g^D& z87P0WnO*E2qqyP&W^Dc?z`CQPiEjz+1orLC)e?p8%lDz9_X?PVtJI3K_|wZrb(vo^ zH-tQ)Up2#{D?_cdpY?;Fc~K~A0vR!KwTzADsxo|_Y}tR{K;Nc$Zwde_Fu}v*Dq+0( zqNd~+IAQTLZip}Fh;wwOgsOuXJ=qVTe(nns`7NUxr%nSPv}?jZN;sf#w0r3#Pavb2+e<5c$1CCWpj2?UrqvN?`a@$Fmz z;++7KXi9@-y9hIyq5i3IB%MO8a$-!XrwKJo5+rllxeAj8xU7?FPjkgW6D zQGkLo!}2mSK5Y!pJLzHduG#*O z=7qvQ$B0{SO(CnD3d=Aw!~($jrg9KLS6Fm<{RxjLpnCfB(JZ0$2H{n}xa5}jCi#o* z^(jLiE*o&BHae_`JA~>eYN88O7I4cx7-&JjV*;chFDCY4=R)|1S!yf z@AA(xn55)?OWHkqZ$M28`{Q%Ed#Bz*f0c_0h@)Ri6p`>`>CW1<-+rTAZY_1isF_YC zNNUS-wZv+hVWKrAlJl{C+f zyvHBBMlOj4Ubyw94jSF>Y%P^o4TmA41uzo5JAXCvgm#s4U*VCAm#TT|z;81^WfLUm zv_ic1l+~tLDmLg52C|<;vYa-dM=`Hs8VA1N7@Op*BS&%bw5MUO@VPzMqkk24&cqQqaz=b zx~uvlk!0DC*c-9@Mei4U8m0Gm^pe0?%wuT0kEI^pAXU7`R>Vh+mQ3Ux0d^Qkv|5k) zVe*atjfC#DNNH;_4BiB-RadqdS77@&^~D6t@0kWlWhE;k z$v#HNv7;m+4k25z_uk`oz0Pre?$6!l{{8d)e*fulyY=Yse!s?bJ+J534zh_5sC=sc znnAE$1B7L>XbQG~FRS(WkEM0J$6hxQX8xa_Zu_m4mT7YnH4;$v5UyIL7cm!K6KbCS zs)>jV#vb5UOylKG<9gqo{G?C(Cz9ZYIoAKIxeSzDXP^=b25xK>nAf4ul0XnT+!zfT zJ5jnF25Uhm8hmaT7|W+Q4QgT)e&@;X@NF05=$RwGvCMywnrJA+0$`1Gd5shS0nkw+ z*4C;X53HR2r`tix`~=WjDkS)x_dNA?I6(9;rFaYQI>fEF?P=V+grdWs9h5-xb*aG9 z;yM0RB)tkfFyuz55Xd-R_BGEfBu?|B{se5dkB7F)Z{B@*CLo{a_N7XFm*3A`N1pJa zDE1JS)912aMsEe{%?)>{0D6%FbRnN0U{Q8|N1-Ff_tIEa!SBakMP_I!sJ2x9InqP3 z;fYg%*G$#;EW$y`MhxmF{qu67;0H9;-?=eCgh%>MK==bZ? zLFrGF_2f%*kS7I{>(g+41%U|BCvShdyR5LNK7xDDFb0jO5VLSOC_58k`b< zr&Q9yLSiqDy4hG>zevImP6UQ5=eNAs5ZejXcii4e3&K7ZWcuhdpx9YfLAdn9ZV?yL zSqC@%u>?AKIuI{jn?;@&Ft~W`liKt0(giCBBS5eLDZf#@2n!i)4w$khC{#@A-0Fd{ z7-=IZxk$&kVA%8;c=!T8I5G<0GKMTIZ6x;26MeMcRv%l#662;*E^onyI}dItDlUr? z7o>t1t<`83(q8O8L@IAg;B+sPak+8ZkNmip!2+{D=KG-xqPty%{sbl66u~9dazW;! zMhKiw{enRi7@ zbJuYuO{{7;MuBS0tO5uu1|x+q>2vVm`6$7{EENbsNFCF~Q4~&l)j|?LO}T8~%t%3d zS3D7PSzo#^>WXTyOD<{GP!Y!68O%%M`l-qE@&#LRAIMr+S-oT}8_EhNoE3ln(Tf~f zJ9ung-Q0-)`LlTy1ZteuUQ}SxAE_n&P2U|P8}3KnX-xNA@}$ItGfJHK6w|yaK~EG{UaATt*dHafoxz!4m-}Gt3k?P)T+pKdLUg zw4Ug=W(-!ltrKeR#8gJQe*q@g%D^me1GolE?kjT!be2Ln1qhn&gTkAc5#bbg`X}7O z3rGV6^XeK_Y#)M2Ol`ybfspeG8PHq-7nhe{R&%Llp#aZ47Wam#ZWLMiA>ppgOrMl( z8Ys=G!R+SfgthS>mkNa^oQJ)2(VRlC682BpohDpGbHED~tMe}=+)@<2Qs`j>N1WJ? z78HF63_yBiqGySG&v@d)04vamu42?K+8gOiyn6a`D0V<5qoty`4;k^ z7+(8o4MUWsX5IT4V|&V7_%4v)6Y99)QFsGvohJYcwQC$NqoNy%mDE686Y#Q`4E*c2 zABWx~7C^D4ESWF$-NOg1-4l`MRG*I6gnQ<a7R+2)r%OgEDmJNqnGGdGqyduEp`0-DD=;Bbad^RLL zEI=~(6AE);4#3|JyN3^+vs)rZNBZZLJ-GBA)Q57@l7-~qwCbc=edLO;BB%y>q%}-z z*@<^q`B5kp?yY7L0+l}=hnw&F5X+XY3XG%>)>pJR3Aq&SfWl*-3xYE7?xal^Xv^8} zFeN9;$T4rnKqAaJb?bZ6y%i1>J5!%~gcZTfueIa6y7OJ=7j1^>8B+6|Wx=Pg2!fSv zzt@w>n+G0v2vFq1?2!W!C<$8Ib0Z#N?9T!bOD%)$Ssp8`>T^4~^_xZBcfxwLg?Gqc zzDj1?@T4Y9|Lj>R*1u11xsOuuf<$VvY>Z$hRvY`<_`cXfMqYRg@<`5a9t4&AHz3B< z7C?%R)4J=us~d&`fSAr8EzLWqj3etK4I|-Kssf|k+7a-4mX79k=8tsJ>;m(}XLLec zW{t7PpRVYGt!6$rw&6?pcpo2iXa#E)qq{OJ>#HfA8Gm}pyQ#oI>Aye*i)8#5J8>D` zfMV+NS(ZISO#{6;y4sa;ETW?bi+=3}^huTlLA2-=-mXr#9H=H0y7yDFOM>-Ek)@ql zB(J>*irdjNFVhbeSOrg~aQdgky=6$hf|69zQ?J)tHEovFa-UarT?e`v132DY{`O{< z%G|?Q^?9ZoDN!C^B9NJLj{d#{ub#iRvMd>E^)%W9###NHK8b3P0 ziVK_%EO zc?Ii4!q@lU{&@WE6R~zl+)L~q*TV;nYd9lswH;@H9zrm;&j~7nW2eKRdEZ%eCULfN zMoc@gDR<@614Y2`1MoZ<6xcRkAB9oD|L!h!RdJZ?x&ZC><@PzB@1s87?Vn0oRdBgY ziLyB!0ZlNb2kvYdtKcRE2{LZ6y7Oub4CPA_aV?|R0C*UYXkU| z9#~p1lp8|z8v;g<($JTWua`&QO$1l?GL*IK8cOA89Q=m3!2V^`(i>xotPtAM(e;hY zB^nasMn4Fq?OMNs^b(54Sph~v$1&-?efvzQ|IASNoDHR})5QIHc?BybuC3(rrC{hE zJV)dv2wK>W!-VRwaXsIIa4(0!w8ZbY#0w(nzLZ!!I(crucp;}8_>Virwmq4!UYRo< z?#RzB&K~r^+dsr8;j3ABA$UY$X0Xn3Ab0x%%=LOHp|4mvX~!sy#nJB4QvUfLiU!%E zKHZ|JxHz(Qu#wo#m0k%7!?&QM_>vX@Xw)O2ZIdns26SOpNRO?2X~A;`B$QUdZNVKH zTe=F}R~Cnas1=Z-azaHT@xmQItq@O*c8V-n>fLYq0t&e*K$1*+wC5~?H;N@+^JL|9 z7NyhjIi!&mId?AANt68;o<}ayX$A{du$Qx4=1sE4#w}lV25hC}{^vH1LB`UYnuo%u zdjVnCk==qbMK{aA~j*VT}0$)FiYRAB6GhI*Q~8x&lRHTdhdGa;v`js? zPzJPHH*{mEy?6=u;g%#NK2PaG;EdcA ziw>g~>UkELNWtHJJcqR!Y9)gVS4g9K2jy+hg*#X9rJ~T`=cV#Fw|BSQ+T3k?_Pc$< zJ{jb{38t9&=?5LPQZGXlUbW`l(Bp?u#g7tz+3ogFE7(2*Mw=ASuRaNM!Teqi0QLk; zuzh?7c*3s<(H+FA;;-lFwS?f#O?QfV6@=LAD44|ofkBvlH+r7iPWx=_dheY}i)sf% zY>=2>8#r~tGF0)9CpfB8i2k(&9|CzF~GKTMO>8`hsza2%qqAHlB_+Cka5H%pb zj?&Q@ZbD&r7dFoBv9Om*o2jP_FV|0G90SHR9D^NnAxPp#09Qz4g zyRS;G@%C*NL-lV04>sUV`J<=uUuU5g4v9q^n_pM?AqZsAMur#(zE_y!OZK)gJB#D6 z4yQz+ml7INvOt*HpWh)~$3o|H`EWL1Q-BhTPTrjMpxUW&-FCWi9&BJ!Rq~*zLWRiH zcT_KO4hQX4;Ag@5A{XFn7qCG4G(2JBF2Nx0L{Xi*VnJ!UC;_U8`_v^kr3^nr(X(LO8Ae{-XqZ-YXVY z7-Q4F0(D`JlA(1r;hS$@i1=9g6H!C62M+gZN!d6h!pgV_Rqyq8_lRoHhg%TshMr$- zPjyqO3c~9_c%2>=3>2QmxTa&!Cto;sJD$Q_{sK6nnZ50Oy0|f2nG3&rqKfCzuRV%t zVv}c~@lWXWHS9k;YLR<52qV^D43{0DpbcKfS#)FIs=*zGF(a5kgUj(odSHR zxXs5`(bWli&MZQ|CJb*HFn}h+B1fXsKbWDg`75){{%}Bt*CErvFKHv-JaU9x<7ZpV z8T8xy1#AIuE{cvZBMAWA@h^V8CvJzS(>-%ngNi#2rxL#2eiI(VHomAkgs~=kQA5rb zat9sC+i(6=5Vie0kpAr0B_vkYl^?^FKQQIq@qn#swUVv{o`FY($<27ED4CVe>|8N~ z5d_5dx_&~_0zdqv1UV?s5&d#MzAKmY8&Jm>p~jy9EnqcB)nwc2A26V#9ONc^1q#9t zOg%u4pwdc>_+*?8S*_4kX<0l`CVWkD_M_9$`%&L75I}GEV!$V=`T|;9 zBxi}NuSFJfGcXGg9a*_&AB7tEUHgt?a`Xa`V?eYUfXR!a8mY%|7=-H+OFS;X9xpCFVY;PAV9AewhSyh8NgA-+|!Pe z1JO0E(J~av5m7Iyay&Z^+V>m|eV~Vsf7eSYifX*gDz` zb+y{U8lOI9W(T^B|2{T9UP3KRPU`>dL&T2yRG2oUFafom3~vR63L#^jX>0~eUA1I?5 zbV%L(eU9@?^P~lMK*oc1{$AHj;K)?$do(mzbW*PHFE4;~=r`nC$r7Oz6+;_wgIN~r zMGk&4ntut7bzM1)0lpZ%EY`@O?oeRg+=1k+vuLIOH&Qi}3vzeX`8Z)aD<+GpbU-yb zHyOI|JO$pjZwjlrwK!YGkP?)ripflH(W9JBr1C}=lUZY_CIF+Hvny*&6n@@)b`2C! zyH&~~xQ-!qWoASsL16axL#IE5+LkkdiASxWUH{in+OfK|H`Mui2=D3(XY>xjYTlsV zTC;}tV9kOZ2@{?BT;MVVZZUha@B?tQ8}e-pvmf+Sm$Gm0NLW?m7pG9Y%n7=!mjLJ0 z&o^XbK}JKmP=~xZ)r#08?o(tiq13>zP&RJfNA;#-bKXq1&IE4IHVdmA};SA-ytFsT`|EcU);b7 zy5d1XHnLz5)`fsS@c-R#*gPH)wXAXRgkW07qWJWj|A8mX;fB1E5g~ zLEJ3+TqBAED2)0REZv;?8+ zUPU2Y}!2{apAFN(J#$L6FI}rW+mZ5+ClK0p{U1z%9^LV4gaBh;yPA}=$U1=2lkO8G~D6D5MdRBKxaiqXHq%y&o>> zqOIH;UstHya90P3nKzd>;K!a#I6+`^fC7`yGCWxwJYYx^g#PD|5Kp&{ErdA>`PQ7J z`RTvRS?DS3;S-YDvk1D%`;%zgCa-P9L|DPM&|IM4q$}Z>ExjPF!;)EzUg`fnKmYTo zQ&i|hG*F&u5wWWHi`=!-P4(ljCFAPF3!#JuK;eUCAHKCj&s`lkct_1f*9e41*eJxw zk&nVr`F~4l`+sxWhir3eu=)0nE&5kI@d&Y;PsA>@$yza$^j+g1Zsb51M$lFTmg+7L zp6iqZ8b*vMPY?=det3KzM{OsLq0(A{SXQk~t5pg7QN9sR1PTQ3<7agzjrJj~pi!4# zAz^^M^aQqlj2&Yhirw6agM;C|5DCs5&c2Td08_5TpUEeKFX%rK-#O_N%f&oG8~OKo zBL3HXYWM)Ho2pcf+tFs2i02{r3*P5|Rkdt0X=P6My$_*YDe1^K;WfIc!PW?M^)V$N zNWMwL*L-N)xb5G61s+(p+Y>X~XXK+`V5bmgbBpkATC>CA9PE2dfo@m&qwrJfkc5a5 z!PR{Dg%`V9Jn|6ictnc?(kT`OS^ z>j2Dk-r&TweS|HvwzZB*>*$C9TmSQvI1rPzLd)4o4!MP9oG0=K$MroU&~>!&gSvw- zWf1-WtwWqeWi;V8EI9+k6Q6ro2;p6dX~IX2IE)dp8UPk0%wU(k+dg6YcY(JGDpaKv zoHX`;i4Db3A|yR^4`?8PW1n&~h(>}Zpo^(sxROXX;(WPbZK=NP|44kk#A;3YgrFJ^ z)jw~Tt>t?V6^{#hX1Zp$Lf zQ+xK3)J@kMqOKJuEI0Zd26#~<<%Ai){WHWZW8K&I!tY;+1N0bFim-C*Ay**W&yJpE z1cAif1MsiHy>NGq&k?=krO5k(P6L*UmFvXo+Zz`3-8oL;_X>%-W@F<(|Hi)_j=qrL z6Y@1OOX2`w=O2sjxk-3t2aghd6&@UCz8%^)_jmDEW^YUAX(rxH=ixW$`To0s@YNPv zK9;WcuhManog1(M$`u*32>kG(UTYil5=deW5k6u`w#ABp@R@<=@tw|MliON^$KgPW z8Q#ZEj7p#(E@iQ|v#kWo{(rWo5Q;2%d$Ju8c2}z(Jb1v!!}C*XA@Mj6nkE$7%Zj(Q z2P`j$NwSw&AY4w+a@IjRn*NBBaNzyVy+Z_G`RbU`|IY0B?@!+9CVp9;1a7dfD)GYm z=(CHzTap>q`zfm89pSWk#M*N$`f*zTs2b6xU&@4YmE7^**4+cU4b2-)cQkSR>y1f& zm(WeBQ2x6VgO__2=t8I`YZ2#5|;Rl~VeFQ`Jd>%xU4bRxEa$coUfB|=x z@B;q)Lr0h%q&t_aUMA5!=#l<0u4AtN!}02GWyJU4dy8b0Okvb>43_0t4kEDcdj>te z7xLeTDlQ86J72Po_4nN+{GQW{#1e*Iz?$ce>T~OCxVKj|#`3^hsWgkzxWlPMC;0gZ z{Ijn0EkC=9JNeNpu%vww;CRaM3^1c#o)EXuKXe#pKl;k7vp^ZlA?e;Ma9!W#HHDVw z)MD&b2y_VxtWj6@aw@_D5YyUv0H?bt#|Ry$AcF?9)(lMgI<`>!iseg4M!Mi;(H@t|>?mU`XKlVyUqkhHMuOKdG z>Eqc(+AqT=y7HNP7R;}%3T^0l0Gs)aukO10;)}H$$5geL$>rXlv9fCdiGG9f-;ybP zYDWzB6`y|+%xG_VSg|Yw0cSmB4{Ni)0%^kGMH0IaT``zt*!=!N28mCPp<&_vA!=-8 zFxgd%f)H-D5ADV-Wr6n;-n0m44g4^~4ifV8fVy5dZ1wSnX_&(;iyLW^rrux9n*uW_fWtO?+R&479`LeL!M zzIHHjyCVPT(xJU)(YlPah$avfD5hxPC@CG5wm_SBuMu&(ZC@GwR!iLVf2@TbWx%vl zj$*!q5GY%0EZ>RtHtpy&W&F($r^mQtHGOaR6HS}4SyK&=efVe`bjvRH1RwoJ(LInd4n?eV&H*S!4^&Th>klLLM zgzn2VR+V7x^A6yhxL>4oQad7gK@}!R{Lj?dlL-3Y|kNW3gF+%<@(lS zdwDRhT5QLjTm2$zU6Kn>Hr0355gw2SEG(qM>*lA*KlJCRL0FGEvT*M{yY*$v3ucXm zbT@@mkDoYU#)ojXu-avd4xa3^ z#bF@jO9LcYY(A535B`T#>lwf-L_Se5g@WvRNmxjoUKq+3IHYe~Rx(h>1oaT}w5o_( znrFGSGWTB>AO#v{>r`^LNig9Kn_oXjT%V91?Nkyt=2sS?u(_J$mOpndKxgee9n-B| zMhn69Hl?8K(oK_Qfe7Z}1Ci_4JIXnP%lO1M`4{^N5Llr^~Q7eB&*Y4OzW3-K>~PDNGcEwc0u zoP(N0_lit|!B>;(XGT@zL#eWTWE%R;>DR=$Yfawj-dW*SuzS$A@lN z=Y*bb3s+1Nbi7LY;ao$wf>h)cY%Xty1eqI(!UL4cDCG?r<)@#GYm$uPG;Rt4R&KcH z2|+hipe>P)8CjvYtye&uAQ1b2ycYliHy+6P0&fi_?Gm>EC!lh^1W>e4vkQ4w1F(2$ z6yngCX>3R>8)l<;Y4iRIE?GR0!>uE zTm--b`_8c|8H+fBS{jhMJOhBd(VP~^cdXG--0Rw~)l_vO6B>;1C~n&3;xV$6{{>(vNFP^xXy@pWkyzTE24t~>`gVU?@u41?Sk2z zgZ*JkDy6agONI%c4190ieg(m;`fE$m(UEeXC4YKk!Nr^s1He|S8yImXxC6gLd zBh2NPApOKZ8TQgUn7XG&7JoPW*$X!yGJHA?1L8Z8D0U6`el{n610v(dhtBqLI4i~K zu#Je!eEhPWm8roleOO~m0N2{I9}V9{=4R}_NuMu25Z_AF8ozIu2QNp{Tc!;6d@`yQp7i}aW%B}Y3c(3n=yvNU*-bXIs3s-HfB zCXiWjxaN5>FnwzZ_%VFBidu%b3REXj?Sh-9x=m)CCbl=|&PlK02J6=R4~(cPTXYvO zoD16BI*L7xHHdrj7y#@$sh?Ap?qw_)GJ_&HR^*|q_!0B;%6bYJ)nHR!y}2R$Z4e4& zMnsx}Ua6e#Be2ut?n<;9f9!{=hRl*0|8WM!MzVZlpm8+j`l|C-4Mo$zXq9xicG{Dg2g2})&0T|F3G{GSm55D$jjCqR<%Qy@2KA8tQN%i8v{M=j>f z(%93Az^KJve4Zzjho*x{#~Q|6)@g+CnYWCBcdK)`{pKC^0NNFo2sK$FBK_HO&ceCurdkK;xy)=u4YA^^C6 z>TuuPD(ss4YyvdJI_wwayw5kxeaZq6u&vETjg;CZ{mYs0m3Ib_hE^51D^lTCN2U6C z$C;d~CHiY7CG-c9*qL9)@hyZez0-3ZyC*3U8d`f|CHDF^Sw*$}-;Z78RC^{%C#Bm* zcG+cfjS6a=l8e#;5z&{D#wsae_HVH1(rxkkid+MvUL?qm< zd28J8?LlksJ8+G*f%6ZEcToaq4j|YO&uF--YevaidQbv(y!G!d%sqhsl;ZU*!B=+2 z2^W4c*+nr^(SlS1q;BsCiuY zhS6Z-ygC(14@i)L+ZD!$?98$6!9m%${R5U%7foLdjz6+bgINdC`^ZsZP2RTr;x*j1JM$@V{4!^yS?->fdea1Xy6y7YJOS_7LNo;@x36EAl|??FSgUq0rG% z(Ox3&f>x0#ko6L>zoa2kr4nHGC!DT%>n~{X;5;Hq9fd>sB@7E5Wj5_&r&+_ZUd2=# zx25-hE%pe#z`c`_qQt}2O%h5MAKr&DM8@=c;@x{|!o}fVo7In&2;Mbs;r;yuG3Pm7 z63obfn~xBM(=-~^*oX$l29=;|uI#I~C%a2o6n#E~taqDV6bZiMw-9b#P-36EW>9i> zy5-%n1m&2!-9W~u$~%eR24=mPa=-p~aet`=2Z837W#pnH>xpTh&(v(IGkhG{MM7@P zI?4p;RgKfjtZroY4^vB5FK4qb;heA8zJ67l@O8FAi+eVmBqCZ@F!%#^Om^KnPT;%- zU()>&W}n_9kp}ud3Y6Vb70#L1?h#!U0)}TlLqws$*jGc`lF`@7h#c*lUMZ)RNg53#i-p6zlK0VT$P59K4hs$r z&4M3FT3MK}_B;iY19dc)^%=$oFV)E`=d$#=$*LMS4LZ%EWCsQy6;Vdh7Ti z)r6;RS3IA&W$6X+&v*DWSoiSTrWt=~|JF6G?W?S*zu3%uSyxXY+x&CUWM80?aqZf( zz$m@N<~pz|$8cq}=K^s+9J#qS8$W&yby0*f8x9&%rtphp0#0wqG#;`+BKc8l&YR%f z3|!9w&#PU|KGEe5shiS(ILKuKwaBNX!_42zsv>3Pm&+8iHz*F7@w5n~X(^Oov@uXw zsiuxq0)3!Lg3g_hV6WgzG`Itgbu-WvMHkIA$S_1i33+zye9F}kxBiAC6l9nY(#)u( z124xg_nbB<$T<-mn=B{?dw}|W=&;ixYl^7i@cTGuG;E?kyu^ae??LnKlHwsxUZB%f zPgrvQxae=+T~G)7A6Y*4gP;)z2g(%^jLwRt))?g;|9|zaC0uhL31uN<*k=ZG?f-^H z6OVk}AFO(;Z?5UsS zImdZNMf#uFg^zt&Fjf}%9wo?-QFKefByWgqN4rWOlv%H6p1XcERi+7Z=(bR0p8c{I zAWP2ranAK7sljv($@}G5=b-*+{szNx^;K0(p|cDv?AkB$O!9)Sn&uv=>v?+RhgCI< z?fXI75eg!*NcS_88d(YpcMag(2AyjxUFfUg(coZlba<6~)}Yl_`2Jof zhe&MNI|$X${TQIrEUWq0@S#%a{?@!9Rzbsp_MhYeB82I65pcyvUX(GB0O=(P4ia%_ zdYkRhmW&dNfK`Y{#?JFC*FJ%onY};d%0BDcmSrKzvp2q}d`k%X0L&z&&w>TLp9AUi zoLU@fKUkazt)g^}$!ovxx+(9xpnL!xowg ze3ihpV{P^=+@?t9-lBZ(JJ>wwNM_fh!p~N3wCfchR?@&kzpfDq>fpx8G#1?92R>AL#Uv5tRxb~DBzoW< zD`DQ`mL`fjfv!@ovs)6hcNT^$#Jly6nrclQk*ORw{)X(xKh3AIvM;EB}J)V2EPO0)6Y9^7^YR9&poB2pY^>ZXO!pM+HLW|DEoEGmeKQ6 zrd*4F6##B>Lcm1{#FdQ2F7uaHZ!qJK$DniX6Ba!vRt&Vs9E?7mIo${(epXO87pW9` zKhjF$_owXad3q`XtPB!(Ww+Gx(TP@f1l?D1k{RRJLeHz6h8D)YJ$*lLj=7ptat_M? znj&h}LM7lb7l0IX(&yU2OAWjwT+FGvs<*%&*=!JPeg!i7sS)-pDvd08r^N})WiU0A z>jwG{<~#V)UwMov6OW-?i4inJ^wE>vkdEW~2whE2G>t)r2V3KQ9U3Uhs%#A&@LQul zny9+;f4lGYEa3u;7%OjANmqCSgyQ)+xLXWyLon6P9Lr8#JyjDRc{qHOY==Yfa~t<(U9+7kFTgY(mW2q9cTjf##dejo7^>qdb!)qwpZ^Dr!`=>97+Um4h%`K#dBf=9ESGIhQ9p z63v6=!r!;6xNdF;g!ATL$2KhH9 zsGaFlP)CWg=!W8ex$#fyN9_)pOG(pgHp}IoMyLw@f`NEV6@19Y031gImbWMZ!vfDN$TnVGD6xKK%v7x2uW*wwCtt?fc)UNr_pwp z@cm0?0jw71!r3hM#?0J$dwW1SXGJPI{Ws&C?}>fA`Hjeq3gWOzS_i-qflB*))cKh8 zMcr(yPVKAP!=0~!c%&}p+fAp{9K++4tntq~CD=n>JI8R2Yk1b2Y|(Tu)3}#G1J+}V zQV|_m&EZ6>c5`CY=CS(iJY~=#ek61UCD){&zR#=WF z0*$~Z6c|2wuihTkz@LOORcFuP9BHY|jfocBgA+3#?m+!R+HnDw93`xJlYZ)AD|k{B zgq|9NB5`0F5P*=eQkZL@+wyy`3sONI+1chm4V;PDT&t`Z^@lC_q@*>4WPeLF$jg-i zL1uFcyiLxjhYx`NEB7kYM`cQm_>l{|wcw!$3J~1^ES-AOjZdd&%%1onS8lOS=Xi(h zO)2`&a0ig#uizbL=A#a`<{2>fwzR6^jTuFU16O8z8B^4c+cA7FVG0EzcSLLeCJ8Uq z{jnwGd~v=j=|`8jwC0>L)-bN?KHOT;6E0;@ER#F|Abt> zuL&b&LE{tRcawdz##eW4G#TMN->L8H(OuVv9RO6i=g zs^;2iZ&2C7rZ@4^Cq!7wDeHC4(4{K-YylzToBOXVt+yEDi&O^A=!CNZ3VwsF94r^B z&nHOI=#^8`#APPHPgezaSVziRF>YL3B{+a&lU9J%)HEw4bLf3u6|V{y66=F=S62iM z>nCX?E_#F}DRV+`2MY#Q&Z`QNymw$4-5iI*VDMLOb4}^Utt7~TJ3dxi7v(-hRG(?C zu1~=Z2>_`5m8Q-i2VKvpXY_6VvqK2o8iir zByMM|m_+P{#;a+l({@lRbTCh{{Ge9Z2e(HNLLs~EWDzyMB;7~hLK@%rHA1*>PH$aE zpI2J%q=p?#N9J(pF4(dV5MTT7j6-iJrfr*@0JdAM*z{Pc4_7VRR738Ro;?e|dNX_0 zjHDIQNE#50FMBazQ?To)!3)KLd#*=v;o>t+j+S&WlSL1QDho`1rtG?s&3<%OTtiG3 z`ykX`HmM_BKxNP4=zhOoyoByCah|#askG)N7mo>WfQFv6 zzq!l)4e)3O_Lpe1T>68%)e{LAtXsDB_JI^6=KW}QuU~oC_il!q&C2OR_e#@gwTtI|rk$Fqx=|I-Z(YZ1usoKm``$`g``Y}sMT_?N zJBwUJfglUan+Nb3e1~K|(a{{4emzIK3#NcZ+fS^RTctN5+X{HZSGtqlje<>_aml&w zxz9(}plKQ#p;b3$1X^Tzq-f3P)%_#1^fa%g`14G4*FRixx5exL8%&QKab4NoifQ{e z3pBf*;jNx}Ya!}6RG)U8a>$1pnt;sITvgh6Cz|G4si1OFO|5RhJg`gg?R{!pr}T2jo%%!2eNGO zaviVFLY7u!iS@fh138+5BOob5Y*dCWo|dPAAeSYb*J!@vB(}{f6XA22ce1x8i{0Sa zG+XQOD}}iDScuh7-%5L~z3d(pu4l1yBh6CUqIP6Ss)L;%@@yZ?S@E*N>h)Y3Hnqj{ z?{X(=nV(mv`*0}>=!G*+O%kZe=D!8;CuTk%X~VFonP!S40@?i=w6bPe!Ois_kp$ZX zi+huyTj|gp89Xf-*BBG+hCD~eh1TkoFz-J&?@vd%invj%RBEhI?wM2?|M`vvjth;8 z?58=6L!7Z|7AHj)o4{7on-x-IlT#*~`PYNk+!ZP!fH}_su;$fDWL%o7js{u4kj@DW z|AW2u7rVBB!1kGY9MjX|Lr0UVvQkBuI0swwTp>J;xO3>DfW|Ot13dI>R-5IjUUCDG zHmqL#HtPPF3Lz%@0@)d(k-6!Ggj@X&M3`>|O_k-2=8Xe1bH2mW-*5NImugVT z${xEeN`C2Drz-`0!&TD^?NauYgiZk54v+lez8E(SqjV}0T7l@2OYu}U=W0Wf+;r8F z_AI{t1*2(ZE`&2)+##&x@H|)<>IEx5kB5=oFN%s8QZOqwQUph6l~;CC11VBW4t0=i zw|)|2v_MruoJeg?B4Ln%bBhGE?o82GMpb;6EY+y#Im}w0c#u3BjMpB>wjaf~+5^Zu zKJulx8k8zh(4*DHc`!Vu^3M8D`Q;3dy;1>UP(p36s{|RF+MD3a2W;y2w*X2Tl)|qN z&T-Yo{elCG2?xFf=>z7u28Wi9xmeRZq~2BF>*@`K0vXOCmJK# zJO}N9jQ=Tt8fv>?UTpQLuF%J7t0TC2xFq>uzv9LTss!22wcc|}}( zMPIua(H3S){Mkoccb??6%e*p8pwInXqV*tG5g7mdA%&Q7J98}s2v9x_KT?2*v)VUr z0gfHWDr;r;S5TxfLboF59HpW3V`qYV6zj_FXpc|ru$13AP%J=yeg;-X8G&$f|2`!U zDqsIJC32nbClVpz6wrn@zT8#r8il?Kch=w z<}TI)r=d;VG#soTXq_5>T?IP9XC=jpWT)1?M)BnxfGbFy%b&)(z6BCO7eLmYwuG&0 zFa1Gql&pt7ZEP4zq8Ua41a7qDK9>aA1Ni?|!!2OaW-Mg7>p+WC>D?qGT~}7EwC?m-9@-CY zPoT~Xq=u9{`1zH^>kp-QVWZ-_^#|%==;BuLH!k2y&+&>gf9|8XjDI>0m*&OFy`w%G zFIvFUl*<|DDae(aA;NT5ER3AP=4s0JbL^$;-S($jh_52FTh`Slr-Kh^Ji$?2*kX@a zlGI%d0vWrbKQ2bQyXZ#>o6yG6TW4L-(k8zup`CM)&2zN$xouBNo?zm{NH=kpCboMy z9fp}f@9^cn*kgET4E&DL@e#?%zQosbPbhFM_xySf0H}qXG$jjdrg8VkvCm?I4ed!Z z6X|$bTFQV(ljye)Pvs}H)U)H9YG!Ql_Xzw`S{haSFv#reQdQR=k1&FDZ-HFtF$0*c z^0L9Uu}$4w4165?rrk^t`}iaf?IWL_P;afUZ!jT)9&7)!{&gA)xRO+QT8-KXweNehXiA^8|7d?_Uul?1|*4`&8cf#wyyl!L~5c0r>)z}H*c z4zkJo`yNwkU7Y>3KGa#52UyJyC69)6DD7#w*awyq>iD8^BA#ND;hMC3WiRCNV*9Rr zPFLo1d+CTarhT%;^oYuZFDitz<-7FBt&57@9Ny~GXwgCCVkn$ightl2Y;?%yU zr_aP%8i3!C=3M*69N7o(v=uXOY&(4)$llP+$xsrhhx9$OKxY%|7T2IhxQXyiqSBGH zIC#F+dA+`r3b(gd|Lc8U3@4tWfBJwA?$lfGMHqNEoIZ-~Tfnxjq;`3%n)7{)Wdp}p zW0gzIG``ld8;g)b#|V;sUyKahG4zlReLu5kT57!ZmXv*sAxF-2fMY4Y$4jEY4c zHKaMD`>&{%PzmEx&e0gHqR5L$tEWfKF#^jnu8ytlT-sDb(*mfSDBbHMShkzJ+UNg| zI{p46qJwvqkovOO0{xo=FV(?Fc!W5ZtOLQ>s;c*$7e>SQXXYCKaauN-%boHmEs+n6dw5hSoR?wYf=Y!M<$+~5rbrMTGtPp;= z;$Y5@4^ERU!^m^Ur^2$U=$wmu3&+iz6nf+{FvtA?sprUpP3J?Qj)<@|ZrURdBM)#N zw|PzZLps_kW&zOuMOK`+W)s#jGABB;qR*-RxT2s1_%q+2X&rBw;(y2nqUI}9iV&Hh z%Uax57o%O|ZlpOW;%^HXS+?I{^+>^F7W?fNUX(sgWDnUP;~3St9sZxf#Ic8SfH=lk zYW3@SAxCkpzUJ6ou^b_6cAQ2Ts22!v|D_=FkTT9UGG4I zj=G;zj$FV%&X)k|Z{|DSq-rEoa{t(aQzD4y^7EXRofIOK2dq4GCF+F`jT0El1yR zx^^)QqegJaNotbVO@8V;5=qf^gYGrw{S&Hc5c;3jEKmZQUyHVp(0mmB+@)Zs;UC3q zior{hqWM-kOs{S@KyCdB<|wA$Q$u9Zte)D-k}(?HzwwX}@>n_ikeR&O99{zO;U`9_ zVl2g7U@i=*uDgX=8M}=&Sg_yY=x(=2u`!$eyukvK8jLG|1%<5!>r;?^Kz(>!$klPX zrDE}>=bG)|xXbZp=VkIqk5~UtzLk7HCHT?JksG%=pT^_JSXgYmdm}C%ZP0rfiNh&f zmy+hn$16N3-yt6KYjbhAz;wO23mgL$t{G0|wm43{?a4^FwS>H;pV#ib5!z8*dU1kLM}yy$Z=dw#E}4qV>pj zlJ-GhQwM6lhrU7qbaLhgP4r2~v>tJE36(GGR^h5g{Do`dIgNUseGeTH-2no)5ICRO zJ8XJBdf9Y259a4Bb}b^_Bt)Xu?^NTekMrwN+%(dNc~jRQzIk6`$CWPARfzRf7Z9h$ z&LJ*Bb{E)tAUkTcMUQEW=oH3u8dwb1A7G^SY8QdM=J)fenZnKKnx^uN_r+nddcFMG zuNKIXGOdT8^OKe`X}^<;r#D;XvnnVfWkFc;oKmRJtWd0v8TV8C19CAS+^OcJaI2`J)DReCqU z{Z@*`1gXrn?br5QjOmyBMBLkcJI!K15hCW%YETo#u;S0YBJs=cwe^h;l*OKA699>y zxTdoac1OVi%vHd`>Rh{h!6#Z?{7nfsQaWdI_St7>71u1BbnIZb+F|t&Z95dVtSI*j z(6p@-gOC(Z9*k zjf})UvHkM9ii=xpHbpQ`cz8HiJ^h-J`FW7imwO0)=L77qky$Siy+v}$1KB-+li43A zamw2DE>s-BY2L-6*W9p3C za*~Hu2Q=rpXn67OKnIIL^cJU}I7iN_j(Hil-hJ1=-$2e+&fl3@AOnioF3q$*II%ks zx%lJm&&Ki?%&Tk@>5HR2mdUuE8)dGnFG$@93;k;$@uf_9YY@o&0d8JX%jcIb#1R3( zy?vVZm(Ncc5Wh*mzqJp#&#|11li;2@bk6C}Iou3PBff!+mw`naP$7ZLF8+BBQn4yA z;2-`PbpFJlzAVOe(QfSLtvUZ6xhoHjnNm~zFp-D5!-cFCCkzD&h0{d#u;5UB;V|^P3$!QO zIX%{#z#!(ywFO8^i7372>vWorl9^)E$)w^P)HpxZ_%6dyb564$r#+$L0UIiS;IRJj znojGu$>zrTWdP-u0V%ARR%Oe8S%E4Pm%8ay!-m~pwWpWDIxGcV4*`nu*D79TXwMA> zZfHEsF`9peN?XxiAql6W>IDB$7H;$EgAzn4-SdS43ohsk88I7zV()9=B@U?NcUt%w zQ4lJU1KWSisvQWmA_(2KZTF(QE4aTSpq`#^Cb{kXDF~T`(C?oDY zbgGn+cvlk&@g3*7Dwcm+V~wVu{QU-I@%IjzioXQlv>XPJ%%lqsuv{>cbH}}^iq9zkVdi%2Klc+Fbgi__kFM0bW4}_-baO|A5i*HBC}L+ zwhO~SioU}z?mCM75cdt#*=(pJ9mSXQ%^PaNGk*s#CPQ8aBr2zAmv-&6fh-Us4+CR+ zDl$9|mHON@01*!4zd$m{zfbdRUUvb4C2e-XVHt3}w=;=dsBq7&a?d~jVWvcH>>wOQ zr}x5Xen|cv!NC+BD>XdUH3~c-+|29P*4c8Z5B7c)t!2@@gD+rn0tg>G{#mk&YW>W3 z;Y7i=*X=pRiujRZfv=z&e6mrN$4(;+Q2_>DjW^c@h4dj^&_Ekl^|JOm$g+&=9|99J zf6!&~VlnvVUiRE@vrR)5C;)9N7J(bKB3J|)e}L!{Hi!eV@po3?V$6mh=VX{tp)vT0 zYS3eEZYzbRAdZR(VGOq9*qx1_HkkC|ykfL%n?RM6n26F_RdPySrlHtSG{i(UAF~ph zE0vppM_D=$lXZIIf@r*LYv?gA)h~^t$NJyxqFJc7_RlQ{R(!+K?066C1!mezGs2mT zlLlbL)HN3YHO3dCi5K5t3vYmBw@CB&8VQ8hi|tPWA(5#8GDPWD{M@k)Z>VI8L?{QD)$LKP ztbr|%er*7#=k=*^B=a}&{amQwvD|MlzjM+9n-oiObP`HW2vmsoE&SFV11N-cdcg?m zVpw)BS(1k5`aGJQ+I}r81#hK>_xdt}hZk2No$oA66N*|L9`K<4`VPqra{k2*Tbqge z!Ge~|!>klC^&%Pd!oj;E?T&-9;%oPXR)fxEbch4Y<66VqWO0IOv$XPZv}N)9D_9_# zRaw?6lU+~+n7%N5e3C2>WdkV5w?Zf!|5DGx0ad@F7vjOhvvuOPxo8?(=;Fpa($o{8Da4uxru6i^0h3ugO~XCZJvBIL0A7r~iyFkp2Z+~X~* zKDUN9(z&`mJm4}RPObTJTqp+zeF8p(rC^A255#M1Onn?%nhbqH$x;oURD*$vCmvYf z1_BvnRQAcp(AWVgmN;cvyIYm0v6W(p()+M{m^F6u6ok2cANz4PaCYNyG}8=_g};Fr zl&*tmNEFy|aP}RsneaEvt@W zKk@6%*s@ELb!P>v0m@+Rh#$T7_|9CIBR*!6ro?u@i$T;|5@_e0ibY<>$>N6VN{swI z*g~O|p#HK;(R0J-&Y4{+ah*Q8|L9kN7zhgK>F(DoE<4R2Be;y%po@(PL%xIaD{zHuvSL)40CRVG5cNO*A=uHc%kpjJw5<~vy^94O8?+4Zs~l-9AWQk49`o4N`10+s=OSU^xP~Q za!vAku?W*q^As(knaq+Q5z! zSsImdgfrn^&JcGbwoY0+9E<+60W@|W$3)KjV z^Y1%*Zzd>;wvo&8a@E2?)OhdN5W(_EcmgUPcn}@6JcL7y-U(GgQ|gj_#QXj$Dm)#} zd?)-vdrOE-eK&6Uz%X} z0rZ+a*t_!+-#=_XdHFTFPSA8A4>%BD+7&o{!mtvg2zmem8`uIQayw1+(%{Vxlc15L zB&$G~s|=MW>NUj?PVUosn=q5_0~CUD)yBkU!5Tc<22PNbXxQP!85WnICYPh4HXH=| zCxj_{XYTeVb$WOZDt%;nBxaAFZX96+2Vv*0&M3bG&cpmB?&}BN|B4jWs^q-=$<8e5 zqdH6g;sF+Fl?T~e=6Rr@8`MRQW1MZ|ZBjBdtjczMXPm$d(Pl2B8isfs_aT5uw1$pZ`qN z-ib0lh4-+qv3)aFyK%#NRXfqjG2!deak`?!uNUhH6`0p{=+3Ap0=^Me{C0+qZbyCZ z#U3K;k6lOZ3bN7DhZ4Gvo!37uEQ8A4ECY`Q>u_2SJj+2hI5hc4SM!Ie=&+ep^_prh|CI=F`4Hn%8*%R384^5$dFkw&+`-w z3Yjy{GSBnOUg!7f-uHcX_q@-$Kku{mU;7`QpP#GmbzSGV&b8LL*0GMGB?E%R1@+?m zwfmM+C%hc@tQ&7bb`sqS&HTI9KbF#Yt9Y$Ubus|W<|T6f+UtV{FF+l~XN3>+7!q2@ zn12ncCjX2_Rh1{p?;Pg8*G``-o2V-;-t?}SyLIxPhKT*wokXz4*!hu7Qx_)APE*`$ zx^pB!+qT!QNZZC!*a7}^;8FyA<-2$sI9mY>u{693BRrzz+>?vS2Co--W-&6lf2%~H zcW}7~DN}6Qeom{o!TS1)~Jl?;NlbS8tv6Dz+FCkB54 zjUg=I7x@$v$nwWs4(Evcyx`-jK7IZ8ZJ%i7FIyIzgR-Se-=h3(J9Ak9dsS@ot_3AV zA4cqk*LW8`{?^y9Q|NgK=QTIb6`Z_Rbp5X)qXOkn&L*qzO(KF~nae z=fShx=qom=!XtlZ>?%ZE??3*6+x(ESkCsS3{Ex+luX_NhF{dNbtNC|BHD=|mCj&NQ zzE26B_$@}T6Z;q1-?h zY6t;}d{1*Zdcm0)t;BC(J34n1KC$l^Yb?dRlL~8PzGcgWUGNujHz z@)H0+<2~9DHh-#*E6qIhg-7yfFPz67`HTQo&pYr0W}mA#gZfe#&rY zFW~u450jxE2Y>tyh%Qg3%I^5o(_3NxBsL1xi|R0!8bVP^OMxZe?GIoF|EA;9A{^t zI2^Z6Dbb~fHOX4sU73Uy+|e#sV;u2K(XNHynO{6O)}P%tZ$~mKZ7TNH=U)V3SM9Z3 z9X$?kob$RPmNBdNd>cM7pHCN7ilr@pwfep^g2M*>aZ+&W|ErXrhx$L(2EKqZEmTnm z20olLlz|FXXQsFJ!S6LSmU%MzU*P!3=wBSg(fVxTy{(%!sl;)MmcwBrs#UBfL7&(e z0B#XOy*^A>+;TaCTd(UgB+P%{B7UF0^XU9tA<>!w;#K?#Y&HOh6)BK^-TgjJA2TpM z2!(6Uf5c!bOmYrZ?~<}HW}kgA*cT$F(V#f>cfu=rc>W$PDfHtK+i#@8fjzl9r#Wv1 zwPF9n&mGoF|N5vU>NMWD0hT~^QnnD;MQjvqx!4@^$F3mr|5r=Jek}lV`i^&A$%7#E ztXTNS?<<}4?oOvyVJoLBFoF*OM)>$~10Na@lAd-8d`F{6&DdpRyPml1mYz$Az5BPv zA0R^o#q!Pk!l!#616KbyZGov>33!4>bvyiP()Wnl^sMbfV4<7ilheM)V5J4Y6s~=J zrH(%B82H-2X_H-B$oEMLLaey>p`5((+O-}MyT4M42=wd&@?F0?q5%i?@__5E=0(K> z*J+3uHZ-0U=Y(WodrEu&!UfS1?ALfX0+?%hfdw<45$4AJ8V%k`xTzo0xqr%x`+kpX zoAfYxv_IBQfQ#Vl#>^5yYHU*{#m7<+me(;pG)*tqBqb?Vrd)6SsXsbZzio^hVo2=32jyl4bZDN3yvO$GEb@G0 zL4Beh_N%}`*T!6&Q_mQZi23^v|0?3t<)7f;y#Og4FaQ54 zh)8j}Fh!kuguF>EY-0Nr>10?n3c;792wj&BJh95K{!({=9B(jlJ3lSCm^_ z2%jax{d4RY|NQX?sfW}z0NqW!bJTBYR3Tz?Fpn0>Myl;oApBhU1#ML$Y}M;qb*|X{ z4?wO(aa-INceL1W^=lFP$2J&4-5&-3g+ic0BB_s}`+xsU+M+~!-P^&$?7T@zFgy7s z0R|xZ{C~~>48zH8pyJQJC7u*qqd@-5+lE5%Bk+`2y0N&!Ab=`AdR!;5Fq19}hZBfP zm`$kuZp0t?q3ODFtZVe`s-C* zc7}r6cNp%D12C@*t)C^lIQGcyL)f`^?g|!nlDGipq#E(C{?FMh^61@vbw&Fm!4>tn zpDm%%fyg#?O?eT6l+rT5Go^xU_5p8y2Mc%ZR$7>i_R$2zIQh}N=Iw=X#{H_b2d4X{ zI`gWndmS0jSO(1gR;END5`Vy)R72xm2SiWcGMJIy9eRRXJQ4=@u7E`1arQ@V0gWH6 z$uVfKj}XjCUO<2aOX3cI9<36syYnr~Ba=yCODDQpV;AnvxKj_y{o9sl@lYATQYtGm zvnvqltalzfcwh8RlnK=oMDO3wz+N`b3U+Hi>DyNo-A=00B338Tpv_QCl$LVHQGi9I zr>_bM?<}bJUJwoVYH5I6KTSds*J{5g>ADfF%(es1$QEN>RCz^Q zInbO748710oqBg3W?~UYLKJEz7R4Uhmp>tCb0p4DFg6wkw_Hy37{^s0PB!OB z-4D77#H)U_Fzh`9V@DiYHo%|}r}fGG-p1z}`$#T8Xt<*`{SqC?z94Yn8jU>rF;DdB zcM`H@Woq(|HT>sl!hcPYp+jp1W#qqlh8r*#njQtr4e$An@GkoC%zr!6zMgA7BwDbw z-1%c0ZmFVxh|YR<6{g}~R?fRU;+rS^ivy_$*AJ$97Qy#nAX!b4cS#l$g|u_|hO-5? zVAdzJNy~Z%c>2YE`axUud-sSMbfe;dUH?l_b+QSNt|GPwC_z+NiMukOiB%{+aCc_F zMA$c2{j8~#X@u1zsI^(aGwZf3V`Bq3y(@r%5GM{532hVrZ*T}dm<2gpeUzf%yNXkS zPjhA?#%kuT@mVjZ{XM|=LXt6mV7PS*h#+1MbK=eqnJUVPDOG6~52F>S25|spU|Bwz z!X91zau#6~{Q+AQ(hV@*SAf}cj~M6?WO`b>HXPdSQ)zg+*p-QYXEO_!xq8{|egX*n z2w2G*5NZ=PT1ExgIS{6lV^e(987(K6ZTFmI{bY)I47mwXM#L9yE`(9~>6Gja2`GC1 z0)CAv8*J%wL1mj?WQ~+c9SYhNK$AK3PJEuVX5qb56|({id*{ zJsVpf_karbq&>E){T>Lx?rg+#NX!TheDf&{J5U^Cf4BumMiOxUrdM|*M1 zzHWL9a;1UxGbODjTco`4StYOc@5@T*Kkm2{8BM~iq1Ox_v;d|XC`lC59tX6yn2N$l>%O0xsuvdkfEG`8Q~6{?EylsN4$2Or zG&n{j_iy8BHrr4LnB2Lji1Y6X*xGoO%$#EW`$hB~*boVQwc~w~bhvrG<@zpynb8I0 zm$*K!5u{G;JoY`gxy5n8ab4*gTc+OfzTl-s3+e2(@ADOghJrMG?Of?QlxwWZ+QbUvysy6*@(|jh+2Zw?*0s+Ka zSQ$p1@-_oFADWi4YPov$k0=`VLhYXsYU=(?lH3vl$5o*P?2E*fc=(EY3vxt0F#vrR z$4}+=cO$|(#~SlYG=kamUlY7fRxP*^%jK!gW7NS0w+OZsgmilY91_XgcrugBwkB+PJn^|YpOIw2mgDKu3)xT0sn zZ{N1%ZtC=R-v$>@?cTR>vR+0zeY|n_uCu8Jis@oE-3H);edCYWgHw0 zkyW~_xo>17nVEMS9t-)D1M#xz>4o=4f>L@Nhxj&Yb_xWjdK*RXR~}7FHwAzujVOR- zYD%FgqdZ-@VZ5>mNC`~ZRgR^Wf5!n0V?T?y3eW@82q+i^2S$|eEW>`q*miquMB>t? zf&}@RdIMTJ8WUncU&C2prirmE&?>zE6Nox@=wFAqFs)ZVIrOA>zKUu1az{AoMA+(l zo;5BwP;x(Zq=coI%g^Fe@6kJN!0ANm6kzPaPa`00e`2tJ z@hv$E5#Z{1&7tLvxXa2P$v`?1fGW8kM8cd0r|iwKeO%Z*NJ*f zyQ`nfB~KXebeI3S%zw(|0C)GJTqDPEWWlk90`NYRyEY?ySGrNM?J5zZ~IR1S$COvw6~OU7&L$5 zWq&*_i^&e`8r9fY4?5lnP{NQ%5onU1cE8!>#mhvcTF~c0#AAOt&7kd9N?y+ux|ll; z^;U-gJ869+p=f<1ThC8)@0I@YH!y1Q4MzU;Sei_Imfi4n^FvW!vw0YIRd_)HW!5(r zS1@GYtPIZkMVJuP;Yw2fmQTR=`C-Uzt-NKbavM>d!Af%!yFdzrqg!BsHaNeY9JTOK zo4A7X=COyOBbDxAZlc$8Ds!1L^=gHbGDamVF7o7Ou1(w>DyZyyd*3Nh`S$cU`<>eB z3##A|C4(>PKTUpO9Ztp?qQ&?URGq4f-jZ(CU-}LimAIP^L{$-R>pwUV!^R=sE~`$X z2P%8_fy=u(frgUF*#Z5ooftbRnSvkfOmT>EG`ie7)VNSYl4njx=G9=7$Eho=daHtU z7eL^Tla(hQQXb#8>9>FgAr?TMM_rj@*#}A|&COg$)R@=|L=LM>)$Fi*87>32*=EAsmZy2o2P2-D`B%)|Cn7 zpI$-6!WHy(<{xSQ65PUq8JVa|>*D|+bj6_gJgLuR?q~-@u8HFBus4v>y=d|{AM{p` zwRqMAbW~1jE1u0WwBXuqyYGt$;AtJQM+gYIcmw{N=ok3B6+xgB-z!_z5c%A?LCTAC zSHl*Sdc&6!R~jtBE_3WZ;{f1A?DpN(`@q9QO21n$`SNkYTLy zyQE*`HFNFZdB5`;I)Dect(=)Ms4m~T5jWJ$8)xPv*`B&Fo<)A8w0F+$8~E8c$oq#+ zn$W+3%Z&&myh3HZCpo3ZThmwpW63e1PaXg>FkUihh*GdI$56rI5zHM)7h39PZ3B3% z3g%#fnQ8AI-d+)d?ir|ke|&*2Z)pZ24B4hGhj)0p9Trf77tj|R8#ynj1UScH{fpyA z@>A)E8ImzdJ&?Aw;A!QI)FO2BdD2>STKUlXNH2Fs{Da3djs0ctg5dTWib3!Lrp#bQ z;_`#|cUH>ZGV*%ts-I~=n_oZmrvWqIxn9FKpr^b8#Te-T zaudeqFG%B&9m+&16J7IOtfi)vx0%xQ%CZM)K`xnuqy=@WM=f%=?n2lu`EhxI>OrID zRjOfI!0lvGKivO6aX^p4Dg9Xgo80j90o27ipvG9A3WO8g9W7UOzj=@{6BIi0?Udhh zGS&7BM=u?#g4*m9m52+{1rN3!O?8!-r0G}{H&3=op-W)5MYeJk5bF;4a8LUI{JG_1 zQ~ERay=-SAt3ypnFTnH;S;}ID&UX@-rJGE~pq(QE)AOS})d{On<%A{Y8lwYb*q%rC zy8-P=u*^J(>RgWuQ$9*Di|W!-16 z5#)zXG-R7)b=gNQaz$#qDhvV6Ck@3&u2J3!qI;hzE=JNQ%d0N4ZieU6Q zrxV+UAJyN!9a#I6_mrJe;|Br)J3J1-_4cJDA$hgDV?mU9lGj{lyJg-6&rfA$n!YvX z`YEcp9(rk0-A@cN)!&i_ou9?K*ZF7FB~6xmPM%=>V&5hIC7B-vc+EkYC4WTc!2?fx zLbf;2{l7C=TTvFnWV6Nh00<@K-H%&?*@trWh(rHh zeCSw8Agx)d9;SCa0 zCpXnTg~g>lsEY+3`No4&{O$jJVc7PZ-SyWCLo&KBbo<50yP^w2&Mns-+=T&kv1M8E z0A?~Jt6C7{s8Fz-Tjti&*Crp^SJg688d$34t57z2dG^|x%+HSY)XtS^W82~I!2rdt z09u3!o>|?T{H_LKPkgs)m!jy1W(H&*G$dZ6vTC3&w)4V24R9k?#DNO+3maDJR?uK( z0w4xqLd)wQ;D>vJP(d<|f+k$R2%NX>vEg@8zIb;SDyu+p8uTf+(z*`3VG+v zF-Q^*j{TG@-7UpAH zmrAN9fta@74XZ{EZ{>;MdfLz6izI{t;=}E#cyB9YcRY7j&nB!!#fJ=V&7$CvyB6S* z{Gj5ZGUpJ)jaPSm3We>u2>@#$Ca+O!VfTz2QWkbfiPe!}#q?e4D@Tcp_DfIF5N6X1m$g=KN9_H+SMw%&C zxCVflApjMz|3G!OIG{{z53ySMso=$BkT1%#l}_6i4~8Dm!Gn{*y@xjD^-w8pV{mQ% zxdo&C->;iMGq5u1(e9gVb1=2&9gd+6yki#9m4bu!VW~duX4j19yDyH)Ue#utZ>e=wvpI2ebPR~l;CK?J~ zJ)h=*idYnIML+mbR-*#=A($VdvS}a|Tt({4V<3*&UII>NL5;~n5AUcNNaUVI^$~wb zuJ4a-Zw&0CMU(W_J+=>h;?WqCIVg&U4l2{#N~^BRG;C!dRc@$vlh16h8eh@#YPxl( z*83DMS7!nutCSOEdzJ-5TNVy5k1SoZJZHeoh2LKubA)eI0O>~6QUeicm+7ZN?b8#J zcZ5tq4v7;n+blfi=&=zTZ%LWpsdX@cSp))tpUFxxi>fmNhfak1#=%_@a_DXXnwIf}aA^E0(hU%J@fY7@cA7Z~Nl zudY87-En9rHPtm%D4|I>DWfL%icnw)ube0455F)*8^oXgp743o0OG>}zaA#Bumxm+ zz&k5v9g(N)S+vmR>8`@|E80u`T7!U#e7jy+IsYe7`KG0s6}XsMo6_1~gZ%T&YReSecs*1X-$o|HFtnKKv;PLzPgm+0*?j+o@SQ7K2P-w$f03w zrXg?^q7e7PDk-Jw3&JazGGYO*G*%|f^NOrXdY{pDQHLYSx_k_K#~5jIxF`wc1_BY= z_VaRd6v{{pLjv~I3bu24Y&=d*B@89`NI$_n+X>j#(9}b#a@|tLMDDUi-HY6_ zr}LxhLV0X65xC#c(xx+;FTEG=`H(j}Zb*j+?O4%d^ncJIcrvdW`G^aa` zlZ7|t7g~N4>QnIMDJ5;_i0|w-Po|1?eld|+R2xRa{Nb|Wx+O~4Yiz-R5fMb3bn2K( z1DW3dKwp1{ViGAybpHByl7zr}C@u6fAv*5px}UVJ^7{!iQ2PWp3trl6!ddW#NsD{z zCVV{)Q(jBwK5Cy;$?xpQh3-}G6rj3AV{W}f`zqsnt8<~@HjQo)-(c8?gS87kRWfRm zpaSb~JqYy0!dOMEe!&xKm=B0v7E?PX{+3w#gHdDRS!FkRp1!D7eH-1`zVa&%YfpAt zQl=P3yHnYGH-hWu>5?O;6^8Kt!|dxPOY*E6uuD`Cl8?@N?LA$;GEB3x>XGnqu;kWE zJY6iHD(i|mlMPMv0O~yt4YiOb54oMG51ds5lP>?*bqckoJ_5S;s>WN}0~k!e<#*e1 z6|aB!kJK3Vt0J19h;#)E-haXa69kIj&R}*b+kS+db9H0)1$%is&L}Cmht{dRN4B>^ zC);v`JtL%VioTPtRkauEt|(BaoFQEdn&aP`btTXWd)%TV4b+M@{HHZmnlvllUcUe9 zeW>wkN_87LjgvpH*2J&_DA6p{aOKi&}UxzbV31ritq9O=TnGXF>qLvmn|1P ze|Z28FKv)N@F9GU6A|G~rthVUf*|AfzM?C z#=pbdt`hq;q0zoj861y&#k2BYJ{hH57~<(84XzZmUChAq$K>konq(*-GTYdcFSN;{ z`PFi|D1;QQjFQ-D#r!wUu6Dj25bhor)1+#n#yz z&90FHK4bjoGv+OtEg%qh1*rUQuw|$QctelE==!-!rhNj(IgG=+p&|l9|2_nJL|}Ww z82OK2Irejuu!YeU9KueM^zYNOwA7CiSNZcu?UM!&0IdhTBb6=!0bG?{B*R+92)y#) zaCv}~Z{#CtP=x5QpW#Ja1((LN9cJQvNPQjESfR>1D?mF20Z@cy4;5w!#QVX1uhW_n zVc{^A2v@&fXA;7tIDeY)fiQ@G9}j(>!^cX6aOL&;uY;h^{~WXfustvOM8os1rrz(1{!u_E3Y$M;A=jfU57;wjh5^R@7#8|} zZbg=Q{~x@`|H@SV488s6l>>eSBKuhLDv4XvDv%OBXVltxd5Qrud;xJ%B zaNND#f4Yphb`>0Yt8M;04ifht4u2DATSq(&4@bD>@Q7Eh=wN($+KMp+n?`<#=ZL{E zzm=hbOZ`8%YPNPH5Jb$(e&Z!Y$Pr*{?ko6BlY7$$GO&6Es$ITU@8Y1_$?*Q#js>6) zi|8$JwU$lWyEebabN}!^>}VEsRPV1v$bYeexc5&CJ-13#J<@duxDI-XE!Te-d|xfAI)KX0*^#a{ea%Ab?25oKlZWy0QAFJsXB#Y zsBmH$h#jlf%_*^O_UrfhPj7r``fKm^Ks2lmtxr`3`+pxhcC1u!p5r9EMV~hw4!w*W zm+h&1Y*c~vI&QsO7FZfE=<|P^K^J6D=g%B%IqbruMf(VcY)9uHh$gws3mmiAg`H_2t|CZbS|D!K3`toGI!Z!b| zUAuO6`cIWS{D;8%WH&*v+(J*~TyMe$?*{rpVfSYT`#n`^ zCWxwe{EDvdtd6H&LOHQ;xA>T-u&Z=;{7ghna#mf>2n?*AYU$X>QctT@vVXa=w4FAx zrePQ{vrfKc@<~Vhz$V=+BB%W^l|Sh7_II+a1gewYtf&uR2lxPRd*fYti?zs~CxdL5 zPLYp0Rtea*FPblWY(jHO1ppx%0KefHMbhL`MIwCPygJ2e6V%V3kvb zepCuv4yQHl-Fr2raqj(T7p7p%m(!PHw)=Ps=d({@w)PGvcgC83DPjgy0DNWD0X!qv zXSov(pM~ohCzgo_q@vFP7lNPfKH2{LT&2Oenq8K-k$rLeNDoo8fBqfpK$-(pT@}6d zUhEh2U4`5w!~N_w?KmQ(n(Y$9 zP4N8NDk2}8j+!cBcRy#l_K6-mK!f{C2Qmbj%kCV$%q)ScbRzM2iVRLnNm$@3u;*X7 zRqc$uH1j<164Je;F!?W3xCMm3kv_|gK|k*QBi}RI7riZP9ww%J6kC+tAY@Fg8E^3& zwnyL;{4%5!Z;l+gz-~ezN1$1fe#*fd^!NJ$CuA2@yQ_X0xG)vG9x29o3|Up$agDPI zo1X1BBoqzo&U}VK0Vr<>M9#wJCrqm=(1u@VY4C6xURm_o@ULi%NOO1t#*xaN0J7)n zt}CMIzeTuzfe5!!xaHX(R6HR=BHYfsyHv3$9R}EN+s0R_`0(YV`u5zTf%#ps)k8Hy zn7RKMDT&b(%}Fu}-Mh44QCSRMf-zgK05M;R;hx{4Y2RT{=$XD)VP8)Rcx{O+W_|}b z9o#n%$&GLSI}ot~rq-mdJ5pCMy@W3y72Sv%wMqtZX5Du@%nT0eTfL3uCueBmH;vAi z1oonJKMM_on?w2PtN6&{CE=c6LJ(za1S-@<`|yec@iWtbn8N{V#$P#B+hDUx=)<^gAxyC zzs?^JghIb=X{xi;LAdQpa2VrCs4F-sKuHfo}9^ zP%)Mr?Jz9l(wzNwCUG+&BHxu>Gz|M|N{qV@ad)%8U z;L*2krYRl~;V?apebNA6-%UEJ>0^Zma%?0ay^R9KRwAR;`b*e{CxmYzx62EsX?>Fk zw<~%k%v4yO^3OCD_g@0Fuq(g3!%4-^5gil8t?5~0&l_-f**0FvpLr@UjQrxm>Q^|w zh`toBVJ&89ew?@sxngZ%`g?rkk6npogbhjL4Puz`fiuH1hR=dIuE^J5umSsO8i16_ zd|#bDm~b}ypQtG++y|A~4XZ_{nQ;L#VZGlbJe4B+>5u2*e*G2@Khu9bn z!znmy!ABh97)#eE8Q)n06BU#mx>u^yBhBrq-@w24720{yEwbPht4U zdTEMASuKlRdp(rRWr(c`zX3|Q7ZH?kPi4z=q}oH{y#+wA_RutZ1oZn>A9wF3^M2UI zx(HpgT!5N&fV#}c%_V@_6WIIfYY@Umybzgz8}J*2jl0=O&vpQFc6hkGk{(?=IgNOX z?E!|ZMr2$#cFqvK|6!F+34e>*^B_(vddEHxesH@#z+H)mA^2*Xtt^OjQGAoyCmJtb z#F=&@Z!rrRc^tY$0B@@)T^l};1WJ_DR&82lzE|y+A8o8pq^U{i06E@^q8|WJG6ocV z26O}3fr;*@b*SaVF8CtdT8m{W&=?5y5mbh1;S}`kvS84pQ|2zO?$Si-U7Iezh_r($ zao0K^wMuG?NKc(j*9Mw=cBotD{qhq$oFT!{-TY$*x~9v33d7I6XrBu_9W4NCaEbxO z+775RZr05`fF7M8U?zR?7}=@;K%mJGX6llnm{E~hJTE!{3>`;;sJKb7SZt~vM`155cQ0PnA??87kT|Vv^=OE0T?%D(1*Jgcvg>F7kyxE&Z+y$eP(&R`35cdAE%z&^JIo0e{dz5?swqUS08?r z8UyxG5ztj?yAU5X5Xi6~*jf)=PNyb7@+tyiV+$4%<{^v#0cBV$7o`kTxM^!n`Llhl zN6sBobW3btdw&zmk$gb=KDKIp5bNFkkt+$fp^VgxGv6GRtYG$*Ep;XgO{f45{Ukgl zhdH+`y7-5TI>2e|R!FF*BcbCltx>gKEPW6#MM?vlKH<)I8pnVzVE0L{zu9dgay%(v zCzGT1+=HRZh6!DJ>_cpV1NC}P=FXfy-=bo&79a*yU&H-04yFW(a>v?1+GbN1G0!F? z4zRXex(YbkvT?6HqyRM|N|l^&;BsYJzmG4sASHa?X+tjuTgOomq8rE*{0iu?C$7u*n!;|<(2x-{}Ufk(>8(^dfE%CRy}Hbc+QGjF-| z1jJ}JAG#n?YasBAo2cJ5gMpg~g#+@mBZ#T#o`id!=iM*AWlu0K_KzO|_)wnKAr2A@ zVkKVMKQp93r-B(JnF~Z?HhWGmHQ<2h1jUZQO8}cTSh-ahnXK^YvkJutzxsLZ>HJT? zC^E?|BAfH2vn;c$>A32g%lRC`)?7fgC?fiN_PdgRNYDG?MY+)_doKTUJnGZ)2R?8q z44)$GpXgN-SVdsGw%-QQVEh$(*YLra5VG+0)l>w0z z(`2KpoC+VWKtft7Bq9h$GIkh~CkPI#-_o^FY}6-$i#6k#hfB;K~h zlBg<`8NXvzUwmnl@snM?t0HttX_8^%k zK7xjjwqPxD0YnmHlNFuE*I6Ew42D)GmY_efg9JCVP!Sr2f=Cv4MzcL(xUzTLG@i`bq_f}1f&lrrTcGTgoa8)yV zQL5gf9RQD#102mubzAKFSiocHLN_O)&Gc#F2k6{18gWCEN`iT((AJ=>#79w#XW&rg z6Wu(ohq}d1p^s18yoQt?I<3c>zsu&(Umufr1s&ibd+yjg)lR1K(X9Qh{yi5H*bStg z-kNI6(QC|^gsKFAD^o`*fPGU_AZ769-I&Z`AsevrSr`Pa@x?lR{l>F9R0EI3E0_rj z^z2{;tlO3?!oXCxrI20eFwn%OnajNDt3ou0fQ?nS`Un{MmeGva4|{$ezI8pMqNHZs z{g_4Z7jxRE$&w(%WwrAnm4(~u6B0kQi$(g6%Q@b=BdqZeO)<<|HHt~RLRkAK>j!%e z%XFR#BQ&TpAs)3JnbvcwxLcU&dYJ3O=jHWC4p~Gn#+>$CE@5v5+%_Qjn;@aW#aiTZ z2=ykhd1qoSl(wu9t2Ri?0dN2X+2LLqJF7UDHay4s5NSd=0v^iPzht?i8W9fxI$!sN zT6Q-`i;V@24PCX%jT2!_EP&wBT^!y&`h|J!T#4obKr$|WXNY*4SU!3@tTtfOjnmF} z74Sxdf(g>;D+e36tuo8W-E0=Sq>Nf=j@#57cx+rUdhU_TCDnl(X6pU`aq3pXGiCo7 zc$^*3EOcY|(kZD#TVY{gc~#Yuje+v1N+<{wBt67tF`XSil-%pV-E${Ym2!WTv40o| zULLGSt1hl@>+_uUa@riEp|99gzIPJx9~Moicu8ufIRFQ1Lr*$tBbnp6!3haM*ON}0 zbH-1gpK1lEVWF*KM#@v?_t2Q`0`=9gVRCb+YX?oF^@b297hm9diqt2-?Q9X(0B4&H zC=jvayrXzSt{TkmRB9N?72CSLaHSg#(`6XW*RxCGzL6u?kW2`4Uiwn}iO3CQQAwF^2=%WJi@TU(IwI z(DkfxMoIx8Kk>JGIL6}6A>vIp85jTQcp#I`7w$AJO4)+ZjPjrI>ZU_*Ftfn_cBVpy zG{4mv!bf4yr+pE(1Fzrk;rmSV_QQn$g0!<-!@!(tqFjmD^&ht{hy;D2+M!{obosAk zo=9TF@TVv(f(nuw3%f77Bzyk+>WA&+@n^kdetfA*U#u*Xh#tkfG;9mI+mqGcx~-q2 zkYdVM-sib}dpRLK+<=JUVmeBfVUzS>8lWsiy`%%un~Bm7Et2tJ=AJ~}HpzA>(G_?h zi@M9f55vcX<&FwQkc-z-c0%|~wYR?yxF-n>Z9_W0l+|>k!>CZ$vWV8O){qF(NHUx zs9ZFW7T7?vzT`I+@tmSXerlsQwdXQQwd`GEDG3wwOohvd+MfVI-s&5jNB%f-qPk2& zpXd5b5QhAcL=ltsFix4aT8daVoGDqca;A5@FufFGCDkWF?&x!U z;J`}dJ{Z!onrh~+!M*@vwsM(Fh8LK-*)!X5iJHe zhXTlnH$AVGFV)Yhw%h7_f&uk0Oq zXJ%r?vMSEnDcw=>53ekSQS#g`6e{^6_B}*$BvYh79k@V~w#PWyFFu--sBnOwlJ8B(HFlSReUC&`7<;O8|D7DDn~R6q_wfUpxG2l z{eKf>LGc9`BUGhtTogT*KflmOlTx|naoyK7B;paJ<#EM|VSbAT?N5`Qq=}OpOzm>o z*=m97U!GC%I5o`dTbO7y<7e#lMYk5v1sPNzZ(vuKij#?#L^#t>N}YQ-SIf_h5BxXl@G7;9FpS=fI4wHZ}uZM2ZXy};oKrp*ehzbpms*82m{M>PgI-0Wv zb2ZjCsHryro|FSKHR~f642O#sYAwk5s|W=gK+Y_+!_(&~Pzjd}h^HS~-|MUskJ$Mt zPKFc{UMV;9X?-~7)BRrJ1m%;4&a-ku=d2ocO!Qy~tZju4Y4BS8)x>W}dw1vG=%EyY?!P5*QQb@BnlHlf@Lexs zRx`>OoLFlk<8hc;Rd*Cn&7E+kO-AUMs;?pJy$%*K!oz???&#zpDObLdN1=pD(&z*5lSZF^(1UzF`h1=8yBSLYEd@QPYB2Bp#6!%bA8jqm9*0e3 zcb3LILP;t!5xxSK_4Ff{jxv=K9l)ak#)=1o@ARafo?gura%@jwy-9BR7J~D}X?wWy zn|0bb`@zI<5$p3DaH0I!kQ^0QjComPrw!j8$yNd_<11N_)`|5oiw?^`pV2q1sCLWi%A9fFV~LhgZuM_)RullKj89aXvaPvahG@LjlIv1V~dHL z5)g&EsgSX@9X%*CS`Z$d@!+)b`neKN}_R z2`*SOPFks~8NR&Big{%&7YW7mqyt{{g&yy&IK&U&tnua65x5!V3y5F(d=I3pxqY4_ z*?F$Wf9snUS^z1koNlWP-c{1%Sdy-uB$gQ6i~xRVg4EFzzr@UFa2J%eH(vH#V<1(X zV{9MwB~^zBK53_m#KrD53xw|3#@!uqT=%YpL6wHJ=fQU7ybF`MeRJ(Hipn?M?pe!OD`;NVQXLn`<(o^|pF5g~1{s%5w^H8IcUtPcNTRCD1R~NS! z8a*6O%McXiQ0oQDVET#Cb>5_$9Pg@ZihL7z6O*zl>5Lz2Sp6Ej6v$M;v2aF)XXrP2 zi7)OUF(Gt6O#((Kdrw$ff)HNxdCn)QH}PYVl-D^qH)sxyhom>o*r6y)ykN;$kA*tU`E%^Os+5T>;&jXwUP$JoC@r z*$ljRZ|cFJX7S_N^pq!1RrnCa+x(P8Y6A*C1v^XNa`EIEWyzG3*2^{#r#tCjEcsu! z-X|LG6Ff-{yGU#26-#;k1+T*7Nbi%4A8Etp)y#Qs)}>W;@_vFGdR{1F((Q z#|sQk=MNm4AJ{*rq;7eJ5^JFq!)>O#ch4?VBe11|iu}c17pDCuO`j&&C5JK{Qzc;W zJrujoM2OeE;VOj$5b;{}A2xBkbnMmn`~f4CmKiw2O(oBTIzM!z^Vvf(D(lLGX2I2f zy1OT;i%6@Oa%h$43>)dy!=%tS+;gk*h7< zx-DTh5nU?o`%d=pCzWYALzMW*-6u;f|Z(zEReR>!H@=K_%vWFeD9^|K}>fMNO>9bPTyR*E0=IQuPv0lpZDbgQI zznU`c-_BE}ivcJlLr1e*O9Kt(2gWLhmZC;MqF(~~ZjhZ0fpleKaIaffGed|a>(`{d zXySnFFFh9J_pi{PhRx{Gab~Plb)FfzT^-Npu@!581|T}I0Z^r^fNh|(J~MW_w%>zh zdf2sE?&umwuNZcPO0aINLhjSe#mfZg4)@lSOaF85BZ=ypHCOBM$IlhsUNjFdDP8-@ zAIRZf3*i2HDCD#nMas-#P}E-9J~?}zKuVRr=*BSAg-^Iv&TPvqa* zYf_iNJCiSHPp}aQL9r=>^ZH4waUukb!svFh@B0)U)H!szFK?GrRX!GEXwx{s#`rj6 z&V>nXf>v^3WK_p5YYQbfE()jJBnw%r+(~>-5>~AF?B;Id+mgr_SF1I)2TTBceg(KF zwz@_T#R#$Yp0h|Lr0fS!fOJzs_Y?K?n5FAG#I04Iwr4JK7d1ufoRA>NV)5 zDG6L9D{=EsfPQFq=GUzgM!!tbjyuwABc+5y52Pe&tR~EuI_i{{Yt-2?70;eD6*7?X zCFS{ARJ~VX&WAlO9}XKu?{eFw4_WTAxSJ<>gT_rY5vFp-*IhPJ;o`dJK{|I%Eo%=) zi0tJDkOY`kHePm>NjK>QiLg$)i4@l)v6>^tE`0>0fm3~r zSG_$Fd?3~4r}1KE(%Zpz%RpppN$#|W49d!BwlAe=2IOq=$1f%xYl4xNKF0sHtN@oj zC>piE$jXj!W3`T~zA&pdfjm{iLK&FCYA1cjy@1z1|Fx0iy4zEzoSZky5C*`?>Y!$2 zjOsS_PvR(Ff0&Y}T|N z&*ZB}wXq)lABVjZnw|46%`@?!=b3IHjf>sVBq(dYpk{e$@(rZ7L2J0mfkzj49#>ux z^_p)k3+>#{Lz<3np{!XyljQRsL*1bCBi~}Zpb=u)|M=X65jtY*a$7bqtB?YtD#j6{ zlGn@8pd=yV{&>*5XoCy<7^|pk`+ewhumdFBi91c(vrO}ocWE2np!e0TXvpllpjPsW z@zH4Ye%^wVOA<>YEx zuo9CUtg}f1ezqqY;5gMjYQxCh3I)R^b@4FNCu$w%Xo@mQ{S>|lwjNjMK4+!kDF5M3 zKbZvK@CO&Kn|Dw{5mDtF$w9*^?^(;LL#`ZEeO0u1EurJ(^liMimOB>?{*o(tk+K}K zq3D!Ow_mPhZV$h1PQ>E|VmSP__#1HaznO6OtCqExF&E1AJ2Q_1jqd5Schg~Q0?*Z@ zGvQ)sEn^#?$Y|$*_lficOx5(T!&IX6C|Gbi%uy*bo{brC@u0SP+j%bcU13D=LrYXY zN{BW!ZS)dZ+>~wtmF{jYW0#5hBXZ@|Ae_Svh1wG^<7^T+1*0`ygbKVqyieHq_ivHD z3#u1AeLV0l5uF_H!P1x0l8q!x=?$C1us-iDXHf_OUcW^53N7yXj;|27mHiEWiL3gF z>gwZQ1*(IodY?4E5IVM;IJV@m-D3%bs}9#Ae2U{@!+q<}ohkA~^dvBzq29x|vjX&V zlP;%~BcFOxvdwa=-an^EFXTlgIqQ>BM<{EfMdf(d{rWk{Fg@t-G!Br+MAv@<#&lyC zhqdfvyc8WN2_ryalP5N3;na2L$8V|U-0B2>TU!28x0b=cwba1`rf54?cD84BCSlh3 z;=*<1&Kz|mZ#%#gZ}VGk9yR8%r(@*k<#Fy6#VIz z_gE~AB1Zw86;AC4A>WFWZZ}d=D>GT_66rrAv9sPAVaWnMbVvU0ucnMN<-UPYgHr!7hYxv50AE3V2-sj1pzg8zU8P#9%s3lur zlWDWsYH2E2(s3%oe!V@~WTd^%c}q+#{4KY2DnN%i!Ltv}#2mdxylBiA%y9ThzwhzJ!F66w-(*#!k)XWzOhlH!irRyob6Ef`c<%b3Z z_foS=ck23l2_$trTARJ+qA2F|23k{pQuZObPm^s+m}A_3OT zxya87y%kyM=HFSntB}I5T6whNuAGbe!Q-!IReLhlsQA`@a={CZmZ58yjdcPVIy!pvi2Un{t*>ioSc5_6C|CxNkZrj^-v;} z=D;?QpB78rw`B?;0J>c@$U2v!1)PxdH`$E8xYOA2eekRZSuCzjnw_8!`gJ>0sj8QKo}FpQ9@sI!x9`~nQ~VN&x^c}u#}y+ZU8dVn>ME!I;t;(~-ba5aI%YlTiB^Qch`A(XC6IO|Yz7@={p(UsvDK9H4 zn<$b`nvm83})lnaMH8|Rxy!fViDBMxpz4AL)Euu z1f;6F2BA$eH$x-W%RBt`{$^dsz5F4_o|DSNqh3NVRe|=wmTt(sYdUh=^7+C60j@t^ zM%ayCFzhbO=~SN}_j&OS2x3eSfu>4wP=A+Ylf<@|o(`|($6Yd}UqWU3ts!Lh++Mku z4!64LtedQaW&7e0Xr^SPXM!SOAf2&HuW@OAM9Nq(B;Do{#WZ%#-i&oXyf(wS*&ycN z!m78J>s2wMY2#KeN@nNHj>BO5OZF|$3t?FYf zMxQS&?_W)r`X+~FqGl0qgMcAS6EY4fjYlAw~*LoL+^JIM~K_KHrJM(NpLUd2TV-5O&naDh#{4R!2i<#1^Zm|Pz z9_l`;#>hGgo4nQ`40`G!Y}Ao$_ER1<^FBgiDD2gjh=U-_DDiDKR=BuH3oRiR$w&gs zn+H&UH>U{b6Sbv|^ZN=piRx)Rk|JM5W~Mf?Plq@@r!vmjLYkWgo!T7PC1troJz(P~ zKc~;-2Qe0`m9Fw$>PPSPavsUOtE@6q^vQE-rmsRqb%N78 z);=aS(H6j93|S1nMn#A~u<~Pxgay<$|1a*|JD$t-{U0w;MnfSDLM5A`l+22NzPo#WexJwV_x=6z`^V#c#r?Xj>%5Nh zIFIpsK93f_QyeflAhk-Xf5--G;)~V(Js8wM^fD|T(MfL1KnM$kn#$8p?^2#FT-KOq zOTE)&EA-6=>csgDkUeKnKOB6i1QfoU&d=PE$CpkdTiNAb&=MpHXtv>;1UoL0O|B(Cup73c{UlU3p*Jt{!9eeoknB9Ub#NxWn zY%0M9PRbe&Kd~3gUS4*i291Ez>*tB(HvpnLq#0%^0@fo$2T=mW?PRlB^kHKQ=SBb3 z%9A;z=FRzf0x4jEQYL_Gn%ui4dqjD6l|+IM8k2J1LFCqwpdUo7Nug-jZK0fP5cixj zN60Q=0)gKQ@~5)Qrcg4wv%Vty->3g}{CN=!YUlG`pmu;=E$HS$11+ysL#6pi_Dh16 zG-!u)f;*mCjV@s?e9Im-UQ+S)xZ%9_y;2lZfx3KS|Ko!%g z#?g2owYd20_bZV!|=IY@&}NPe(|f_=`RsNL3!A*PWq$1qYC228XD$os}D7f)Kt3j{t*s z!wWNU!S6rwNw_8tfL+p6Sc*dl|Gr>P(EuUuB{j5%11SDi@Z4Z<2W4=I9{j8kE`@vT zP@+kQJ%p3lcr1&9(x2{8%s?Yb{$4S`K00*)l)m!4Ls>eRnwaPZV%(oxh85@Vxw7kZ z^iH_n183h|+U8UN<}4DEaGw!N_zr_x*6vHeQMovA<}5s{6p2HM|88k_rNEq9Vp%MK zodX592e_-4GNG%O=>}GtkpT%`VC3pHr_(6?NL9X%TQL!Id@+B<$sOFc6qA>W= zu_;8G4NlN>B5ZaN*PG5*np7+J+Ufd;o@^59T9#uya}h4mj2yJm_I?m|w$ z-k|ugrv&4aJUk9j6a#f7X7mK?xXt3!TY}M1e%~wG@Jt{U`*`V=>=L#iZRLV7!=SB! zHMuoS-GM>iy%9w#O->&dTHQmT)$-QJ7*QqM6Wq0ZJ@s#U0;6K2f-PjSi!2w8p+O}u`N5wu5IlQR&0kzN_@qxmWBdZ5q)XgD2Ow#Ue6j# zwa}kBvEn#PKA+yW%KrgSM@UP$AJ&U*iV^+zOEQuQrL$BcJ~uE-jEp0^9D=qnHRK(= z-QdH+wm3CJ^@ZQy2u6KU)%1U;eZXJ50HhDQPDSjtL@;@eA@ z;ZH~8pDntm)3Nf7e}F|U+h_& zTbqEdRACr-jNQ~>D1KFMV|K$r$=_`m=hsW5N^$o5`!YSvffagcrPcI?23kt8zb1Q) z0lrPb>%=uIAO(KWfQsQd4{o#D!1ibysF20J$#jdLVmL!nhr>Vp=9GjE!TI+%Rhc^@ z4v_Np)gB%mi8(oKo59z)(0O>>t~3*uZv9Va>3?h&oDZ>{roV}sg8cghZ@`K@j%K*s zamkqe7d4FV@eLFOxxEUA!R-7la)O?1!MMe|1B-b$a;OAbOqDHH?s=Z%-Jf0gaf}rr z`$twtDptXJyA574amv?RM5&?O2Os8BE;lpD8X%d5gczeMuue2qop|eQF8a}wTj;LYU6kfPZH#nePdG3#U zS_T0D{J-Cs5yw(EdXBJe%n{8C!vqOaLy z>}tDl^e`W%H_ls>jNh(6Y62^?f2Jh%ydaROBB!sfVPDw@o;u*zwHLT4KC1AQU8`o6 z*jFyV0T^BWG7>Z8!L3uMt4A>S&*QyLBaYymWc~Z{|BG;ncd16u>0R&U`^%ITP!o#( zvZN1uwEqjS92Ep~mVXVWdx0o+FU-VPFVkwn<1>9tJ%Psq2UYb$$}gDYrqfyUklSBg z!967KSP^|UxcBne-z*sS5rP|dNcF&02MeA?P`f$G%bsG1MP;TB(FieyA!dS^k1>5UiSYL|q6c zYSV~JWT)_6+>SECnYF1sNyDENMlVC~LvOAwcVmwSbnVK<*^+PonwQ`lp}0?pWDPD) zS|sskf`gooX2{(Ortvlfd4l}STpRd6tGN|r2lBq0_-)hRKGNF3k~Hd6zLbLm$z_?G zJqR~Iab5;)d>^mBgg8pRs>~;!3e~|EoAwak7N2$xxBy+DK~ii1c@Vt>;)aoIQrwK8 z->(H|ZCI;Tde`Q+5SVZz_Vnvv7JdNoXnLPp59z=@nLcWay#6i%*VVM~!S0q=6?{U< zT37-CKh2A51&g_4*n|Gt1{%?N*#%HB0c{QHp5J`hadYv0xxZ$WDxnt?=;lT(4g_O9q~3rHN7vN2?o$)r#v()j=XQmBj)>9>mw+(TrDLvtW6* z3Osa}ZQuhe@7|)Rm*pzwZZ+zDJ3@T`FYc455ReTMtF?`#v9Q!ph#o@$)3b-D?`Nck zsF|FI9yI`bYX1eC1!A^=(cmSgR^!t+D7n zQ4~wgJV;Kp1Hexoq_xwap({*6_o*dGEw>R3#98Wrj>Y?XwXd0u#SI#oPJ#k1KZqIw zVd?@efXKxSOWp+&A?R%j;69-7@f}49(BSY%&l>&sOowM^WDw^dKqWzf?4%nmIEmYG z5QTOZu&I3izH!9ksWM~=zKVD}-xkP~F&>Xh0Bl^9i-{~}oq)(`2{NPCtf08q2HMhZ zI0v9CrcL}t43S&KqGl%mCcMn4S^z<41Xa4QW|-6xYBL^ppf$&4QEv^ZX_GJzC=En2dETj8 zwtqs^V-{xS_=Odq1zdxkeSNSBfP2Y~8Lcjiaa&ERp%T=4Xim1k&;bOAC3|88D%|%0 zZzFEa_dZrNJLw!S&uR=z3r#`i(z1+*p$&!-OjfZZXTU7x!3?qgaRt*;b}I9=23xW- zVCEo1TZ;!Z=sT~yD!%>0-l22^d*&I-H7U6gTcH{UQ3j&V_prAT4pkEp?d&hHR78*2 zavBldx26q>iIV^$XN``K@u0jf+w3V|x9Zn1jG8Y?Q=R!kFcHPV$wTgF^sP&$oLNv2 zTpiFzS)kjS=fMI!$$8cIk{8UWjC}ifw)KT6Xc|M@J*921%frx5Z+EpzSWFewF}8F&ZDs`2Crmz2 z(a#LfDrWX0Gmd>st@xsU?Hn0BKRLU!-LHj1_nAr zw65oJeA4$0ef*Q-fZ3Gz$~i=)%qSe>`80sr#Nm)(kVq+i@g^k%bKwLJ#S5&jkY{+? ziFe|^gK-(10B{}QumOBUBJ_SXbZUcbr*qkBUXICJ%5!1SV0;I&I8M1C=tipr5DoK% zJX`po!KqYiZ|BgB2owk*9;Rp^qWHik2%L^K^Z`xZFiS>=Go$u)vqt|^vL0eLnI<&t zDy?FMHfB0kJ>S>&yhDES`XHrAzqetF#3;mQ_)(aiKPY^qx$ zQ8>2*;ujKJjBZ#WSK+?pmU0a)U>x{=yQPERmKvwW020)j29O{YcJ?K~k*|`-a^y9Q z*|6{YRS#HU@D?{l8tzNA=X=|@JM{;Yx!R#wL|q3^^*-klb5YmKLCxEo4O{s1!>pr!YXOOhG-%V*@qP4z`XN1b(4YVXW4D+Xk1wbTp2pSa1 z4k70w1wW(F?gpTcsg$h2j~H*+@m;;dtg+9DUoid%_kI%d%fp#2b3E8VAg!>Seksp| zj>z>|f#Xvv);4VRvL|p5_fr_<}H5+F=?#U$Edk_j5zqr8pRlbm}5Um&$pN; ztcaBFkw#?0Lf|a5z^t=;AVj((F!L7!VC}2mqWgv_tL05-CaXpfp97-2ySjRRIx7N( zTTv;7GEMZL86j%LEG1;S<^3;cJrRWJ_MulFu#I{bg)l5N!MG{WAGWI7KX!hb{iCf4 z%b@Bu@AE%Zw@)6V^G*{2vLs6YN4iz$=s+1!57AL>{rk|{jtsG19W#_I=&PD+PoLVN z5jJ<$k`v2hdV&AF0YHlNq1Upf`(f}`7H~q_;2@bwegpI^YKtb>>F)@9=uDd0d0%iU zc?_`A*(Y9HD65?)2e91~^Z*e_y%UDStPjQ4n1T#-oBi6<2*+2+l;iHsL(t)C!r}UJ#L;x&d^GLdA_40J?O5 z*t!ma5w>i2;B#Gi2UO}W&`eChSKTPOVF~2fWRmjHV!Mtk164iS#dXkT?sGm3tQ`&a zMU6EeX5Yjx@o%ou%ZzfY{vUI{xhRm^7`FK+Lv6skp~u9$9DaiaaN9OJZ*}_`ko|v{HXeb8iD#yf_=N`PgSkjmpu{Pap|04+F`^U`7_nE5@g%?|j}}EdKd~7hgZ~ zQRamRnKCf)nI+lNpGl5`j{Ij(FrNwlet5T{)!g1hJ$i6A^FZdKK3eeue25xpRMV5A zh{h*-wnuE;Ie#%6j9=I@9p;?DLni!9%v4s~nzB(|3hg9EHSecBC+ASyg?4p1dm z>H^Up1Im{uplqD}BXz}HgfNQLTirY!wlDPqB&-@`V|!vlEChI8Tp zf^c8%oEM7OYRh*jBQGk?c;}5lRw|;;!2mU1;Elvc2KDdKQ#dyS$nG zyhbeMqQs<$bL$fLh<1Ro(&UT9)sGiLkXg&K4yqUS%?FIGC}zkK_#h6Ds>1M$7C|6{ z3%RsUvp*o9?!$um)MCZCVfcSJD0aJdONRlIjzFQw3*xoNLcl3cAJTI>&my3V$rv!N`AO9$f{w(N3YDHZwzh>8*BhdIwT zeuO)I@TR-c#lWE-u`m9S38HBZfxiPS2Hsi%BH#;82Ee|wV;AZtP)W+^C#KZ;B0lwQ z9T_(P;5Vg%{IQyf>(;tx+Rw8g=L8ni`QvXzB zZ~V^4FV9b@_oCn#G2g;PAoQ zu3X?pm2ZcaF>5-kXBMk9-vEVfEg(~}rK(T5f%JkZfQ`HCiFe02OBmCvZ+5|-2ewK1 zYok#*9P)}}igNA1fmqj`yT=E~Q%7Yc(gT@0ZwrF;lQ4_cBFv%yD65@t_H#KDt}6Q5 zoQ1Q!vY5eqX8c$W00ZHP3RFnL0tRu)i^+UxP0wW{Q|i-tM-8ok_~-hIJDw z5H>|Am*+56j|Msr*7^JLy!004NPqX808|1!ODhe5v;0WIDtuqgPW0lPs-VWL*yDX- z&`Stt2kl%j9bfuMxm#D0wt>++?`}T-PP_&q7W^7%ePI;=_TOH>)BxA zWh1}d`;kP6k&qMMJ|s$Klsu1j z$S~;Sj_W4>B1=z7AY{(&szct!Xmk^V{WL#a?TinD!4;#DnTB89Ug9<#6tz0Ualvo& z6)^!5YlQf@*W~7NcvpJ_HrHnyLZ;w=>|1S1xEcA@E4~lSS1^>%30V5V*6^y3sWn9DC#3+Et%5o_B zv}6{;=X8BfGi+ZuNkX+v`?CKXT#H^6S8dVnJrj%uivR0)bD9N+Igta+kOc|q6D0Gx zd17Ix=uJ2yb(y(Yw>jN)W-%&Uc+Ah)zdTN_S-bMlS&^MD!n%}#<5{@YzU1cr-W!mq3wi$qLis)c(YO0puc>EV{k{z)V^KO%DWtZLP;fv^v!|f08D>ISQIB&hs;bDVlu}HMbh`n< zV}9)`Z)noB@WWDQ`W}rb@ltwA-0N!DYt%z!(!@#x3FY_gj2rpmY~_6^t%;NB7x)HL zVqAHUe4e8aH|qU|*YMyRBO9(25i>*j0#i5py=Ee`05uumeNbeaxLZnye+nbF86#aX-^8lNZ#SeANhjj|0?6ouJy`tWQaR!Rhf zB{%T;H`_KhmW!t#uQv~)5a)r=PR+aj^HHM=m=jywE3L?-U?S|~1t^tVV^8N@#1YnC z;UK{k$w27>{Eb=g@G(PXM+d%NKL6 z@6ez-nyX9vT;td7R%vBhclln2`h+Lwko$z#7wrp?hZ( zyBZcCcPQU;_2C)-Ya{L?Fnckt6#%ldC$7uLRTlw!r;87;Z2X{ILz%iAod4IKYU6@& zKaLWIFn^>J3F$C zj6mWbq_`Heyk?X?$}Cc>e~n7Hig-N*_Kb|*OLpwbNpZW(MDu9YiK(m4AlX1+|Hrl7 zk&DUEHhQT0c2pE|k}*JZTm4&4j7U=l&YZgSM%2hgH-qx7=CD+$lz@}bw{NwO8E z+AB%=%qLoEVU}0g<9tvP>v_nm7ODI3>He7ba}*&3MQFUX1;jH=)A41T_rK7}pQ9vj zzWom1fL$5MF-V&@rKQ5AJpd}f=H*w&j_&~1 zCmY82TQSjbbJh9GK|a=UQ#fa1n%85J=B0&po*-i5Iv^m`+T4s@$NMQs|2b6o$s@WMB4qFy}Eo{tC&hvVq91r&dzaB_{UP zPS4uDNKM9|U;@VM=6#CyFa<7qxuH+f2vAzvp<=1a5NaLqKxa}q-Z6p{iB}A0dSyS4 zZ;$Qwj6v#05Li;3-#uuMY*`n|-w9{e!SjAxmXkLGGoq%r0q<}TY9mfdfYujk*FjA~ZP*TJ2hIqp^s8DCkKnL_362`x(Zy@BH|T@n5M-9% zCUNTNfA#@akg!%1KVUWgtw3c*a%I3or&z48Q7#feH~>YUj#8D z(wR_on0TN48jt$5g5n1fiX4KY|6m>^M7S~AT7N~zLB#o5ZdliVkr{}PKp((l9kI>h zJex*A8>)9M%TmaZXAnwX{CsH_^&o(I&C$$PLkl<~V9;GKM(aF8Y{qy$atw6Z&e%CA z_C$G{j^7#1`vzkYqWccwE?If&k}x2=Aws+Rl7#wFPKa8Jgg6;n!1S;A2W;j*{y z*lC?GiCF6>YP2)#Daa*h(9dYNy0L1_)v*cbwMjyc{hC1nXePMVg7X_-9y&?vhJRk# z%6JI2Qx?j_LoP1|OsDomeW^u-se^X&l?+OdQ1$6izLv8VZ@y7Le3qWOVs_X6c2g0? zm8@%+E=3NPvfaDH*C952KUovcB*;%5L# zV%p)Ri2LPZNvya1%y(E=4PKoG``q7?CTn4%{iuLLLWM>nvZsVkLBNDW_+VP@cngzg z#9MKw1sFicH0>D-Vc7#pUKm`a_B4WVxHAcLcM zFr727S1={QxC|K5)A0{tUw;P4sBcMEIuY+o@o>Wb?Phd>ZR57mWxx`_6M8ZXXBk|>liY9W^3pEJmjZuXHxBc z)%NylMg}vF^2+jh7Bdah-d9Y|G?Z9&B-!Ma6MfieBkQkG6{XW>BYkX*weYw>lSIfu z8jE7@ix9{$he9`Z2C@v!1JebcGIs@Koils|9=*}rTA0G~U*7qDQKm+v5J9%IONDi5 z{t`t@WVDCW8lIr?JPETkFB8>U!G+~hikBZkI6JVDcflcHbpT(gKFlTqNa}20el)kd{=vUEOE%{^`_F0?7j7mn}$Y z3+63FwVN_5b^;}9eQM`ks?qp#Ym(db(X`b_7K^yR@0>PpeBg-baHZ6^(O>k;AndX- z2q~Gn#}K$kr765(=p-9Z$@;`YgN1pg5CSZ`>I{|-rJ*p7H4#RjJ^NsL=E*NTf9HSYniARCr2NBkSU|zZnKYC3%VU=6t~^* z8%^Z%VbE0w^sv+?=feWqj&9~<2@ko0v}dW}o6)cDyg=8F!*Iww*KFh_P<;umoVXa; z;~i)0R5B(KCdY`>d5Q_E^T_M~EMLjfyHg-Up3%3!~-zMGzrUl+0t_HW_NGPn+DbO1^RpXqUEY#&wHQg zw95=rVSYCVRl71ym*c8kPjJ<)_8f@|yTFFL1)LnzxLOZxEZC9`g=_G(hw`?FTF$2{Zk@uOmqr(^d`--WcBC{^_x*Wb|e}to{_A*g@r}6FCt& zn>I;D6Eh;1zuWXAj5AeAb|`wHx(RxLM*IIsJ8}|*RM6dgS)_Ol0^wup z$NGq|bfpp1arTP5c*%i>1Q=dlU+KJ>1+7R*Ip{sTKx`l}`BAnY;_O&GaX1YIlvKo4 zwfwte{FTVxemNIAFaEM9u6k|(x{!zgre;e%@2;#K6CtIb$#fPbLe+9wslzcpwt3zV znxh5MSmHh*0Q`!}K|f`t{wf){LuOcWJmE&Lg;j}a!F)ouK^AjbfzY9fjX zx8JqR>^ZCPS=y#u?sP%K9hQQ!e~Nq!xBG zUO_(qNt8{CP}q>!gV3T+o@PcprRJkssJa(7pArM3rE(};?-zmjn`k~olh2oZNT-s= zvpR~eKL1Fu-<%ae5NTDM!t6C3lv{=kaPL!+bvI9n&no3FO4||hSHP7qks}>$ndXOF;&AnzYwW;6zWQS6m+})$f zEbBguMs}I;A~6oBUtjlb=uDjFqe;%YwfVe%lBTN&d<=oY0v?l*IOu$?Ldl_notw~X zbA9;@9vcn@_A8(fLR`tm8M_wUw0oj9t=XXaB{1APgMhLLb$z!nla)gqwV)T&-%(R0 z=b{Dh8Z8?{D08pI;Yk38=|i*7&4J&jMb$yX11fc+JO~TtD0BosGL26w!?ABOJ%t>( zrV9)4D>R<)Du~+^|D2a_UmJiEGXrK!=I9mBEB&1N1|f~5L)%a2wEai`?GCfA>5gbB zAc8Oi6a$~t5_I!^bdK(ehsl;kh#XT~^JeTzxXU6;1b4krp#2kdN#!VV?tgKZjLc=} zo?bTZY*;eN5sSNGx3LJx!t?K4^C1&T+2x*AOWB8cR~YgQy7o=5IF%pJwK+mD zG5PsSZ|C4XiH*(c1lop^v&w608V*Vc0_nZV+{>NKS#wuKmReG}C4k3YxFMlB`{Bp~ zh2kgAb1fN)_q=YGIoa_r0d}y6+Is)9Z&G4Q`#!tt44upDjfsS4EAh_n_!wxiSH2!Sn4|YfqwHf8RMCbwy8Q?qO$O$3RQDeM-5;dL`S8 z{jjCw>Pyv?&SwwdEv&nPJnXU|VbUZeVN&ens+AqXos_*l$Bk%szveDxwPcAHWLy9E z>N!~|=Gk4~%mnH^UmXJ9X9n=wC-05a_T4pePt|Z$r#V37yOK)2>TrH7$Hi|PmUHf% zIV-|~%w%e4Y96ZNY=l=QJoC{v7TKjB!}-JN3eu@f_}Cxz>@f0he--#>ov}1pw{&w` zbU$<@-M8esXWbB3FXqI47!d+Q7`>R08$g#b(=;C2$MTCTWwW6l)&wGw*7RWK-5cvn*;TGs)&=!3nw1f-uY~r-7EOoKsPWn12lN) z^yfd<47H4u&gj<~ZKlU7dg%AF<#)8aQBJ=;oNRiDa>`gY!64DDx;V#faUfMWuRK3u^i}@N$mfriiVfG>CM?ss$JY3j*QI$6*=Y>Zc0|2}2*&A{MRhCVckm!LSDZ%_1rf-BTDobvt-%$I3{FJoq@*ub{R ztzVK;if7xQ?7FUcg+ktK_u+oQJ4k{e5jtcWHg@%@YHE`L%ixuej9IcRMC($h)zcpl zr~A9<$ z7d8&Bg&t>8kr1ZK=wzvyzjC+sfVRYvzV@pMIr0N^4>FiTAo*V{knbq@}v6Y;E~+Ty55k7ymQm4MPrEzA&*1@&n1|I zEa;lL#QrRI>9!mdk_fu3qHWWAe2L%Sj+K1-k^BUY#k_ZSErZ%3y)BrhGcjij3EA85+TBE{KzP;&>P@QRiL_Ss{+?so}WD z`j93GEA79c(^|C|&g!eG3E~<8*{VjGP|#v3m$HEiu2~Cc2W+4+? zzY@c7F)6{Gr(dbk%HV2!=Yom{Fa+E}zuteU#OB~FvN~hr?E;Bk6@}&kKn*a4 zL7dti8yT7R8!m%T-LcqR9Gx}+JyIQY>1>%k;di5XwOYQSnc|c(~W6Nme~gv$9otxf4W54g31I8`pgQ!iQCB)8sO_Z&~_jqi$b0@2?u=P5Qo49h@t*U7Q=0 zJ};4J%CjiVqE}9_XcIk9D>sl7ym}#55G;JI>oCiqQVNR4via63$$rl4O8hT~kp`X+N2@D!-o+vaR=4_;-#6qX_A|rkhLhE& zV5>`nTV1(4bKUkLyZjA5REdVbAZV2Aouy!$T5g(pk?pc!Wtd=bEcMOA)}JfiMb;-) zLX!ubSwNx82QtVi=yLNLrFwN{=$_cIbGP>adhs@GDs(AbidPHmG@Mh_In;8ALKS&a zBT*ASTwwl1bCQ2i?L32xq+F^0u24^sf|Gf4?#_!w-AT#8JPsmy3mws3s;xCAx{uW} z@b~;wELvZ=p1b7`_9jvBtp+wFzLIz%s4f)q$b@gn!C}-^={XA>^IhLY&pvVS%W61K zZ*3R5D#EP%R;jk}0}BOXUv=H2HH(PxB~I$<)OJ(LU3JK_cPcnc);*%)Enw#t z8={b?oF2Y*xBi=i++z0!&u#*Yr3w&OQbHI4@0(>wqk$5voWihoTgKW~Ft zT@cr`rgMu`fgE8}{5A?SBUSzqP{U8~>b03*8*59A6v)~TWy-nTEI%_Im=MUJ$qu2c zH$*yZMGA>G&a~%SCEL}Zsv3y91aQm*h7@b~EsRABPBeTP`X-QXp(RXkU4Ys$p>-G* zx#{_yQY!a7AAW7+R$E`@g|185AGrw39j`VkPQ@<40($Lxu{P5i73vx_k)vrL*gVau z7kE%pLzgH?AwfwB&0|@A1!vR5rU1jTCtB?R3N92Eq$!B?~ksd4mI|51)cHOd*QQC$uI^34N$#j8Po562XR3 z<*$`vo`DY^+2}zv*-eiPKz2pxh)x+t*jKijF;G~9LwuNK{Oz*(h&w~5H-vaHG@$IY zo(5C$>=NxuRi|p279TlMdf;3^3!oBO6%VqzK`;3*RON8*8QZ|v z4=o^iP`K3{fgjSlyKxk;5>Ll5LD417lOCkAsxY>;*SzZ~Y;Z7E0RnT(YtU{3Pq1Am ziU=!Vq>$j;WSZ{RQ%ssg1?#z^UAgJcXaqGl?^e96ZklEQ&}~+7F~A@j0fTBMpx4AW z)>~X`X8`r8MyP?>*{{rN>%x>5YExNPC1c1~?O)18i;vli@1wY_3*stvNtQ;k>nR$% zmXpYV-gsAliWj;EE;x+ED~uI!$sU!^ER;&B4HA&eWfo=5nXep|=X_+SMi?{6rlPGE zcKks8ONoNk#yvWN-)E$&Q!LMmCAEbp@M*@=#0$2td^2Po5acG5*hvPjU=q{BLDL}>uMLL9N`CvJ}nT*6? zR_#Qg#ZQvR@E(f=9$zb2Sgi5u^XT9og0{G%sSgPyJF7$hYeTD|sIaR@qB0!&QJ^cB zX4ETS`;Ni_4Jq;kpyAClo%*DNP#&|ZNV|x~mfJ8JPw_^ssWv*S64mpSKyNN;LdD@m zDTPInGb0qi(%8iSq7Pae%n_H_01_G6eJ}O)C-cIEv4PFBY`YV?;O-o0@al^#M3sJ~ zfAz>KyzQ<(uT)X!rDAE0?EmK{&X4L?s7Adr~T{)uGUhpuz%c51?l1z{PqH&+&zw8AlrSLDGfD`|SPAJy?AXS4yt#7sQBIcm)BZnbL zloWOL4YM&4)f(4B(J2(@D?m`ow*b1ehQ3h35(WA1f#x6>O~D6<&$%j}i*vh^^m~ic z0lN3@!x^z=3PfR;Fqd91i+gL!NDY;$^K@3a#1= zyd09=^2uDhR>mwA`tDo~r`fRN!|}LxlS|D>H}ys+j5wfDq^IK#D3(gY?%FB&NY zUQHAbCg2b~n16%d4hZnG16DSxn$LwLes-|Z7tI8tmX^)Ww=TMwai0zv+kTL@+8#7R zga&+Jp(#JP!dB^(?`4YU+*W$dMt8XJ8h!hcZS^L@9Ra@h}oGs z53Qe=|5oMeBcSg0h_~qR-5PIm!S}X_%NaYB6?lnUPKmiHKLUSXs{{Q2i|bVZY%F@a z4r=^oP0^|x1=LK=t%G}xxj=ArRPuT&<^%8`#6Kt``5JS$gm1P@sI-kfJdGLMHn32< zr(*Kh*W4mQr@o#sy*HW7coP$_CcFk~g5=#4Vc7!ozgG#bm(7oTeWwj+v0SVST_r>S zi5i7Bfo62O7fqlM1%&^US^bVh7dwh5S>C+ZFB0H$`8lvfJaoc%vPpYops81Gc2&}c z#r7w)(q;aqLioxGDN#^QTP>bb+EjccGUXT1WA`wQyQz*Xl+OP_Qts6-N+!9Ffu~|! z{_4UZ2Y5jP4vT?z72VWl*FCMQ>i0%ssI6H=uoz{gam=4xKoL%5`juXsb%CqtID#2~Ag@OS`J2K>z zw&883AQUzhvA7Qye6eQjC%k|mhB+i{yfx0 zz%8gwRzdaG7eE^wBe*?@uSqLYO3^G&p`i-^zJd_t#Qm}A@=en$Eqht@bSM@DGo?>H ziVIPKNpWwjOvJ38oBgBCR&p0y+ujg<^y5$-CY0dlNV)y1gZHGiC^_~Pgx`Y2;Y(k< zf@-^JDBUa*(-wu56v9Ud<$kr5s-TnpzC zhaY5DvT^TV_PEySKA*zf?#HpHf{+}1ONp3H1zgFI1U7}*g}B{FHQ}ky_OGp=A2TPI zfvf@q!jN{GFoDW}BIHCsM#&pwjZPJO#0Yeb;dop+b$(C~f0`Nd?(DNNSCWZU=7qAY z^I&V`j1#ahi1oKHXfZl{RW67U?aOU=F;E8yx@fk5=|YttHz@CdO_JL#k31h?(#iEo zUhCPnFtSL@uK{3c%#Z>3>bIxS+wD-s9`V^h8QzpwQcoK}5Qi-!^XyQex0>#fOH$)0 zyy9syf_qiJwp0$ZKs`Ej+P)2N!ESQ<-pG$F3n@`bs4L;?a?Bd>VQaC*7M#UREjF9|p#?G^ z|A7nbt4-*K{4gpH+4^?0b1hawXuZFjME~yS1o6*K=_BemLe@!0 z(4RiW!(Pn}c=5Y~&*tG+Y@!eh!4TvKrf4h-{r!j2Q#3Sdj>c&g4>ql>^`bR)jyuZZ z->47fBwN4@DQ1I<={5}Dv6><`z1)o2D zY~%Oz@bG~EZ>>veSB%5lbWE-mEB1^Vuw*O09=&eA7Qb1Y+7}#)V-ud{XZC_~#?1!>Q+!p(lpyIxX^t3RT*cAp3^y7&k<{4=FL{Lg)d-wouhwn#U z@8nzOjb17x?0{Tjxx+{gh13ZzCK-m`9-qBZjs0@#JO6VD(MwQZAr|_UCkZ-w^L)4k zb!sAcwc#?@GBG$1QpO}(;GHw4E(Bk^fgJ8}fyDpJ#AgzdYn4|HDdMp1}^L-bmp;;xMDZ{1eKBYtDTwLam@-lbDkaSTQ+zWDsb^{ zKwBYUNO0X_$1Hoaq#yu$7_opGlS+kB1DTN^?3fL(4Uyzj#obj`8Z z(6ldM!2m`nEHC~~0M4(yy&t#|pTcRJUcEY$uOllfdw`nSAS{2ZWPzin`z+-%j4xl} zu)T$Ab1DbB=Mo-xwz?n^8!YthD~4Qs)3OV5E`3xuA78h^+VE!rRPb@Q8WY#cw=|$Z zMOA7#dHx%%kaU~JV=$;(!;CAJOrAU$TzZuzo%nic9QvZ|&hD|LPak6Gi zfpbI*MCj5CDvgx*mw|#$uh8i9aBr!9$%U9E;t7>sMZMFP!0sNaMBrwKGvN$RO!rCd zpXF=@ke#ZbYjYexH+!DmoY09J$#jSg#u5W+n2Y-V z-O^n!&$@m4_GBnXaEY0lXV^{0?D$%2ZS_^{G&X=84%@9`r3_hy+WZE8V4sJBVO*i- zB5UUDCam0R9089pGM-v?lBd`9;(U5~eeHD4|>}S7! z`XAG0hzW+x%H;oO>8HieMRYv>e{vDA^r5n%*N`ocyn9TCE%Ofun1**9$)+W}2WEFG z)bZpo2d(9Ul`khQR@#23ozsKuSH3^PS13sbFk-&IJ*b3Hb;mL`a3}aSydHeK+|Y*) z!Z>mM=Q3)q0YaKQ>_8|c^zzS_23tx6dUQ3fIa(K6!0oD^r`wI4A|)K)c8l9`h@4W0 z@sH=E>8#o0+AMl>8OzJ&4(C0(MSY!-rbuV)Nt=o0-XA&29QK+$A&1x&RTl%U9kqIP z{v-t37o05*R4h(HZ7x9fYfL4O3Qr;F{F`CANW2UJ%*S|6YV`-QYPV!7!Vf?;cLFpv zt`pZ|{e=HGINX1gAZe)K8h;jBJ=m#9!Q?x!5XM^%PHY8VlO1@sC?ThdHe2spxbWKI z$Zg7@HyJr(5a_U~FRc5){LR6A_a zKb)uVe&ZRiaFc=5wI%0`B^7J4UiNNb-!$E+13{c=35E&PZ<$PWKIvKJJ8W8r83wRL zSk7LsoP5; z**-;uQ>QYZLZ_zWV46;+O{MbN*pHHG!K16LW;#*Io*sDxhMv=M@5a*I$MNH7zm)p> zmOBJGO(l#^XHN0loPNJVlb^two#@MBR75*+{a9Lcq6;wva|IAx9}$~R_7ti@sZzT; z-sR+y`@UVkx$pti<8!jLP;^Ap9h45uCBPw0N4D*1i2D@ev zD;fuYs0HzQ#a5GTvcMYl9X*zRdD~vR!cZnI?LdB8OBF!wK>>+jTOqDMy&d0&l5oK$ z;OEyIT$mtajEnQ}N6aoI{L%frVnwk`Iwcv|on^^&ma>M1hNav$dr@gj%B=JT=FR@f z_Xz_XeVti&?N_Bgi)1&G^K05)Gt3|8v1T3Fk$@P}J@~FBZAInr*n$qVz zdXb(syF=LMtGY%W^1Ll~+M87KMnEpW?xOm1!Mi8s55KYHCl^n5vVXAU{xZ_ec})y@ zU)7mglCP-y|c)DnnciEM=5|{+6*IrP)MC{u5<1V}Q1#lbm z4@*+SYmSe#CWpn2#I*uPfPr2jzz&zV)!PE-2Ig#SR5u%1&uDJB|_o!aW8)f0r0gDpS@o+v< zT@*9NrRh!%_z>4|lDP|=B)-6uV>HmfR)?~OHfx1weBZcyIJM}<+o~}@Agxy9Z}-}N3qvC_D%B( zHmG9y1KwdG3;OOXpwrPdujki{R>l21acCl_ zLpK^w);6sa6c?(1Y0PXZVu5oHatx%;7FjL$-*ThK2^u7nm=E?@s^7-;`&wfsM;|x% z;JqQT|D{yaXXDhg6AGqJc1ct5p3F@u)BxuYW0$z?TDtU=sPQ{&1+U}Qq}OL+i!2R# zj#@BwtA2WEccj|E%IIYyMZ9?HXxv1IUmg5A&u>()eHl99TKAbEztj)E0>`Qa!l#?*1u83*+|+Re2AtHNrD&yqo(j#zzt z;SzmwDnRL6agU&bQNg2?jmN?R0#-|Y3lHayb>FMjajSggG#3_GMg^5XG75ZX9$7?a zc{B&X5-I|ZBK|sP-6u^XkU=>d^p66eNOiL}`7$Mtz*@^&XaH&C4~T5prX*(Ox8WKv z!lfbqS?nDOZcrqDb}>Vf>s}2~wHY_7C#`}ZhZYsl)^q)6_=;E{dx%{0zt6uLj4O>X*WJlSOvUczYQ7>+@4TAy+QH zJotjiWxgFeJsnZIai^*f;p}p{5M$#`^hSkpwlexPvX9@fF!lGa$~Y8u&`7QO^#0!} zS-%>3#)Xc_rT%6Pp#fHRTuvNqWlfWsrz}vsX?d9W&d#%~+C9!J%+_=5U8!k$4f#uU zLFxAb8;24^ec!=Y%%p0azyz0-^lQ~VUhhWJ&I_)HG_I$UxUGtK*&EP!r>8pk$_=qp zFFj8X$^72YwP;CA@#5jqT<2n8<`FBOo$qbN?o4=v6=|LdE9RA+(YWm&cZRPq#V#U5 zwQ2EK?X-^s3bl?c9)^IV~VJojhmlQ|kX=mt|E>sey&&cM3-49iJZ^ zSGm3Rx@n^E)A!UnwlabVB7uQXrtj}rHg8V1GoP>(Osd~iR3wylj_HAB`d-BRg)v>l zXZ4yyHS87@6WZ*REzuOq#G9Go%nGrzpfR5^0bQlTs7AaQN38(tIIety2_XV`2$R0| zWEs4q2t-DV zMn8;!FDGA*U$=9>zfJu@V`pF@wc@0+!(JbfB&xM3$K5l{GHxdJ!?j`$CZVT6*26_zwFW)B=TsFb(!51y6_C7k9=~<3?3RW{ct3`04RX~0E zF3fUW?P~3-0+|K|=&t%PE5=uVKOb1U6k`2EhR^D{m_kFO7DO7H`faDF`(@}L3LN;1 z)H%kr!~nmGh7%vN`baGu%BQld=_+i$u`(ju%1;7KQQ(Cwvd-HimYqWU@t!>*iaC!~ z(vkw{uIyYF(5a((2nrAjsjGLOQhagpHs#3+GT}!kyu}Vl%7%y9EcOeO19aciaApFSU7YIEg*R=Q488XW1wo3+gM*=eClC}56{U7e$GOEhH-5ym$5mZ3H04YTz z1q2Kl6_D+3$_Kh7EZd^lei z60WuGUtBroyarSKF?mK)cQ}nd9NTh~o|R?owI1uJ-F7gKRNMMeVAD8vDpe*}!P;%_ zmi0uZRrGbYr#l`JZPxeS_xtVS9^+OBZ+wn%!817c>ookY9}E*iy9_i{kUE7-nMRqvz@jeNZOTvqz!4; zdxEw3K^ErxD?o17PQ68k(9G;KfON!+%qr&xL6s%YzvvzB_Uwxn^}gF_S{p>=_u;&% zz-j_VaYwf1^@jt+?_r?XZRKjQx%6bStSEw?+-Be8hhJ}T+^as|CgH8B>?m5%$xfB| zo|@lL9#{k^0$mukh7ssEbd=R*I71wl%WXkjbnXSO8hb&QKS4o8PyWNNW?b6UG}vGl zSN(BS;^DL56X-el0DTuEsbr-&M~#h-|F8^?Q=pb{!`XRYm>s~^t$6A}vBOm% znGmOi1kgpvAEDVMQ-w5y}z zkrct^GCFl8RjlIL+UiS8!FXMc%UC5vuvF|kon6T(WrdpPBiSzgrwkOzvNN>RI!)I$ z_Hyle8Qz!Whlii3bxUvTqyaNQQud>vCzQ*9n*5q!i9thLS7o}pr@`#2?%bKl1v|^J z2#w+1p6n6H@1usP>%Vw-v-F40y4=%N`%yNt7qe)eHg(k%L&ok_WW0JYxGLtlhM=2q z09n9^lH&m<$}R#T2{SuTqD1X7dqrD7Z{wrM$37;}x!Fa4sLh1<3e`kFTdWbr&1A>! zZu|(I`{lhF2V~J3u2%!fgxxHIg5J5sl^>b%n&`M=#E6hq#<0aHY0I9kjnElgMDz=t} za8ygP03pNaVy`9Kc()bkYpfv1(&6^r-eK{0B50IoBmm2>tQn%6>39w0v*{4AbAnsh zjQ*();hbE!yr>0opgtHNRe2SYJMR2w($e2Ai@VXfS7eeuu2(mXfaUGe8b zs@D{1vk&+hTNgFto#YzmK`Z zaZhflx4gX4{O~P8nr}=WOMAW=rcCZJ_S$ICdGQKftPS3wD5Gmq>L}J8kR_i!BhpzB z#TFMfu7%>BSZsFwE^Wy&NQQ+IKU15^^IP4&PvXV2_A<-RuCuzx`*NPH&!C+CCb z0~+sRwAs_V9EQ#^4!a4+H4LgL@e;laF|%VSwjTY2Plc;+p19yx12hrsia#t*q)A0d z!u_>-Vf^h=;>dI7p+Se*!&c8vUuHIWeP(qaQ%z?GC_*q{+k<(<(s@WdO2{yyeNan5WePr7Akj8BEl|$8-{EPFVoP zR@)@&*Rx4N#%^asHV-hyw^!&2Er!`Nat+#R0eH};6IdneiDQ2q1lm#^%oIFhpjgod zhKAxRFrn!)-@Q-?cEIjk2lKsb=-qTM*YBcR{r$HmX^|spMa$#=QLKrEx>6hu`<8BX zL20^sG!ri`XiRjKA=_@kdGf9QyZfGf#arCSLWY+3gdgHI|DHA>3uCyG-9WV`HaOd(h&Gk1%!n z`o7iJD-j1*b(;0Fyqc2XC2}+_dsg$UUprnKYBBzZuKba<;}tyJ*j=nhfN?BT$(Ch# z*ryj_tnj=xr|O&J!iM5z`$R{wjdbp+ilyk>M0>8GRBx`KKjB+fAjv1&D=)fcYTu-G z-_TR=3;2yMFGNJaf8X~c*mp|aD_5@U?~R^Date~)Ai)tD z1X0i=a)HW^H-oVjxPn+x-o>*p`zj)i4%dUU__ z>l3sa%{cx*O(W{9d_&7+)A3mLNy*O*c4=;jJ+p#MZZ;vVWn+gFJcId|RlB~QwzKMN zTDaI#;AB_v=$u;P)0f5CsydYGC1i|>A1a4=<*P(G(r#)wrM%v1{CJ{oN(wfWR7>GH zVRW&&Iiq~1iQs1kxVhkwItpu9>brB!l|8Hn;SV~*SgsB^O;RDGZ_+_7&>^8ZuJE1Y z6iNLHI$v$$hH$2(1{SMU1aQM9ha`6bC0>vu&J3z5-}vmD`R)tIqtVfc1+?9a0X}zc zY4~iJ@rFK_Y->bycU7fYbPj{aV^rk%*+IrgxCGU{XVbn#)&452&P1w~q6InjkqQUT z6}UW~l$sM?1|q5yAo=+H=YGKTt%ItVsO9I=-_cGAtvb%mE~|QOKH6F&mc+Iul6LZc zvHr^x|F?fN0j_Yg%?U<-xA`Ugw&ji|Id<{tb$mol;$Y6e?Hn!n(XbKh4s}#c2F~|5 z)Zk$~mWiYX%rf)XOtycIU>{DT-MoK@?#la`lQVWurX}yL zND!RkT5>BHUt8*?-`;KIVi@04U59jf@NSmXCW%(&-Lf>oo(wSpGA1TB-A!Rm!=V27 zkM8m$c&Z0pi$rgy)wXF!bSjobYDHXKAnHsyq4My%=OU+3sMxBN+AER&tRd$U<)Jw3$;4WjD$zhvDajLuPT506x9PJd<=4}9u<|4bgLg< zbS#wk3be`{6@*yawpN^*5X33SIW$5oW?_~3Eyv56Q#0hi;|Zs6-?+ITo?@@~UI)?5 z8E|tLkQG#ocJ=_q5>gQMv;VM|^${g*9>DP=9$7HKZHGMe5_OmIWj% zdl}S)TzN4L*_z=3`?*xbY@?3cMyW-NrXJxb5gvQTs>*97s1CURIj#*Q)zf~+Vuoe*jn{URnmH>WkZGds(*EthAI2y1J0yhyFVu4gzkINJ)W zRi;i(0k_kZs7BrjT{PGC!)`f!htowTJsg5}CKYssPkZo-o^GO!XS5A_x3u$(ci8=k z${C__*Q5d8_u}1iLC|HXKuFCq1*rroKU0AdETJu1xC1I!VNf5Q;stja3D-!A#sjoD zlkK0@$MNJf#qlVS%ugq~J59p8&~`h}L}r`zV5)$cIy0~YJG+#{Q!T%Ls2?H;fYkNt z>s1W92|r|8{_Cy<{n7xuveN(zmf_E~q(#hI(o@8|0N^=J&9B~<@3&o*EfT?q^@Ows zkSloL88_N>A-2a}LB0K>?SFs7bx(!dZrY7;Udy?spLSj1#v zo1vUN6&sNtiAPc3f|~m!F5LzFVWnb6@+l$b1UGXk_3vk9%E6YV-5=-e&UB|C@2}eb zvc`o3QnpLEBIjf9*#+qOueqi;m^&GE-`&=MLYR!c7iZV~LtN8Wf2>Z9`$i;A>sz?B zpCQ?Kzt_)h*!Nbzb}CiQfxei+p>#Gcn$x1svObbW62w3+BgOb0=Qr}ZiUEDi7c+Z+ zad$bVVlAe!+-=)_Hvy_nC%<`7ki5;9ji` zUYOZ?)L$r3;cQj*y?nJ>6G-?nGod{W1&xwmtVINr=30{NA4*J}3V@1(Lb_Ux&Q?3z z!6fCZvu6@=w-iB|u(!gcsAtd^1llh^X{|@4iyt!kkEMaXgB~0w zTdDKq__O}_ROQWvj`VHD3LF2vK+!qdTgu7pL3SH?b9 z*IwM3C*LFIH7psc_&(vd+MT1r_*HDZ({QejI||g5K9|iqCF3xfn1{N^KqUhbj&7$iC4{42}GazT~6ea1>V)n1wW%A<%$ z!)?c!*(#w)G@WdL3DfLE*B_k8iRmIz$*|Xwm|y*Hj9naQFu%P}e^?EHwLY!` zjs+7Gzyf-@AvA@4!p3eR5Cf2y)c)niS?Zdvv+?{y^R3YTCuX9I{~ zW|?eNK=SGQ`qZklu1*b*%O_i2Ub&6PusYoIewcM2m|1yOsjStYF|rwxLa5C60O}dZ zlt_Jr`N5~(9i4mlP}sg#-@%3!wi$mHwwJzs$oqZ&{vAZZaT}llc+z%9x=g}oXU`p2fgL*8>qw( zw(L8JL14pnu1eDkPgU7zia^CqK2`Vy$#mz8>BP6nrQPR;lW$#0F~YQ$6uVzU}FIPU`KX4@1`dM2k(U5?6lx@QQQKOJ9B93cfafv4BD#EW?e3qa(RdO0h(h ztBh)sUR4@s}8&j0ti&^l+TzLhXb_~ zNWzdGtPnB--8u|tDWBg?IxRs}q8{$(IJnFe)<}@PmQ!x~(&tS-^!ZyD=x;Yd>o672 zscXxfL2OfiM^?XDZ__&RoWcgQ<9~p>ca&}_Od=>XgZT@|_Oc4>rUJlW3|oN_>QRq? z+mLHepB&sp!~7`)hwn|$q*OsqnS%wBV(K&F_kyaeYJ`=z0VIRczoBB8WbeAUbnTg> z48JP>t~VH9@qM?}6u#fY)*Jo^gU-U_rO!!9r;r{jD>JiXXVrn(FHwjswvs)I9a}h! z^sz0zP#t}$S=}@8ysNcpzw~0QJ0ESy@&GMV0t=rjxvEuVSdD(uQz-M-I#b3a**z

XW^ZHnl|kbRDQjH^uT-f-!NAMuyK6#n`*H~uZi(g(>OOt8zU`T;bMBG) zm6V<(;nN8ME~*P;p@R5lu0~BS7-hO?m)c4oJQ@oY^>_E4mj!NY$91kXYVLMs86(hS zFikUy8S$!|#%q;jEkG3<2QA4S&m@qKx_`{1dfv{z>|?k0S!zkj=x>0)eXP$k=Y(X{Lo#f9C5Lp9 z6CR+#p##(zbn-SdIA z9M?pBK?TB$=#f2s2&-os0pZ}A;*5aDNFnXUeTq@L`-9I9bDEmzX7(hPb((LPm7C(K zjauQ(T@0D&g^nY5KqgMT54f37m2TUT{)jy<7s-x}IY+*G^qbLeGy=O}@@|TbgxF5- z9~W!_u#HzBQ0i4e3cBN|9N4)a%7BtfiwQVKe6$RKAj*065bl&(lo_-}Yhudw(j9y7 zF`s@M?USJG$pbo09S|fhL!d=;*W)(^=%59%F69MsXc_PTPSWof>$x0$>tBuyAaYre z21Rq!jI$}ypok?tf~vKQL;+S!*KFXausR~E6Cu<^#84b+!dYbw3=99jSai!N0nRz` z4nK(xeKvP2wqVh;ujH@eR}ZQooZQtFUJpwItnI4jjPmw~37{1s&$xUX7l#F)bMF#r z%wbrq3xnH|SAriEuR$AmxPGiVu5oz=6SE#!X#Xgt>0`6UKJ%^Y6vzpv!6n z$N_?42zgQd)5vS|x}6eYNa+8oF|vFQxBrYB2LCp~4 z*#AfoP2*ADzYdAD;3y zV&-j=kuUdup2nJH9R%8x8+Bd*HGM#<$TB{8W9m49x&JQ-2E?hHWW=`VaD?*FU;cUj z4|tNntEV0y!!&{#4}Gf*79b*Cfsnancb}D@>=&R_WEq>sseD1+O(X#f+gLLX9a8%p zz`p+Hz(h&(mw!ZK;C1_u*KZCk`wD+~X5k^FU=jM*#UilC^rZD1a1K3>0twFl7tw)_ z$Pv{-n9iYo24`Wdgr8!oUVgubIM84I*Qxy{q^S0XYr@V*<}Hg*25vKa4nzMWjul>H ztT)L5UGPdUSDVbDC>QD=1NbW*C;OjRpm+|}K!zqvL0%&c`KF!s{?4QjM9|MtS@5PB zK#_eM+;~qm5sjc03_YVsWoT#pt9#rBmd|F-lN1gV+h@tgw)>mX@a3cmb5CI?D*Jiy0XeDv|uKd0-z-$e&hpJRvQf1q~2 zf1$;Jy}n@CLCy)EdgK-?=^0t(+X%?w!tYDoL38&Bi$uTW-v|%YgQ%CR`X9U`@=<`q z2*ji~TOWkjRxA%U$UCed1Qx>cDAJShPaQ!-lMDbS2t0mjCiz#7$(R&HAo_#Cs`1)Z z{pxA0634}4gk%n*niv4h{U)4US4T36?J~3xmioj#e4x7%R0k-e@oLXQp?nVJyE~wq zaIx1H@|C*IOeH%mNb|yA$_%}kVj@6GH44poFSxAFM||`JoxYvTG)OY7YpEQ>;pgQ; zx6l9eqDVkoFCpo(6oOt9@es$O=qCR_E{c$-Ein?jSL4Cz(S&4RZM7qOQ!96p3d^6m z2?mpuGY*}$nzXG!0J{7oV{{Er$6pLY%p>mp@>`)k!rw5B!Y+91Xp4Rl@IzcqmK~;M%iYEto8EB9z2vR*xFpMr0YVK>Sg3C=@ibdwy zCD3^^06gZwLPw{xw>CAA~DjAaphz*H#QSxMQ(VksK1_9x89co+vL zI$NOG4+S8)8PCtKV{TJVe?Tf_!2)Y9wHfp*MZLu@$t9TSs^KAsX~Fxq9dn}8fU zW8oVG6P3iFSUu~ad@mhprE4X;&w_%w51zGyl8hlp(M9lf=8QdIL7-sV588x#SO7EV z3UZ>Mz)tp@tXjjyUD_HzG{QKoMn$wS_Kb@INuNQFEnS*?6KWXr05kOKLxz6At-NHv z37Oy{JaLfyF?mh88UylT8I$y`z+sRzFJBbnaaqf%wB7inR|4ZJSFEg|S8nmP0~yr zN8>AT72W(O*!&XLC*%k%nfN?xH_T%?j?fJ)OMdTmrR$1v@i@pxwyAWrFeC`AuO7#6 zn|4cs29-dL%$rw|fXAI3sju3iA@CK>Vi1GEm#^?}yeE>CPTfyz$1--BZGno|3B?B}{|P zl{Uh>-4;|dL3gGYiT#`N8owbPu}FAv;e4_6L=$;5jJQD*AJ!Iz z*-csy`}tFU$^ukVl#^~9?k%=TF}H!q3`;QLqYk7?$3g2dKT!#$Aa39IUIXJmEY_zW zB#HtO%QQ2ThC2raL4VPi;q2QAknMX8ScVT7`%e9aU*0sL+*&jq`0rn^4&YMLIE0-1 z2l>(Q8V5X;5t`(f^6`i_=nk?WR&;TUlN&j`Pr!&?c#unukRgGr2WGPfPpy;fB6A1` zB=nLAd4oh0|JKixT*t-xy8(z06N0`6?-Y$plVcLRuIJsO>!A&USCaf;k?w=&wGtxi;qFbXIxon<3SG* z&0%TH)`^_`{@ND`Cl0O?a0A1$!b{)a=myihKIe%^juLDhIF3YA=7bnz=v)U3Z$y-@E2tc_`2cs4{h3c29@h+GT;NUq%5GIauN*xAV_W|am( z+4+0bj}IcwY?>Kx6j&tu`Mvcqc01g551_Tm^tbUMg^02|Hx0@#}8+sX>O09 z%tFtjNCn5n)N&l@(jdqNzHD$!w?Kh2@h2${0Lq+TpqH%^ghKR^;UQ3znzDyWjaLHI zX2XS{3Tv3GF`etWxl|7YXHo&7G#qF*Cd^UZuoQ-gn&)9wLI@z5`=Zk264?+u_Ma*~ z|CAs8595rl-5?7gD9x;*g;0@Km6{Ih5_3EfSk`|wfJPt02hsgHfyvS=lAW*Gci+XA zX5%J#hPIf-OJ^{7LdaC6va!}gU4&~y&b3#zJPKk;%pjbcr`;NbXx0Hn??u&qQ2+OQ zu0bUk{J&h-}|ZNG7-VwU28rt%AbC;4PvuW`vj zav3?FNNg@c0=dk?!k;XN?-o`>9u-$DG)AI<4!EL{7n`r?1GXlAwW-HS0cE+<=AzLI z2tpxY2#add9tQ%yMxvQ*EpOEr?uQrN3^;?9wWe4w^`rO(7Bc2$m>JC@hV(vL_jfZHl{K1>k;=dvE*|^v{U>{QTMtPn^3J4$6lYhwMPL^bd!Q zeESR0Ly3KUr~y%q14QyW7sX0fr%bh+1h@G})U!A8b6^GI^Fbsb4YF`_x#G@)JrcA0 zOrCho`J@g~N?e_sY9WI|Z~ zhi#h3(VA!xrHKcv_`r3p;isTS)jI9lH)sIyATrf>#vZ`9l)Scegz@TU zKqZtFnO|V{#=%M9d<;-RRl&zF=(;X?L*H0VSFP-0h6jg&z?}*}?5p$T|9t-5BV+P9 z*gTWYjHKF(Ti4!Po&hyi?uWo_uxt1qJPhRG5}}uN&H#zV4g#!zd2#U1sX&MLzM%Zr z1u^0WCthLA_HBsCkH_CAMSCX1a&S&SETnwu$HiNa!HWF+#5HSx81_)A<7d#-yMVBu z5plH~8je%4n^Uh7ZC7EM@-u>$7uOZba@CU%6}o!B_{~lO2J;l-;_;eq5qmuHNhb?_ z_z?tNbftm-8Bf04+mL@|2x5UH=$T1^7TDY<*oikCu+l#$71^x+(E^yes6YI22TibnvKty*s8)q)co$P;6!WRp$EVV?}X-Zhb>nMCU@2?WGiQpzYWIgLaugleeQR z_2&nVJrKgn&8UoIRQQm0qkRzuC8qR)TB=LADf2rddUpb}&MJl};Y^wB)CHh?c8CId zFceOTi%xefXx959cpF3!5-4@*>#&P+Rf}7q)?8t*6{pyDLeW{4?ez*EE7tlt2@7ow_#fVP>mOY^mCnyZ>|LcI( zcOHJ z4aVcpu7NzR!EX|q$7w>`*Smas0W}n^q;Qw%ibwm)6&sPN!hk?kK#LHv$@^mU+UQG} zBP>6EIK}-!z5#!g{_qrrON5_lbhJ-#-&zi2Sa1DGZR7udRM}EKVX66XY!Df&kSBYf zFIo9JGB8yAs5DtUe>s${$1Zf;C-&o4&!f&JCQn!peJ;>zm4pz4PW)MtSTeGX7h6v1 zqbO2w4QwH_!A2TIydhJ-Ti-ObeKx$aCh*%spCB z?Du}?RBX68Io!|hwCK{Lel&39-dsnB!$F8YrjJ#F>uEs9-Jn<=3A%zV79onbTC_-L zhhy!7?h`RCau%Mj+}x1IY83tVs25!$AHhR*br*AJ0C_pdyn2AI{0Nd6Aivkqoz(;}oAK%e_0`|}_23^@jIrZ?p? zJTxFbi`6jr{4AI}9<2iX`_X_DAr;Q~t%mqQUpD~f=IYlH4vK@ISQ^0i9-6itbch82 z$f2WCs5trKLy%5Ta^Tw@M9vs(Gh*yyk6~C{J)XEO4mk=C=?i^hjm|TW;%3l4QWzr8 z-z`;%BT8>HIx5n`9>+&laaXx>+)mRridQC4MwvnWFi^4M`1i_=o9U`Jk3_QCClP@gkJg|rGhV&+E0H{P*}F#smt-h2z+%Y?{OhfIeyPS_Q74E)hn)NTk%pke$}p?;N2>-auA5XP zb3raYWSTaGTE!P4t&+&>^uBeTa#lOlD#=%v6=CJo_cNLLwy~5S=em;);ZLhqUM$P$ z!QqC9vQc~sndh~299eoR6M_Z{3@oGWT2Fo8f#7+hp^HV$VCC^duE(TN*(2m8Mopv# zxXND3(M(>b7+)i>mdYf^wWt-Nx|mBr`KylsXj^<)N+p(iSpnp2ii9-Jw(p2AE5Eti zYps>i&*L)v6tk|J(++I3ed7h$k!*@}p`iU31{o203}@OcMIQpuFVfyp+(`;C+`-%r zNz(Asj;9$&rymh{CjV6NmigXE$#BWEd5`^s`Mcdi#&b4nJjM%o>v?O&6*?^%E`hDf z?yBn=#)GSaauesN)*s`)te3SEm8*hTd=J}`y_xEv{DTPl;WM7{6`?J`DO@$K?;4#& zH!G9*o{I-?s#J1V8~z>_FF`ZqVyj@S@z8sHU!WW^*KWV=2r3kEl%tnwK(mz){)1b2 zW4$&;sAJ#2|JI$DcGpt$&Pwsj1ec7LR)6kmE%)2L@4jr#exzTj%kU7Bf7ojm-f3#< z_U&4Z&~^yxOMxL)|7#{v-v~=Kh+9V*T7(KE5>@SF9v+c#6=kM!uui9UF;F(XE|Pri z3yGG~c2_TeJKw$g@r6Q5V}H|^Q_qqmZy}<&v@W8VR|+brO_SL|`-#WRJzhlTD3p2H zR=SkB1Q{C#gcqIWHy2!|ys9R9+*{kWV@r_ZU7Fb@5WJNzPXb=9MUpYfw`DcX(Da_b zJlMpPOi*ktHL^E9W8qyQAjP>!`Yv1OSd7pr>gb;ZKc3r#lxIT=kMeJ|^#h)?Pz%Br zHl6Ryy90LI$WX@2L=oBPP%@}GxeQ48n~u#jqD|(*2OjBgWGHK zFNWQB>jRdOSA&f$$Hz-Z#|vIFYa;Ojx*jFBzW9D%;Vqcqx?;PlWj>L&tiNAbWw70S*;-^%-_?tqSF#l&cFJI)pXBrI%TV-ziiV z?lp<&H(+)691r$ygLCw&61#zp^Y{NKM%K^C`7Dh_9*NUt1OG zgPG2&-b`pqL0IQ#TjIDk=Ok*)#Pc6N+SRas!a~9CG(IJq~XHJ(I;d6mc0=6Ni&%KMFIsZ79rJ$2%?7Tjg$SJ~WEH zPMgI32ngs2JPxZ|t=A{N{^0-B$zcBMR-@yjT~||O)L@%JO#ZCiL_Ez~wQ?qk>Tf%{ zyv8q-Z>))wq&|#2PFM8v$Y5+2XHrP)!dYfrx2;OP4-#+9Q_|)|dqhuQ%=X+X_#Iob z)3ffVytx1awQoN6X`p!2x7{-@KR6w0GuZL)Wt@jb-h)`5TW3A$stZpv7Y16CDpeAB zyLJlZ0hC#Wy1U!V;Z{MPwMo3sQb*QyGtYf^U9J7-ofVH^$wiX1Ta%?1Z*NAHZ)BAJ z+ECAPT{>neBW|#u>yo-HbG12F*6Vfs!=z;~+1-9E+A7~C40q2|L~_Os9S(t3b< z<^=t2S@VJD?K=6*@BLTj*}7UX*e~-Ao8Dr!sMvYR>hWXqU7_IHnl9^)k9O0Pd#&dM zd2KXScb6c=Z2%;*VGl-oz7*78k@m@0oK1^W6c3lj&Z6&6=Xj(Ca9NX@VC2TkX5iyF z!~zz&D7mBd>eJD0Ly0G~B%17M6i`3&M+iv#P6J}V=Ir&VTvIyC-GYYlSFhMF{=9D5 z$Jx6#GI-j!0TmGmQ3oUVY0KIcebMrhU@H8t7B3C&Q-}MC*>lpF7+fOf8GRlk&$*b; zKi;3Cx;39(6V~D&#LS?YGl$t9{gK9LCxW-^)aJ!51yNOVxz9~8+p@kUqO(G}Vnbb@ znr1SXDGb^kE_+l)p486aTy`FyC}8JU-nxC8g($fE<1QsnN|KLeS#ZS8=03T^CvpXP z>;9%EELA%j6#=vDow!o@?z>+E*iQxV2rd!pCGO{aKSn+{ei|bg z3St5Seui;oN)`t>XBBE{JLAZjh*K6-7lVTr9tByqhWCswx79JS3tA;^Qkq(RR>B?D zF#Z(rwWU&=_k`;=v*jP@CpgO+BaAB7UpiUuF!?^A9y%mV(f1{Mf7Yj`;gJIsbC^Sg zgZp=z;9}2n3)TzeN4#Z^#!q@(*Zsw2KY6wIt4$}R!zY>(d==bj73I)(p1<~ji{imU zK**2XN5*}DG>HhNd`S|N1QdN%S%s^O4)$+x#bz_1X}RzyuGs=2aXQ~Of(Wwy$Av%o z4d(EE?vC_VG~cyvCZXTkywnNsN2#P+-pOY1JD(B9R!K{bz2%2tPn91WZa`lqL=)a9 zPu&aJBJb4G1Lw^gZS%v7<`yDz?@(VBaxGGwkU7V;j~}>|6n4GX`q#+8XhE?%`Wj~4 z1wZ)}Tg)w09@vII$(_#|=iIF{zcgUQ_;@jfj9KfG+4^MATu0e>(n+?MVV+wpWh-KN zJAs>Ap%;n+n;9}&cZv^Ku z@7=m@g1$j1HQwEWwRJB}?a}kO4>PwmQ_|B^E$8-P40{yx=yDuk0BB4t&zWlx1WC)L z(Nf{8VS1k;zz4MB%Ez62xFz%}d1vM+#l*Mh-How|9)mA#gQ8w+qzQZ6)9Ev+>`{0N zm0ej2SC*J~GQVhy;tC}P7U$}3tCnmBNd*+>7i@S>S>arWs7akSmw5*k89IJ#pb$r@GssI+WeJ$X6zG%UFu|=huHX{`HvAKPivahn}n2!-z{S5==vJPS2virI%miK`W}l zoQJ`sgM|$EX&(sIH$I$6?WT{=QaTyyMr#z`Y~N;Cz)$zUok}|?bxXsqMWzPI55`W$ zn@?K?bDItDXFaUkv-S?4`PRX@?a=RO-QJZwxO$uKdL*1Qx=9_|`26{6eXI^on4`3Z zZ^qH8N{*VuG>_8JGRO%eUvt2W}*KY&KgrQr&Jg(m1R}ed6$aRi7tU zP6Y`l9Ptr*!=#?^ZtPm|Qgff2fhr^IO07lJOW^>ep|#yh=kO-;_Utk76b0v=iuFX# zYtw7)EohQEPo6UBt$!U)=MebvRH5LK*N@ajyRnx9wQ(cnOSNmdX~Ws8gRK=}LYEno z`M&PXo?r(LRvhQiD`PY$l~>!Cd)Qa(S-WL}o-ftJJQoa^M-)>wcc8f;360?aJKkkz zTr>bRxhWU4qFZ(*@NjM-Hv8CfzE$+@Kg;{BaOc)vAg`{DNe&4O?W;q4d(X2~M_8S=}}zNW;mm>PPPsY|E9*Ox!+?%RrpF zxxJbs|EbF;(MryZm7b90vKJpWXUz+-o}o+E2Zn~TzD8Bp4p+F?OXM%P+~Hgd3LV8& zGM~R8!d5(by2!O9Aa5_`w1fAD^Et7W!I&9t$Cxw1+~F}&vE*n`;U!9XI3=3A}S_O^HqE$d~kUa zU=_$T2`(sqw;!j&ueffuvFXlU5AL16 z=JeI}Q0pozdNn>?v|Cd)zC%WX=IgRJE5=q&eh*BPIE6TO`r@VKi9pWOuZCX=8rz~3 zcLSC=&u$-9i))vY%}~{J4oryEqtX7gqpyF9?sZd$^{Jq%bR{>=W#6roPY<3l5e$mi z#F%ZF2l8_#r{LEN&=6!I$wxqV$9lJO?(+JhWIDGv%ZBLWM7!xW4@pK`Yrp7yyT(f; zWgkBqRc`-cNSmITjB#2B*7n#tUlEgNcp8bQ$*M~Rn)qtMkyu^-YI$zjK&SgV2ZhMx zA9w}abd`>G6c^{ms4fGvGVDQ3#GuFg(ZDwA=*5y>)2i0`{Ws^&+nm#C=lE(-wa2)< zJ{JL56zu|=>)pz&SvQ*<@wZj`L+nIDgLf-6mi;txl1NXS6qQvQ_$GmQI3!?g^=%T~ zwxy2OJ*DiY=T<((l%L}I46E9(>(8x@lU~&4+ND99@Ii}8vIu_3=yR5_QGfPGs9x$m zKecUaoAB<#Q$eIb9CJ;8`%N2+f*b2pk_#Mo+Im!-<({U8(Pi$#sFfAZz+TL!I@FZt zG|N9P=0O%HqO13s)iCAH5e<2LCFw+E-Dt1F9+tH0qYyJsWi&v8Jp_|9wzuaic5aGq zo+s0_V~V_Z_X$gi%OLH3?=PqHlxHHx2|%)Eu+j`S$M@6PJQIuKif_z#@&;p678=Hqe8v38S4J8YGp$t|JdDM3 zkIr>Jlm3`4vbW zeaTwnbb;=<2PHr-}U zm!y5=;MvbL8XDwmVz^M8%<#~5iMj+#qya#!a!u77GsN!PgYteb8LK+SZ6~CS0*%pY zkfj-n=y{GKk#(ev5_SkSE zLD;Cv^&b;@Qmr)jCB2FqAH$)1UCYn-oXUJDm9|t{>OO9^H8xy#>SNgjE+HOgK6>Zw zw`*V#L$fvAL!@#n*l8;As-K=SuP}{^`}&K*p-fIlo%O4j!?o|nPvn%Zt&AP7@oOH= zi8?umxwgGRv6~+!KPL@4HPK4dpXShbdvz(OJ2ZYo((6mJOtDz7M&=Szhw&^wlh(zp z2UIO3t3{;MnaR|;M?G(I!sx}Kt#vUa$)ueNN_ zhg~QX??@qszmH(PdJu1Eu0uhzkmJbXonM1@ncUTxcydRrl2hVWOsonudNvct6@N6{ z)=N9iw`XxP`3Z~63>l~KH|Qa{uC(9DwVhH#z)L`2yKLkHWNt&4o=5(?0ImFt%E#MP zS&tKXr*WFq;o!28fz`?jd3gcFm5q5~ZxMg7_>IFz3F1EjE$ss-*1FIymk z2(47K;Qnm2p7??t9+F89H}fLN;xhXj?ljI4@v4j}f93d{1 z=VUDBe;9d?QM@0OzfoN_TQZc*1oYbnzU~5c zr2__Ak=|?fUZ~nfhMBc%=Wrnmf7_YvX4Y-!VI#7cdNZ4kA2%bH=$fXIJvKf4eTfDN zYF_;P5+R7d?8|3DtdUDZ+bnz)^>o%uaEYXZ?Xfl!|Mw*#;7jhm3ekg|5qFr&rHV_N z$H*)z>^cKZ4(42;OODgnCwSO1c{9(vxxtXr>08n-kw#2Ps!qzxpqs7HkDj-NW(yv% zhL?GR!?&Uahzs2jx4gJmF<@95Z(F)@Zar?hH`?k0d&}gtiZ4=NAZ-O>hJTyVlHc*K zkq>boX*rX|#8L9?Pk8f{?~kaN6C5_A@WJ8Y6}VPeHg#%Q2t zkk##HTF%GrX-0ZH!dW8CiCqpuM?5I};aT_fo{=aUBE9u?NuuVK_xQrDOGT`AGkdg< zk`Ccvf7_Ui*~y8b`7m}}cdmu?kND9Kh#xlnSde;={Q2WG7i<|Z=~ug(oI9g$gdo3} z+nBG)0(WCH}g8*s>vC(CQ#Y8K*>o86dRgz zL9$2!2>KtpTY+3UU$L&kGajxq@L1~G5m({r`>^s{{+UeJK}|S^vDxXb(UG>lUAm*{ zNOs!cb4>F6BbXu|ZvToJ{j(C%S3-^PG%o1fP4z*FPgyk)|Cq5l<2aX0?it76@Z*O` z-FI4#IBmU1GU_DLo;rc~If?J387uGIQZ&P*(30WehkpmD+3@qf37g$y+KxfPzT3`&xo@>7;$097M^4ftR?XB_l2y3z~{Q+;STo?Cfw!@(dmrGM4(^?Pbp+kmMdDXN{=OG#vGs<+-y6HX zxBg0+s$HRSXKYT|kIsul$YO|*bl&`tn7);L=Se>9kyl|YL^1D#8&qy|Yf!C~$<35r z8c062{X2cvn_2ysZc!DkAb{a&v=fO?;EFW-CU&ODV`)#mZv3P}d=MtNhTOPxWoxY? zze|X&{O3{}u9|IC^Zw>tG204BV;PQ=1-Dkoqz|G^##J;(lILcTZJ~v!%1+c87?T(N zWYJ$|xJZb3Ks*)Slfd~J32%~?)a>Lon^NcurW)l+6T4hmG4bE$$op#99e)Hk|CpzY zd1yQL$oi3g*{y4$75*tL5MSiF&B$?Dxbh)llrBSn`>w-i`(hNdza`zqLnd^V=@=t` zL6x`Ex#ZW(UP7FI!AyXL_?>zpzljoapdh0 zU-k+=13NI75`5X{NZD0 zgzfeA<_lf-onSIHMdgObw`U1l#%xp#w={K2`_>wb+S^45i2^$nvP3eai{!>6FMrGt zdK3zUwop#rsHmNjNl!%*`1;=O@BFwvH8`!6=Umq*s#vKQ@gVk+>t@0rUz&LPC?Lii z)jzu29~3(zMPbHt&ZM=euxmGeSHD8Fe6rquGu@7$Z1Oh{bVdd^x z|2Pz|A^|IcNHTw)Z$19MSDJuZAL;(^n0TD0QZA;XT!g;eegD--4Rb6S3B zA_xdv3v_{vl;YWeM$TIH{sfLH(&N+cmcoYhOVZVAdjMcc&}NQ9Rs8=WBQu2_qWV|m z8rBRXBkN`EL|JFFWss5ep3=txf|QZ?6fEdnwf40~ng-Q#XI|baSh%iX(pAA-KybFL z!+-%wjkSabB?*B=mV$r|;5biY5Ohg8AmNRz1^YB=4@R&_{^u%fjlar73iEu;zdOIy`B@bn>`p;H7m8p7EqsyX~gR<6YORAj^Dil@<~DpUsJRr9yGb<=~Z-b zrdYA1nfM%?>A#IK33-D6Gz$zF)e??=gKd)wxq(8(gd2{*wz-lj2&3C}7OD3f#e8bZ zFe7pFoW)hc3BGyw>buXmaX+KZx-J5F$(jhQtJnGPU_ONUKD-kd8g=%3*#Jl)+uPC9rUzGp)U0tZ=H%PtXt1_wQ7y=u` z2pP-eJ_g$u)>J!%-YZ2BI02#*Pwt~nKPHKN7feiS{2`bpMNfM|;_4_f60r0%*l?hW zT#fBx;y0ys1tv@yB5n zcZ28192N`b@Weqng0v@m(LZ}9{&=q``Ac#Jx*Q7dUdbKraFBaYeE|%iKZSoX8vTo$ z!dm)*hZ^Xa`QvCk7Vbb$HGv7Fub&{>u2d7kiEi{oUD)=`N5c5%*8Eur1C&kCL3*t9 zsle8626PWmqL8xxxQveAlYx5)#)1s=Uq>irrQ~b6>`hdtos6`t~0*gj{--h{b zzxrJuDp$X83VSoLN8rD%;}tl$ekY$dKVd@7)4f810u;twhk@6$|NIRL`acCrr1)Ni z5i5gZ58vgod|5+ZDU%Uv@K5D1H=vaAKNcxu9_&`fE&*sxB5t}lHh3ES&^vOE_X4D_ zIJ%>7>2E#|okstwu*a9H=sy4xiNZeWD+myUY(x~5NhDJ-4T-YwS=xGZJudeR@y0uj z`avJT^J%<4b~TVEE5zSf?a>__=s&}zrH)Le)jY+B(v=ZwEe)2Znj(DKK(4xkM`*tI zM{W_FCi z`j@v@LLMwZ!6q4EFu1kMF^ZSa15thkstztTEm#E-54cPlvDPAg{S~_2dp(~J4hpbk zEY%=w(^&lT+*!%~9p`oH{dv85_j`Q*_pYM1*w5!75iqnR zID*d!`fP^?RX$iItHpzp>%CiUEdjcZs{v!#s{n&rf>P2xRIi_yJXji}9N&S#RzbMu z5K!p0n8m9=>>0&l!Q@NBZLu-ZpFsT{SK5e-i(neZ9R8 zA>Sb!(3VyLbR2MAdE=53r6bRWbDD5WTFR4CMbgn9DGiE9%D|GcAe*#uuso-&xUH)( z(>v_OK!YjXSEy@g9~(W^vL^Ztj^;x*?kj)0g!hRy571JVlD&s%Bq^llF+w z6peAl1al6C4xlbqp(Xecj+CT$S`o&-;G7nB5^Dg{ZA;;JA zXG?yWA5jM`sZ>-cTsYP@p4}|{;)@TZ1n7pI^1gxUl8r?cG#}$F6c7R6qMyT{nw^BO zZ-56Hof(Q;2I};>{DpWaz4Nj$kGUI;>qUHHyi@`(PqQDtwXmG}9iVhTbZEJON*Y!{ zEvahl>u=c*a=Z|xiqB*MIL^J-P-nE5QPp%9xbOo2v(+3Ct6LXlKIFssVYE3E;N(@% z$55B4W>Tae%`+bNZm~08^FX&h(bi%-|A|!N28pQ2HnbWxld@P0wOs^O!+(GiZ7-ye z7xXxCsdcafuqi|kC-ymGE7O5@w97XqY79(=Ta`2sHT>#H_H21guCI;Kmh5Dag{5R! zG}S~-Vz)Hg7NjpIzJ0#y0J-haCv{N{Gx_E)nW>-#hJ};XZXIb?x*gC*O-vX#W?9Qx zfe;6W73X01nwNzDbv}Q=l1}5p3p_lp`m)C50cHWSn(dU23;Uj6;0b#w&7_tScuShQ zTqCM?^|FnK?r+z{4UJf0ew0maYP{gbJCm1f9X*G=y*0S|oxRS1n0*;|zHR-u5R>)YA8}6nETB3#qOLGg_N_DoW_AbUiV4kc8RkS+v%swo(Iza zN!LFW8=HwLkcA+fQ=PXC39Ok~M4(AhKtR`X_Z6^en*;o@dNj&~8HjZ)Lx9M)_1R#| z0ayyge=LQ)2^)-A$<2lVO95bTLK_fSf!1gD{7E!i0RUyJQ5NQ!6!MP>WlxG zP5u~s0@LGh?~zB){9!f;JEpD|1YJht@V|5?W7~RYxsM3G(?A`>J>=nW>==0C^UA+8 z9l7_V6+#LDNbg82KAI0#S83Sv4pXm%>b+vUN~$4~EJugNH>n(jaTLHsFbvIe9Fu@& zJwtTmhb!f66f&wqZbr8^&ur)(7%1K1Ex8P{TmQB`Vus6octqXI>*a5(iv+>O#;VD2 z9?PT?S8Trqc2G(q`1qC0wRvNynfy^|2KBsm%MmYDGi~=MnZtorbjLq1JT(nCq)%#S zQZeP(<|8tUIlljXtFZ<~<10_GaBu*Ei2N;)Q~oW&gEeBN<(s0X!9+d>3{qY{`E95! zAHR6VwX=s(9gg+WY@=?Dsf)P6d0#m69gHc;88K+;0Lm&}cRy&BI|<5F9>9d_Yn^&g zRap)EhWsHg^#(Y7bw>83<=yzt#tQAYDX^Z+Yu^j7au%bmY}WWFnEi&BKy^1jg8dORfEay{Ki#_UI1^>ptVPyLk{_3{)^w(Fb!-EuN1FWa0^YarqPG< zdeu;N0*OOhA}DJ69NzcH>3nz=^OMRqpVk_f3h`Zzn}Z1qH6YZzjdF#0sMTBZOH0Tl ztMj0%Foe?PhC?X7(+3Ap!bDs8b=^5Iu3T@_zX(Nf<9lmjdTn>&;FR+l|5W;^-A3o2q6c&PSv0y$YhoRSA2>F0NSxrG;mDuHq@OrdjMnyqFuBZ#^kg9R{K&egIta3%WlSWV>3`?DeM4nYnV zI7@>>srd2!qN^*@4vMbUi=NU7DT-u89_xVctQ+lmZ&+j1uAv7_zTEjN^L;klXL`qH z$(dAXp}@=Lk#@W210+d%xP6xS%)g6vzBddBwGnnk($?X&{M&st08BC6G>hB9Zsg3_ z=Tg7_iwO{P(Y2VA#Y&8SSPgvionDLz0{2L%P6;JehETMpYq9_=G)^TXa-bTJ_w?ZB zedxp`#rg&SAtPe_WIRGI&0e!063$`t9jv5{fJpeMJQ6AQ;ol}}Vu!%`!VAe)S2}ZF z0^xPwT|YjK2O82dH8Hy|NU%xCdJcE-E0KoL$hC(TxQjIBsYCEK9!`hr!2WhYRm`@ zg1a*f7{AXJ#>BqiYNj+PJOv(FFzlD8+m2ne+M^LGueaQGJqx_?MHZ8Ri)Q1ma08J0+ePY&&Fmu=M&;dVk4&F1&s`ZTm+c&axC~gjG zW8NAlHbzoq2m(%sU%5VBV z^a9OLmL(7=A=Oj~zGZLf0^K7tu?^fBF&h=2Z;A(#HwXQZXl6{^+yUmikI&v^0tb!_ zFO}Oc@G=Lw6HiPAw;%K#%@K3Fc|?+clk^%*M-8;Ac*L~8}CF|t^Fsh z@__gya1QWh_Q;A*(SbmCt4>Yzg&6hZ$x=Hl$XXOaxLZ9?`l$CV@Gx}u3>g8)S0fn6 zb`Yq1tbeC;QalKn-23|qHk6T7^`Po}9k;SIZrLy>KHz7Fp#4siZ!A*t>%uex7i=%c z17_qNkVgw-D+40O;aTROEltAz zw2))T)gGKOiqoOz(5v`s^i>KhGDhOH&F%V*{CpN~_7(?bsyd-ZOgv2pdUTr8YbA8` zeG>cI-w$~XeD#=*@3{B~P`;8n^#&96V408=s1f&E#PlNvY-i_J@j!$h6{NWUQS95t z%9sVa^9V1Vlxdf$u^9Kan*Xg=jZr9&TApn368Vjv&wt*7JzO>@L5c2!#6ct$(cl{I zXpN9ZCr?xj?w)OAe;#>N1dtp;>C0$lk@NpiEn8kak|^Texl?~O6nBljZJWXxTZ!4> zCeiQDVNj^1TM**%bw{eA#lH%j3I|lZr{0;jxMo3L(#l1dv78B|O*v`9F%y8jnn*8cWAPni6tpD;D?`n_sZ_<3}Zqwr6&*n%>}S+JKTGS2NUMK z4?&|6tNQa`EA=(Xj1c<|!BxBv5vN_lC*uCt2M`v(CxdJIf(c8cr({0B&{@O?GV_sx zi3}xFn#5t5{M;#!Brb+E<#ANj%l#|V3@Ep}bD7LYuk z1z69YAVb^qkpm>9TlLu-whX@@?YW%%4qLlFul(OhX+xgnsb3`afd7@e$Ie(Irp&$M4K#)R2ZS_GTN#T9ZZJ!fI9SbX-n}!1wGV%sRxjR0dZ6^6!k^N_eJa%6}*HWi1-4j^;dST&Lmpud{pTfom+shbY3bjN7jdOOh$o1`Eg2-9|GzzH@OAgI zcgACjP#FoIX4X0|LrZgc>i~bTy20?bMI!zIslD0z7o)5(@hR9H`w7!y#jp^?jEvY} zcqa*|t<2rAd4IVA+J{l$(FNZWosga7crM&Oc7ld^2QWmctNXraezXR?mU3^ESrJtw z%KEGU0X~1@CJF2C0luU)lhcQ4S*J-&sfp=VPd<#a55&8Y3SdCx;_1%eYfQ%Q$vn2A z7<-mvr0UNzUFF6fsWW(a<4kS9U&emtA3tb?X*zL}N7Th63(Q-Nq1*@c-OHVLojw3B zyxru^8&sx_^MWM|V`pzgufKyCYIHw#C)mS{1jA6FFaEwMofPxQH~0bO|1^2%SHw|K zu}W7g>KZ^4`J7arhG9zZ-dT9m&_k2hlRiK;Ha`9wJ!WHd;j2T(-{DCj$?=|1j=Di0 zBBc8-xppf&0%p~wEuu;U9U6WG?ZenjC{R8_EB2NS{0)sOdYK3EZ%}C`4%L|`ICvD0 zQ@CTR?&ErtPT@Z;{6D{hCInm>^L>>w`A5)+pSj=Ahb`b$c;W-%5^|`hOj86Zq^V#% z8PykwNkLgfXVYTHohCKBHL}+%Y5AQy_~+fXZ5IG-=zN~B2Jg}wC#A>hKLfZ)pfUA+ ztVw`47M}Q-%me%!+9r4xhTbcr*z(W_J|BhS#gRP!Az1h>1Y21-y1xkLb^>U9Hl+)3oqENK`K)O_ zr|>*W|CSO^xkcSkSitayU|}It+|r`N^k1L;2MGUQ;-}0jm~_6nYIYhugUB9=xX@G-&$2-7U*u;*VdVGA_qB4vsd>;CHqKY*}F+u?xfZD-`O*591POC)#$ zse{0nFe^&kMU__O`<%Q;( zpYJO}sWlnm&W=nP=dbpL>qX5_>pA6d(mBuO@I0)!pgJV$%bxfHdn8hU+^L+am<$t0 zfLtk0iy0x2utaWodvGFBUxM#Yd7|}I3l@VxCkl!^$L)vZO#UUqAEn)(G;s~O$ot-X zQc6`-0O&eJ4x{HuluTEFdWCW;P$kn-+}1)$YY+&i>H1<$u&5!+*`TP?i4FRoc?ZJb zW~6!^X|K2-IQy!XIfIa81ZT>HydxoA8$FTgmRFZj6NGu)m?(kaaV^&8abB~%)s0GH z5U@ALjmWc6@7O~`9S9@ID~4qDal3Qo#hmduVODln4j5yh)Z>F&jwo>$(ny4P8z}kZ z*btiZ1d;0i+~GE`A`>kGjfgDut>U$W70TBhk>gC~?G#}EyRz6mYJA|zecH*1nDP94m zts~@`Muf5<6O%~z@HP%r9>;L|$BTG6&tAzC+M02%FEkIy z)ms7=`6p&(!aL(v3*DZBq`^yR$Yq}GGhf-IWJw#4akrv+bG?6aIau>E4a}XaYU|&L zCQm|k5OvNw`XwK@Op|amV@eu|USxzZh*1B^OL*%h1fUHZfVbb#UnY-&RM7+YBV=F}>aHNC znRmaxxJacfMTrII>7_eXXj~9RD*Hiall*LE4fBg`;{vA2)_7xVvG&s3@%o77qE5D_ zqr_eHui2p?xru+gyfv=RD6JGg<0>Ex^ofzys^V?5DFf9orb-7`855=e^3 z(D>Q$>}AXaU+$4(Y}#Uw1Im0G>nqKpTYHRCK8AT^>9KEDrro?QTi7s=5Ni20=bC6D z)e>CMLbza(r5EL-2$YzE1$?Dsn&jlCT?MI;AY6YS9VClX%W=ap63oC@lEP9{C??jU zrTBrgNqa&I>bDgY0bOnRz-j_(bHX>AiT_BedV4?H^xHMNwb=^9k$%Z(#gYN0KPeUv z=<<~YePpL2!x!;rWOjJ%)r8Ky7P@OXrwJ}Wdh*-_im&bCYx4~uFj&irGvbnA8>1bR zed|`>Tzw&}Uxqv(LP_YJB-~h<>SUXGNwF~AmKv_230dQDpal5Q(z z8+q1wD7vykp*)Wr3TlD@sQYQ(t7d8vaPWAi@qw8um`pP&%)}5}1rgYrDoTZ&cM$pA z*nb{w1#>wmrho&U`%EMt3bYMzzx1483TWyyBD@3P z7yb6>N!FETGfO}!8n%vl6rk!&WNJOrmC5#@6n|$xc zGhRcA7z4-bYJx6|GPhgV8#6u1xeLV+OMlc)U|h7Q$aXSSQkPBudoyro=?F3{h`!rI zB`gu~TzZ(+>`$&ezL{m=60`K=Ls4Hi*V09M&A25Eps&5S#L;JAU&HmF<6`5{j>T7# zpjJ5hD42g1Kftj+d?kXjv$Wh(MAYJNvV@^+K>TgRd~iEM_)N;EY`4q$q-JpfqK?hC zXjCwhy0WiPBNNCaBQP%>?3UAHr; zD(oY45;z_wZixmn80Bk!v|xV4L<^=2XswPXF$03AMCkfOxIJ4pKj`)u{`y+bG-rW@ z@=&)@yM}ocT@xhU-yOxVj|~;7w*lT}UM_lO@^WKH`GQV1jO6Q$xHdUImDRwLSo34m zZ4dQD79m~b(jC8z#|J^_88`m5AJ;>{;xMefWWD9nsaUli=hL^Y3gaxl9M=;CJ0;mM z7^HOD9}Ya_5j{$#NkCB;3zwP(+qoEqTCqQa@_kEzGBCPo&1|y∈aHx^GqZa??*+ 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" + }, + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/open-telemetry/opentelemetry-js/issues" + }, + "dependencies": { + "@opentelemetry/core": "^0.3.1", + "@opentelemetry/exporter-stackdriver-trace": "^0.3.1", + "@opentelemetry/tracing": "^0.3.1", + "@opentelemetry/types": "^0.3.1" + }, + "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", + "devDependencies": { + "cross-env": "^6.0.0" + } +} diff --git a/packages/opentelemetry-exporter-stackdriver-trace/.npmignore b/packages/opentelemetry-exporter-stackdriver-trace/.npmignore new file mode 100644 index 0000000000..9505ba9450 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/.npmignore @@ -0,0 +1,4 @@ +/bin +/coverage +/doc +/test diff --git a/packages/opentelemetry-exporter-stackdriver-trace/LICENSE b/packages/opentelemetry-exporter-stackdriver-trace/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/opentelemetry-exporter-stackdriver-trace/README.md b/packages/opentelemetry-exporter-stackdriver-trace/README.md new file mode 100644 index 0000000000..f25d5d9329 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/README.md @@ -0,0 +1,92 @@ +# OpenTelemetry Stackdriver Trace Exporter +[![Gitter chat][gitter-image]][gitter-url] +[![NPM Published Version][npm-img]][npm-url] +[![dependencies][dependencies-image]][dependencies-url] +[![devDependencies][devDependencies-image]][devDependencies-url] +[![Apache License][license-image]][license-image] + +OpenTelemetry Stackdriver Trace Exporter allows the user to send collected traces to Stackdriver. + +[Stackdriver Trace](https://cloud.google.com/trace) is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures. It manages both the collection and lookup of this data. + +## Setup + +Stackdriver Trace is a managed service provided by Google Cloud Platform. + +### Installation + +Install the npm package `@opentelemetry/exporter-stackdriver-trace` + +```shell +$ npm install @opentelemetry/exporter-stackdriver-trace +``` + +## Usage + +Install the exporter on your application, register the exporter, and start tracing. If you are running in a GCP environment, the exporter will automatically authenticate using the environment's service account. If not, you will need to follow the instructions in [Authentication](#Authentication). + +```js +const { StackdriverTraceExporter } = require('@opentelemetry/exporter-stackdriver-trace'); + +const exporter = new StackdriverTraceExporter({ + // If you are not in a GCP environment, you will need to provide your + // service account key here. See the Authentication section below. +}); + +tracer.addSpanProcessor(new BatchSpanProcessor(exporter)); +``` + +You can use the built-in `SimpleSpanProcessor` or `BatchSpanProcessor`, or write your own. + +- [SimpleSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#simple-processor): The implementation of `SpanProcessor` that passes ended span directly to the configured `SpanExporter`. +- [BatchSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#batching-processor): The implementation of the `SpanProcessor` that batches ended spans and pushes them to the configured `SpanExporter`. It is recommended to use this `SpanProcessor` for better performance and optimization. + +## Viewing your traces + +Visit the google cloud trace UI at https://console.cloud.google.com/traces/list?project=your-gcp-project-id + + +## Authentication + +The Stackdriver Trace exporter supports authentication using service accounts. These can either be defined in a keyfile (usually called `service_account_key.json` or similar), or by the environment. If your application runs in a GCP environment, such as Compute Engine, you don't need to provide any application credentials. The client library will find the credentials by itself. For more information, go to . + +### Service account key + +If you are not running in a GCP environment, you will need to give the service account credentials to the exporter. + +```js +const { StackdriverTraceExporter } = require('@opentelemetry/exporter-stackdriver-trace'); + +const exporter = new StackdriverTraceExporter({ + /** option 1. provide a service account key json */ + keyFile: './service_account_key.json', + keyFileName: './service_account_key.json', + + /** option 2. provide credentials directly */ + credentials: { + client_email: string, + private_key: string, + }, +}); +``` + +## Useful links +- For more information on OpenTelemetry, visit: +- For more about OpenTelemetry JavaScript: +- Learn more about Stackdriver Trace at https://cloud.google.com/trace +- For help or feedback on this project, join us on [gitter][gitter-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[gitter-image]: https://badges.gitter.im/open-telemetry/opentelemetry-js.svg +[gitter-url]: https://gitter.im/open-telemetry/opentelemetry-node?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/master/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[dependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/status.svg?path=packages/opentelemetry-exporter-stackdriver-trace +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-stackdriver-trace +[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-exporter-stackdriver-trace +[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-stackdriver-trace&type=dev +[npm-url]: https://www.npmjs.com/package/@opentelemetry/exporter-stackdriver-trace +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fexporter-stackdriver-trace.svg diff --git a/packages/opentelemetry-exporter-stackdriver-trace/package.json b/packages/opentelemetry-exporter-stackdriver-trace/package.json new file mode 100644 index 0000000000..a6d0e2b5d9 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/package.json @@ -0,0 +1,69 @@ +{ + "name": "@opentelemetry/exporter-stackdriver-trace", + "version": "0.3.1", + "description": "OpenTelemetry StackDriver Trace Exporter allows the user to send collected traces to StackDriver Trace.", + "main": "build/src/index.js", + "types": "build/src/index.d.ts", + "repository": "open-telemetry/opentelemetry-js", + "scripts": { + "check": "gts check", + "clean": "rimraf build/*", + "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../", + "version:update": "node ../../scripts/version-update.js", + "compile": "npm run version:update && tsc -p .", + "fix": "gts fix", + "precompile": "tsc --version", + "prepare": "npm run compile", + "tdd": "yarn test -- --watch-extensions ts --watch", + "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts'" + }, + "keywords": [ + "opentelemetry", + "nodejs", + "tracing", + "profiling", + "stackdriver", + "stackdriver-trace" + ], + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + }, + "files": [ + "build/src/**/*.js", + "build/src/**/*.d.ts", + "doc", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@types/mocha": "^5.2.7", + "@types/nock": "^11.1.0", + "@types/node": "^12.6.9", + "@types/sinon": "^7.5.1", + "codecov": "^3.6.1", + "gts": "^1.1.0", + "mocha": "^6.2.0", + "nock": "^11.7.0", + "nyc": "^14.1.1", + "rimraf": "^3.0.0", + "sinon": "^8.0.1", + "ts-mocha": "^6.0.0", + "ts-node": "^8.3.0", + "tslint-consistent-codestyle": "^1.15.1", + "tslint-microsoft-contrib": "^6.2.0", + "typescript": "3.7.2" + }, + "dependencies": { + "@opentelemetry/base": "^0.3.1", + "@opentelemetry/core": "^0.3.1", + "@opentelemetry/tracing": "^0.3.1", + "@opentelemetry/types": "^0.3.1", + "google-auth-library": "^5.7.0", + "googleapis": "^46.0.0" + } +} diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/external-types.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/external-types.ts new file mode 100644 index 0000000000..7e92251f80 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/src/external-types.ts @@ -0,0 +1,51 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Logger } from '@opentelemetry/types'; + +export interface StackdriverExporterOptions { + /** + * Google Cloud Platform project ID where your traces will be stored. + * This is optional and will be inferred from your authentication + * credentials or from the GCP environment when not specified. + */ + projectId?: string; + /** + * Object implementing the logger interface + */ + logger?: Logger; + /** + * Path to a .json, .pem, or .p12 key file. This is optional and + * authentication keys will be inferred from the environment if you + * are running on GCP. + */ + keyFilename?: string; + /** + * Path to a .json, .pem, or .p12 key file. This is optional and + * authentication keys will be inferred from the environment if you + * are running on GCP. + */ + keyFile?: string; + /** + * Object containing client_email and private_key properties + */ + credentials?: Credentials; +} + +export interface Credentials { + client_email?: string; + private_key?: string; +} diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/index.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/index.ts new file mode 100644 index 0000000000..2563b8b901 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/src/index.ts @@ -0,0 +1,17 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './external-types'; +export * from './trace'; diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/trace.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/trace.ts new file mode 100644 index 0000000000..a49c688041 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/src/trace.ts @@ -0,0 +1,145 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExportResult } from '@opentelemetry/base'; +import { NoopLogger } from '@opentelemetry/core'; +import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; +import { Logger } from '@opentelemetry/types'; +import { GoogleAuth } from 'google-auth-library'; +import { google } from 'googleapis'; +import { StackdriverExporterOptions } from './external-types'; +import { getReadableSpanTransformer } from './transform'; +import { Span, SpansWithCredentials } from './types'; + +const OT_REQUEST_HEADER = 'x-opentelemetry-outgoing-request'; +google.options({ headers: { [OT_REQUEST_HEADER]: 1 } }); + +/** + * Format and sends span information to StackDriver Trace. + */ +export class StackdriverTraceExporter implements SpanExporter { + private _projectId: string | void | Promise; + private readonly _logger: Logger; + private readonly _auth: GoogleAuth; + + private static readonly _cloudTrace = google.cloudtrace('v2'); + + constructor(options: StackdriverExporterOptions) { + this._logger = options.logger || new NoopLogger(); + + this._auth = new GoogleAuth({ + credentials: options.credentials, + keyFile: options.keyFile, + keyFilename: options.keyFilename, + projectId: options.projectId, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }); + + // Start this async process as early as possible. It will be + // awaited on the first export because constructors are synchronous + this._projectId = this._auth.getProjectId().catch(err => { + this._logger.error(err); + }); + } + + /** + * Publishes a list of spans to Stackdriver. + * @param spans The list of spans to transmit to Stackdriver + */ + async export( + spans: ReadableSpan[], + resultCallback: (result: ExportResult) => void + ): Promise { + if (this._projectId instanceof Promise) { + this._projectId = await this._projectId; + } + + if (!this._projectId) { + return resultCallback(ExportResult.FAILED_NOT_RETRYABLE); + } + + this._logger.debug('StackDriver Trace export'); + const authorizedSpans = await this._authorize( + spans.map(getReadableSpanTransformer(this._projectId)) + ); + + if (!authorizedSpans) { + return resultCallback(ExportResult.FAILED_NOT_RETRYABLE); + } + this._logger.debug('StackDriver Trace got span authorization'); + + try { + await this._batchWriteSpans(authorizedSpans); + resultCallback(ExportResult.SUCCESS); + } catch (err) { + this._logger.error(`Stackdriver Trace failed to export ${err}`); + resultCallback(ExportResult.FAILED_RETRYABLE); + } + } + + shutdown(): void {} + + /** + * Sends new spans to new or existing traces in the Stackdriver format to the + * service. + * @param spans + */ + private _batchWriteSpans(spans: SpansWithCredentials) { + this._logger.debug('StackDriver Trace batch writing traces'); + + return new Promise((resolve, reject) => { + // @todo Consider to use gRPC call (BatchWriteSpansRequest) for sending + // data to backend : + // https://cloud.google.com/trace/docs/reference/v2/rpc/google.devtools. + // cloudtrace.v2#google.devtools.cloudtrace.v2.TraceService + StackdriverTraceExporter._cloudTrace.projects.traces.batchWrite( + spans, + (err: Error | null) => { + if (err) { + err.message = `batchWriteSpans error: ${err.message}`; + this._logger.error(err.message); + reject(err); + } else { + const successMsg = 'batchWriteSpans successfully'; + this._logger.debug(successMsg); + resolve(successMsg); + } + } + ); + }); + } + + /** + * Gets the Google Application Credentials from the environment variables, + * authenticates the client and calls a method to send the spans data. + * @param stackdriverSpans The spans to export + */ + private async _authorize( + spans: Span[] + ): Promise { + try { + return { + name: `projects/${this._projectId}`, + resource: { spans }, + auth: await this._auth.getClient(), + }; + } catch (err) { + err.message = `authorize error: ${err.message}`; + this._logger.error(err.message); + return null; + } + } +} diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/transform.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/transform.ts new file mode 100644 index 0000000000..ae17b053fd --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/src/transform.ts @@ -0,0 +1,130 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + hrTimeToTimeStamp, + VERSION as CORE_VERSION, +} from '@opentelemetry/core'; +import { ReadableSpan } from '@opentelemetry/tracing'; +import * as ot from '@opentelemetry/types'; +import { + AttributeMap, + Attributes, + AttributeValue, + Link, + LinkType, + Span, + TruncatableString, +} from './types'; +import { VERSION } from './version'; + +const AGENT_LABEL_KEY = 'g.co/agent'; +const AGENT_LABEL_VALUE = `opentelemetry-js [${CORE_VERSION}]; stackdriver-trace-exporter [${VERSION}]`; + +export function getReadableSpanTransformer( + projectId: string +): (span: ReadableSpan) => Span { + return span => { + const attributes = transformAttributes(span.attributes, { + project_id: projectId, + [AGENT_LABEL_KEY]: AGENT_LABEL_VALUE, + }); + + const out: Span = { + attributes, + displayName: stringToTruncatableString(span.name), + links: { + link: span.links.map(transformLink), + }, + endTime: hrTimeToTimeStamp(span.endTime), + startTime: hrTimeToTimeStamp(span.startTime), + name: `projects/${projectId}/traces/${span.spanContext.traceId}/spans/${span.spanContext.spanId}`, + spanId: span.spanContext.spanId, + sameProcessAsParentSpan: !span.spanContext.isRemote, + status: span.status, + timeEvents: { + timeEvent: span.events.map(e => ({ + time: hrTimeToTimeStamp(e.time), + annotation: { + attributes: transformAttributes(e.attributes), + description: stringToTruncatableString(e.name), + }, + })), + }, + }; + + if (span.parentSpanId) { + out.parentSpanId = span.parentSpanId; + } + + return out; + }; +} + +function transformLink(link: ot.Link): Link { + return { + attributes: transformAttributes(link.attributes), + spanId: link.spanContext.spanId, + traceId: link.spanContext.traceId, + type: LinkType.UNSPECIFIED, + }; +} + +function transformAttributes( + requestAttributes: ot.Attributes = {}, + serviceAttributes: ot.Attributes = {} +): Attributes { + const attributes = Object.assign({}, requestAttributes, serviceAttributes); + const attributeMap = transformAttributeValues(attributes); + return { + attributeMap: attributeMap, + // @todo get dropped attribute count from sdk ReadableSpan + droppedAttributesCount: + Object.keys(attributes).length - Object.keys(attributeMap).length, + }; +} + +function transformAttributeValues(attributes: ot.Attributes): AttributeMap { + const out: AttributeMap = {}; + for (const [key, value] of Object.entries(attributes)) { + switch (typeof value) { + case 'number': + case 'boolean': + case 'string': + out[key] = valueToAttributeValue(value); + break; + } + } + return out; +} + +function stringToTruncatableString(value: string): TruncatableString { + return { value }; +} + +function valueToAttributeValue( + value: string | number | boolean +): AttributeValue { + switch (typeof value) { + case 'number': + // TODO: Consider to change to doubleValue when available in V2 API. + return { intValue: String(Math.round(value)) }; + case 'boolean': + return { boolValue: value }; + case 'string': + return { stringValue: stringToTruncatableString(value) }; + } +} diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/types.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/types.ts new file mode 100644 index 0000000000..91dda1bd05 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/src/types.ts @@ -0,0 +1,142 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Compute, JWT, OAuth2Client } from 'google-auth-library'; + +export interface Span { + name?: string; + spanId?: string; + parentSpanId?: string; + displayName?: TruncatableString; + startTime?: string; + endTime?: string; + attributes?: Attributes; + // This property is currently unused. keeping it here as it is part + // of the stack driver trace types and may be used in the future + stackTrace?: StackTrace; + timeEvents?: TimeEvents; + links?: Links; + status?: Status; + sameProcessAsParentSpan?: boolean; + childSpanCount?: number; +} + +export interface AttributeMap { + [key: string]: AttributeValue; +} + +export interface Attributes { + attributeMap?: AttributeMap; + droppedAttributesCount?: number; +} + +export interface AttributeValue { + boolValue?: boolean; + intValue?: string; + stringValue?: TruncatableString; +} + +export interface TruncatableString { + value?: string; + truncatedByteCount?: number; +} + +export interface Links { + droppedLinksCount?: number; + link?: Link[]; +} + +export interface Link { + attributes?: Attributes; + spanId?: string; + traceId?: string; + type?: LinkType; +} + +export interface StackTrace { + stackFrames?: StackFrames; + stackTraceHashId?: string; +} + +export interface StackFrames { + droppedFramesCount?: number; + frame?: StackFrame[]; +} + +export interface StackFrame { + columnNumber?: string; + fileName?: TruncatableString; + functionName?: TruncatableString; + lineNumber?: string; + loadModule?: Module; + originalFunctionName?: TruncatableString; + sourceVersion?: TruncatableString; +} + +export interface Module { + buildId?: TruncatableString; + module?: TruncatableString; +} + +export interface Status { + /** gRPC status code */ + code?: number; + message?: string; +} + +export interface TimeEvents { + droppedAnnotationsCount?: number; + droppedMessageEventsCount?: number; + timeEvent?: TimeEvent[]; +} + +export interface TimeEvent { + annotation?: Annotation; + time?: string; + // This property is currently unused. keeping it here as it is part + // of the stack driver trace types and may be used in the future + messageEvent?: MessageEvent; +} + +export interface Annotation { + attributes?: Attributes; + description?: TruncatableString; +} + +export interface MessageEvent { + id?: string; + type?: Type; + compressedSizeBytes?: string; + uncompressedSizeBytes?: string; +} + +export enum Type { + TYPE_UNSPECIFIED = 0, + SENT = 1, + RECEIVED = 2, +} + +export enum LinkType { + UNSPECIFIED = 0, + CHILD_LINKED_SPAN = 1, + PARENT_LINKED_SPAN = 2, +} + +export interface SpansWithCredentials { + name: string; + resource: { spans: {} }; + auth: JWT | OAuth2Client | Compute; +} diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts new file mode 100644 index 0000000000..60ca3ab548 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts @@ -0,0 +1,18 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// this is autogenerated file, see scripts/version-update.js +export const VERSION = '0.3.1'; diff --git a/packages/opentelemetry-exporter-stackdriver-trace/test/exporter.test.ts b/packages/opentelemetry-exporter-stackdriver-trace/test/exporter.test.ts new file mode 100644 index 0000000000..cfacb9dc02 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/test/exporter.test.ts @@ -0,0 +1,227 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExportResult } from '@opentelemetry/base'; +import { ConsoleLogger, LogLevel } from '@opentelemetry/core'; +import { ReadableSpan } from '@opentelemetry/tracing'; +import * as types from '@opentelemetry/types'; +import * as assert from 'assert'; +import * as nock from 'nock'; +import * as sinon from 'sinon'; +import { StackdriverTraceExporter } from '../src'; + +describe('Stackdriver Trace Exporter', () => { + beforeEach(() => { + process.env.GCLOUD_PROJECT = 'not-real'; + nock.disableNetConnect(); + }); + + describe('constructor', () => { + it('should construct an exporter', async () => { + const exporter = new StackdriverTraceExporter({ + credentials: { + client_email: 'noreply@fake.example.com', + private_key: 'this is a key', + }, + }); + + assert(exporter); + return (exporter['_projectId'] as Promise).then(id => { + assert.deepStrictEqual(id, 'not-real'); + }); + }); + }); + + describe('export', () => { + let exporter: StackdriverTraceExporter; + let logger: ConsoleLogger; + let batchWrite: sinon.SinonSpy<[any, any], any>; + let debug: sinon.SinonSpy; + let info: sinon.SinonSpy; + let warn: sinon.SinonSpy; + let error: sinon.SinonSpy; + let getClientShouldFail: boolean; + let batchWriteShouldFail: boolean; + + beforeEach(() => { + getClientShouldFail = false; + batchWriteShouldFail = false; + logger = new ConsoleLogger(LogLevel.ERROR); + exporter = new StackdriverTraceExporter({ + logger, + }); + + batchWrite = sinon.spy( + (spans: any, callback: (err: Error | null) => void): any => { + if (batchWriteShouldFail) { + callback(new Error('fail')); + } else { + callback(null); + } + } + ); + + sinon.replace( + StackdriverTraceExporter['_cloudTrace'].projects.traces, + 'batchWrite', + batchWrite as any + ); + + sinon.replace(exporter['_auth'], 'getClient', () => { + if (getClientShouldFail) { + throw new Error('fail'); + } + return {} as any; + }); + + debug = sinon.spy(); + info = sinon.spy(); + warn = sinon.spy(); + error = sinon.spy(); + sinon.replace(logger, 'debug', debug); + sinon.replace(logger, 'info', info); + sinon.replace(logger, 'warn', warn); + sinon.replace(logger, 'error', error); + }); + + afterEach(() => { + nock.restore(); + sinon.restore(); + }); + + it('should export spans', async () => { + const readableSpan: ReadableSpan = { + attributes: {}, + duration: [32, 800000000], + startTime: [1566156729, 709], + endTime: [1566156731, 709], + events: [], + kind: types.SpanKind.CLIENT, + links: [], + name: 'my-span', + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + isRemote: true, + }, + status: { code: types.CanonicalCode.OK }, + }; + + const result = await new Promise((resolve, reject) => { + exporter.export([readableSpan], result => { + resolve(result); + }); + }); + + assert.deepStrictEqual( + batchWrite.getCall(0).args[0].resource.spans[0].displayName.value, + 'my-span' + ); + + assert.deepStrictEqual(result, ExportResult.SUCCESS); + }); + + it('should return not retryable if authorization fails', async () => { + const readableSpan: ReadableSpan = { + attributes: {}, + duration: [32, 800000000], + startTime: [1566156729, 709], + endTime: [1566156731, 709], + events: [], + kind: types.SpanKind.CLIENT, + links: [], + name: 'my-span', + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + isRemote: true, + }, + status: { code: types.CanonicalCode.OK }, + }; + + getClientShouldFail = true; + + const result = await new Promise((resolve, reject) => { + exporter.export([readableSpan], result => { + resolve(result); + }); + }); + + assert(batchWrite.notCalled); + assert(error.getCall(0).args[0].match(/authorize error: fail/)); + assert.deepStrictEqual(result, ExportResult.FAILED_NOT_RETRYABLE); + }); + + it('should return retryable if span writing fails', async () => { + const readableSpan: ReadableSpan = { + attributes: {}, + duration: [32, 800000000], + startTime: [1566156729, 709], + endTime: [1566156731, 709], + events: [], + kind: types.SpanKind.CLIENT, + links: [], + name: 'my-span', + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + isRemote: true, + }, + status: { code: types.CanonicalCode.OK }, + }; + + batchWriteShouldFail = true; + + const result = await new Promise((resolve, reject) => { + exporter.export([readableSpan], result => { + resolve(result); + }); + }); + + assert.deepStrictEqual(result, ExportResult.FAILED_RETRYABLE); + }); + + it('should return not retryable if project id missing', async () => { + const readableSpan: ReadableSpan = { + attributes: {}, + duration: [32, 800000000], + startTime: [1566156729, 709], + endTime: [1566156731, 709], + events: [], + kind: types.SpanKind.CLIENT, + links: [], + name: 'my-span', + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + isRemote: true, + }, + status: { code: types.CanonicalCode.OK }, + }; + + await exporter['_projectId']; + exporter['_projectId'] = undefined; + + const result = await new Promise((resolve, reject) => { + exporter.export([readableSpan], result => { + resolve(result); + }); + }); + + assert.deepStrictEqual(result, ExportResult.FAILED_NOT_RETRYABLE); + }); + }); +}); diff --git a/packages/opentelemetry-exporter-stackdriver-trace/test/transform.test.ts b/packages/opentelemetry-exporter-stackdriver-trace/test/transform.test.ts new file mode 100644 index 0000000000..1a35b76d8c --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/test/transform.test.ts @@ -0,0 +1,255 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { VERSION as CORE_VERSION } from '@opentelemetry/core'; +import { ReadableSpan } from '@opentelemetry/tracing'; +import * as types from '@opentelemetry/types'; +import * as assert from 'assert'; +import { getReadableSpanTransformer } from '../src/transform'; +import { LinkType, Span } from '../src/types'; +import { VERSION } from '../src/version'; + +describe('transform', () => { + let readableSpan: ReadableSpan; + let transformer: (readableSpan: ReadableSpan) => Span; + let spanContext: types.SpanContext; + + beforeEach(() => { + spanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + isRemote: true, + }; + + transformer = getReadableSpanTransformer('project-id'); + + readableSpan = { + attributes: {}, + duration: [32, 800000000], + startTime: [1566156729, 709], + endTime: [1566156731, 709], + events: [], + kind: types.SpanKind.CLIENT, + links: [], + name: 'my-span', + spanContext, + status: { code: types.CanonicalCode.OK }, + }; + }); + + it('should transform spans', () => { + const result = transformer(readableSpan); + + assert.deepStrictEqual(result, { + attributes: { + attributeMap: { + project_id: { stringValue: { value: 'project-id' } }, + 'g.co/agent': { + stringValue: { + value: `opentelemetry-js [${CORE_VERSION}]; stackdriver-trace-exporter [${VERSION}]`, + }, + }, + }, + droppedAttributesCount: 0, + }, + displayName: { value: 'my-span' }, + links: { link: [] }, + endTime: '2019-08-18T19:32:11.000000709Z', + startTime: '2019-08-18T19:32:09.000000709Z', + name: + 'projects/project-id/traces/d4cda95b652f4a1592b449d5929fda1b/spans/6e0c63257de34c92', + spanId: '6e0c63257de34c92', + status: { code: 0 }, + timeEvents: { timeEvent: [] }, + sameProcessAsParentSpan: false, + }); + }); + + it('should transform spans with parent', () => { + (readableSpan as any).parentSpanId = '3e0c63257de34c92'; + const result = transformer(readableSpan); + assert.deepStrictEqual(result.parentSpanId, '3e0c63257de34c92'); + }); + + it('should transform spans without parent', () => { + const result = transformer(readableSpan); + assert.deepStrictEqual(result.parentSpanId, undefined); + }); + + it('should transform remote spans', () => { + const remote = transformer(readableSpan); + assert.deepStrictEqual(remote.sameProcessAsParentSpan, false); + }); + + it('should transform local spans', () => { + readableSpan.spanContext.isRemote = false; + const local = transformer(readableSpan); + assert.deepStrictEqual(local.sameProcessAsParentSpan, true); + }); + + it('should transform attributes', () => { + readableSpan.attributes.testBool = true; + readableSpan.attributes.testInt = 3; + readableSpan.attributes.testString = 'str'; + + const result = transformer(readableSpan); + + assert.deepStrictEqual(result.attributes!.attributeMap!.testBool, { + boolValue: true, + }); + assert.deepStrictEqual(result.attributes!.attributeMap!.testInt, { + intValue: '3', + }); + assert.deepStrictEqual(result.attributes!.attributeMap!.testString, { + stringValue: { value: 'str' }, + }); + assert.deepStrictEqual(result.attributes!.droppedAttributesCount, 0); + }); + + it('should drop unknown attribute types', () => { + readableSpan.attributes.testUnknownType = { message: 'dropped' }; + const result = transformer(readableSpan); + assert.deepStrictEqual(result.attributes!.droppedAttributesCount, 1); + assert.deepStrictEqual( + Object.keys(result.attributes!.attributeMap!).length, + 2 + ); + }); + + it('should transform links', () => { + readableSpan.links.push({ + spanContext: { + traceId: 'a4cda95b652f4a1592b449d5929fda1b', + spanId: '3e0c63257de34c92', + isRemote: true, + traceFlags: types.TraceFlags.SAMPLED, + }, + }); + + const result = transformer(readableSpan); + + assert.deepStrictEqual(result.links, { + link: [ + { + attributes: { + attributeMap: {}, + droppedAttributesCount: 0, + }, + traceId: 'a4cda95b652f4a1592b449d5929fda1b', + spanId: '3e0c63257de34c92', + type: LinkType.UNSPECIFIED, + }, + ], + }); + }); + + it('should transform links with attributes', () => { + readableSpan.links.push({ + spanContext: { + traceId: 'a4cda95b652f4a1592b449d5929fda1b', + spanId: '3e0c63257de34c92', + isRemote: true, + traceFlags: types.TraceFlags.SAMPLED, + }, + attributes: { + testAttr: 'value', + droppedAttr: {}, + }, + }); + + const result = transformer(readableSpan); + + assert.deepStrictEqual(result.links, { + link: [ + { + attributes: { + attributeMap: { + testAttr: { + stringValue: { + value: 'value', + }, + }, + }, + droppedAttributesCount: 1, + }, + traceId: 'a4cda95b652f4a1592b449d5929fda1b', + spanId: '3e0c63257de34c92', + type: LinkType.UNSPECIFIED, + }, + ], + }); + }); + + it('should transform events', () => { + readableSpan.events.push({ + name: 'something happened', + time: [1566156729, 809], + }); + + const result = transformer(readableSpan); + + assert.deepStrictEqual(result.timeEvents, { + timeEvent: [ + { + annotation: { + attributes: { + attributeMap: {}, + droppedAttributesCount: 0, + }, + description: { + value: 'something happened', + }, + }, + time: '2019-08-18T19:32:09.000000809Z', + }, + ], + }); + }); + + it('should transform events with attributes', () => { + readableSpan.events.push({ + name: 'something happened', + attributes: { + error: true, + dropped: {}, + }, + time: [1566156729, 809], + }); + + const result = transformer(readableSpan); + + assert.deepStrictEqual(result.timeEvents, { + timeEvent: [ + { + annotation: { + attributes: { + attributeMap: { + error: { + boolValue: true, + }, + }, + droppedAttributesCount: 1, + }, + description: { + value: 'something happened', + }, + }, + time: '2019-08-18T19:32:09.000000809Z', + }, + ], + }); + }); +}); diff --git a/packages/opentelemetry-exporter-stackdriver-trace/tsconfig.json b/packages/opentelemetry-exporter-stackdriver-trace/tsconfig.json new file mode 100644 index 0000000000..a2042cd68b --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base", + "compilerOptions": { + "rootDir": ".", + "outDir": "build" + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/packages/opentelemetry-exporter-stackdriver-trace/tslint.json b/packages/opentelemetry-exporter-stackdriver-trace/tslint.json new file mode 100644 index 0000000000..0710b135d0 --- /dev/null +++ b/packages/opentelemetry-exporter-stackdriver-trace/tslint.json @@ -0,0 +1,4 @@ +{ + "rulesDirectory": ["node_modules/tslint-microsoft-contrib"], + "extends": ["../../tslint.base.js", "./node_modules/tslint-consistent-codestyle"] +} From 25d1028ab38d5dbadab63d74b77bce197a14e7f4 Mon Sep 17 00:00:00 2001 From: Xiao Date: Fri, 17 Jan 2020 12:54:11 -0800 Subject: [PATCH 04/21] chore: fix pg-pool test env (#706) --- .../opentelemetry-plugin-pg-pool/package.json | 1 + .../opentelemetry-plugin-pg-pool/test/pg-pool.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json index 2f0050a462..0e11029dcd 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json @@ -54,6 +54,7 @@ "@types/pg-pool": "^2.0.1", "@types/shimmer": "^1.0.1", "codecov": "^3.6.1", + "cross-env": "^6.0.0", "gts": "^1.1.0", "mocha": "^6.2.0", "nyc": "^14.1.1", diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts index 69edba1677..6dcc9caf1e 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts @@ -94,8 +94,8 @@ describe('pg-pool@2.x', () => { let pool: pgPool; const registry = new NodeTracerRegistry(); const logger = new NoopLogger(); - const testPostgres = process.env.TEST_POSTGRES; // For CI: assumes local postgres db is already available - const testPostgresLocally = process.env.TEST_POSTGRES_LOCAL; // For local: spins up local postgres db via docker + const testPostgres = process.env.RUN_POSTGRES_TESTS; // For CI: assumes local postgres db is already available + const testPostgresLocally = process.env.RUN_POSTGRES_TESTS_LOCAL; // For local: spins up local postgres db via docker const shouldTest = testPostgres || testPostgresLocally; // Skips these tests if false (default) before(function(done) { @@ -192,10 +192,10 @@ describe('pg-pool@2.x', () => { if (!client) { throw new Error('No client received'); } - release(); assert.ok(client); runCallbackTest(parentSpan, pgPoolattributes, events, okStatus, 1, 0); client.query('SELECT NOW()', (err, ret) => { + release(); if (err) { return done(err); } From 021bbbbee9356ff56c4b703633e0f67d16b3e425 Mon Sep 17 00:00:00 2001 From: Olivier Albertini Date: Fri, 17 Jan 2020 17:00:35 -0500 Subject: [PATCH 05/21] fix(getting-started): remove extra comma (#704) add ts-node package closes #703 Signed-off-by: Olivier Albertini --- getting-started/ts-example/package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/getting-started/ts-example/package.json b/getting-started/ts-example/package.json index 00f1e003bb..20c0da256b 100644 --- a/getting-started/ts-example/package.json +++ b/getting-started/ts-example/package.json @@ -5,10 +5,13 @@ "main": "app.ts", "scripts": { "start": "ts-node app.ts", - "start-tracing": "ts-node -r ./tracing.ts app.ts", + "start-tracing": "ts-node -r ./tracing.ts app.ts" }, "author": "OpenTelemetry Authors", "license": "Apache-2.0", + "devDependencies": { + "ts-node": "^8.6.2" + }, "dependencies": { "@opentelemetry/core": "^0.3.2", "@opentelemetry/exporter-prometheus": "^0.3.2", @@ -18,7 +21,7 @@ "@opentelemetry/plugin-http": "^0.3.2", "@opentelemetry/tracing": "^0.3.2", "@opentelemetry/types": "^0.3.2", - "axios": "^0.19.0", + "axios": "^0.19.1", "express": "^4.17.1" } } From 6de9afb6778847458d5381d6eefab2f3d2d7e8bd Mon Sep 17 00:00:00 2001 From: Uladzislau Kiva Date: Mon, 20 Jan 2020 22:13:56 +0300 Subject: [PATCH 06/21] feat: add jaeger http trace format (#696) (#701) * feat: add jaeger http trace format (#696) * feat: add jaeger http trace format (#696) * feat: add jaeger http trace format (#696) * feat: add jaeger http trace format (#696) * feat: add jaeger http trace format (#696) * feat: add jaeger http trace format (#696) * fix: we should set sampled\unsampled via flag * fix: we should set sampled\unsampled via flag * fix: flags should be converted to hex, not decimal * feat: create new package for propagation jaeger * fix: remove unused dependencies, correct readme header, moved out jaeger from core index.ts * fix: added jaeger keyword * fix: remove comma * docs: replace NodeTracer with NodeTracerRegistry * fix: added missing jaeger keyword to exporter-jaeger * fix: remove test for browser * fix: remove yarn for browser * fix: use same naming style as other packages * feat: added index.ts and version.ts, revert test for browser * fix: tests added index-webpack.ts * test: add test with span generated by jaeger client * fix: apply review changes * fix: move out from sub dirs * docs: use common language for docs * fix: test script fix Co-authored-by: Uladzislau Kiva --- .../package.json | 3 +- .../.npmignore | 4 + .../opentelemetry-propagator-jaeger/LICENSE | 201 ++++++++++++++++++ .../opentelemetry-propagator-jaeger/README.md | 58 +++++ .../jaeger-tracing.png | Bin 0 -> 61523 bytes .../karma.conf.js | 24 +++ .../package.json | 75 +++++++ .../src/JaegerHttpTraceFormat.ts | 98 +++++++++ .../src/index.ts | 17 ++ .../src/version.ts | 18 ++ .../test/JaegerHttpTraceFormat.test.ts | 119 +++++++++++ .../test/index-webpack.ts | 23 ++ .../tsconfig-release.json | 7 + .../tsconfig.json | 11 + .../tslint.json | 4 + 15 files changed, 661 insertions(+), 1 deletion(-) create mode 100644 packages/opentelemetry-propagator-jaeger/.npmignore create mode 100644 packages/opentelemetry-propagator-jaeger/LICENSE create mode 100644 packages/opentelemetry-propagator-jaeger/README.md create mode 100644 packages/opentelemetry-propagator-jaeger/jaeger-tracing.png create mode 100644 packages/opentelemetry-propagator-jaeger/karma.conf.js create mode 100644 packages/opentelemetry-propagator-jaeger/package.json create mode 100644 packages/opentelemetry-propagator-jaeger/src/JaegerHttpTraceFormat.ts create mode 100644 packages/opentelemetry-propagator-jaeger/src/index.ts create mode 100644 packages/opentelemetry-propagator-jaeger/src/version.ts create mode 100644 packages/opentelemetry-propagator-jaeger/test/JaegerHttpTraceFormat.test.ts create mode 100644 packages/opentelemetry-propagator-jaeger/test/index-webpack.ts create mode 100644 packages/opentelemetry-propagator-jaeger/tsconfig-release.json create mode 100644 packages/opentelemetry-propagator-jaeger/tsconfig.json create mode 100644 packages/opentelemetry-propagator-jaeger/tslint.json diff --git a/packages/opentelemetry-exporter-jaeger/package.json b/packages/opentelemetry-exporter-jaeger/package.json index 0a024da993..6779c9a5cb 100644 --- a/packages/opentelemetry-exporter-jaeger/package.json +++ b/packages/opentelemetry-exporter-jaeger/package.json @@ -21,7 +21,8 @@ "opentelemetry", "nodejs", "tracing", - "profiling" + "profiling", + "jaeger" ], "author": "OpenTelemetry Authors", "license": "Apache-2.0", diff --git a/packages/opentelemetry-propagator-jaeger/.npmignore b/packages/opentelemetry-propagator-jaeger/.npmignore new file mode 100644 index 0000000000..9505ba9450 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/.npmignore @@ -0,0 +1,4 @@ +/bin +/coverage +/doc +/test diff --git a/packages/opentelemetry-propagator-jaeger/LICENSE b/packages/opentelemetry-propagator-jaeger/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/opentelemetry-propagator-jaeger/README.md b/packages/opentelemetry-propagator-jaeger/README.md new file mode 100644 index 0000000000..5c6771c3a3 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/README.md @@ -0,0 +1,58 @@ +# OpenTelemetry Propagator Jaeger +[![Gitter chat][gitter-image]][gitter-url] +[![NPM Published Version][npm-img]][npm-url] +[![dependencies][dependencies-image]][dependencies-url] +[![devDependencies][devDependencies-image]][devDependencies-url] +[![Apache License][license-image]][license-image] + +OpenTelemetry Jaeger propagator provides HTTP header propagation for systems that are using Jaeger HTTP header format. + +Format: +{trace-id}:{span-id}:{parent-span-id}:{flags} + +* {trace-id} + * 64-bit or 128-bit random number in base16 format. + * Can be variable length, shorter values are 0-padded on the left. + * Value of 0 is invalid. + +* {span-id} + * 64-bit random number in base16 format. + +* {parent-span-id} + * Set to 0 because this field is deprecated. +* {flags} + * One byte bitmap, as two hex digits. + +Example of usage: +```javascript +const { NodeTracerRegistry } = require('@opentelemetry/node'); +const { JaegerHttpTraceFormat } = require('@opentelemetry/propagator-jaeger'); + +const registry = new NodeTracerRegistry({ + httpTextFormat: new JaegerHttpTraceFormat() +}); +``` + +## Trace on Jaeger UI + +![example image](jaeger-tracing.png) + +## Useful links +- For more information on OpenTelemetry, visit: +- For more about OpenTelemetry JavaScript: +- For help or feedback on this project, join us on [gitter][gitter-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[gitter-image]: https://badges.gitter.im/open-telemetry/opentelemetry-js.svg +[gitter-url]: https://gitter.im/open-telemetry/opentelemetry-node?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/master/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[dependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/status.svg?path=packages/opentelemetry-propagator-jaeger +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-propagator-jaeger +[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-propagator-jaeger +[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-propagator-jaeger&type=dev +[npm-url]: https://www.npmjs.com/package/@opentelemetry/propagator-jaeger +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fpropagator-jaeger.svg diff --git a/packages/opentelemetry-propagator-jaeger/jaeger-tracing.png b/packages/opentelemetry-propagator-jaeger/jaeger-tracing.png new file mode 100644 index 0000000000000000000000000000000000000000..0c8693a2b51f612c02414cfea96e472f5ec1eceb GIT binary patch literal 61523 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 ?Z9tFlXCaH+}q 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 { + config.set(Object.assign({}, karmaBaseConfig, { + webpack: karmaWebpackConfig + })) +}; diff --git a/packages/opentelemetry-propagator-jaeger/package.json b/packages/opentelemetry-propagator-jaeger/package.json new file mode 100644 index 0000000000..fd7d45f807 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/package.json @@ -0,0 +1,75 @@ +{ + "name": "@opentelemetry/propagator-jaeger", + "version": "0.3.2", + "description": "OpenTelemetry Jaeger propagator provides HTTP header propagation for systems that are using Jaeger HTTP header format.", + "main": "build/src/index.js", + "types": "build/src/index.d.ts", + "repository": "open-telemetry/opentelemetry-js", + "scripts": { + "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts' --exclude 'test/index-webpack.ts'", + "test:browser": "nyc karma start --single-run", + "tdd": "yarn tdd:node", + "tdd:node": "yarn test -- --watch-extensions ts --watch", + "tdd:browser": "karma start", + "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../", + "clean": "rimraf build/*", + "check": "gts check", + "precompile": "tsc --version", + "compile": "npm run version:update && tsc -p .", + "fix": "gts fix", + "prepare": "npm run compile", + "version:update": "node ../../scripts/version-update.js", + "watch": "tsc -w" + }, + "keywords": [ + "opentelemetry", + "nodejs", + "tracing", + "profiling", + "jaeger" + ], + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + }, + "files": [ + "build/src/**/*.js", + "build/src/**/*.d.ts", + "doc", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@types/mocha": "^5.2.5", + "@types/node": "^12.6.8", + "@types/sinon": "^7.0.13", + "@types/webpack-env": "1.13.9", + "codecov": "^3.6.1", + "gts": "^1.1.0", + "istanbul-instrumenter-loader": "^3.0.1", + "karma": "^4.4.1", + "karma-chrome-launcher": "^3.1.0", + "karma-coverage-istanbul-reporter": "^2.1.0", + "karma-mocha": "^1.3.0", + "karma-spec-reporter": "^0.0.32", + "karma-webpack": "^4.0.2", + "mocha": "^6.1.0", + "nyc": "^14.1.1", + "rimraf": "^3.0.0", + "sinon": "^7.5.0", + "ts-loader": "^6.0.4", + "ts-mocha": "^6.0.0", + "ts-node": "^8.6.2", + "tslint-consistent-codestyle": "^1.16.0", + "tslint-microsoft-contrib": "^6.2.0", + "typescript": "3.7.2", + "webpack": "^4.35.2" + }, + "dependencies": { + "@opentelemetry/types": "^0.3.2" + } +} diff --git a/packages/opentelemetry-propagator-jaeger/src/JaegerHttpTraceFormat.ts b/packages/opentelemetry-propagator-jaeger/src/JaegerHttpTraceFormat.ts new file mode 100644 index 0000000000..811c6d9d41 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/src/JaegerHttpTraceFormat.ts @@ -0,0 +1,98 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SpanContext, HttpTextFormat, TraceFlags } from '@opentelemetry/types'; + +export const UBER_TRACE_ID_HEADER = 'uber-trace-id'; + +/** + * Propagates {@link SpanContext} through Trace Context format propagation. + * {trace-id}:{span-id}:{parent-span-id}:{flags} + * {trace-id} + * 64-bit or 128-bit random number in base16 format. + * Can be variable length, shorter values are 0-padded on the left. + * Value of 0 is invalid. + * {span-id} + * 64-bit random number in base16 format. + * {parent-span-id} + * Set to 0 because this field is deprecated. + * {flags} + * One byte bitmap, as two hex digits. + * Inspired by jaeger-client-node project. + */ +export class JaegerHttpTraceFormat implements HttpTextFormat { + private readonly _jaegerTraceHeader: string; + + /** + * @param {string} [customTraceHeader="uber-trace-id"] - HTTP header to inject\extract trace from. + **/ + constructor(customTraceHeader?: string) { + this._jaegerTraceHeader = customTraceHeader || UBER_TRACE_ID_HEADER; + } + + /** + * @param {SpanContext} spanContext - context from which we take information to inject in carrier. + * @param {string} format - unused. + * @param { [key: string]: unknown } carrier - a carrier to which span information will be injected. + **/ + inject( + spanContext: SpanContext, + format: string, + carrier: { [key: string]: unknown } + ) { + const traceFlags = `0${( + spanContext.traceFlags || TraceFlags.UNSAMPLED + ).toString(16)}`; + + carrier[ + this._jaegerTraceHeader + ] = `${spanContext.traceId}:${spanContext.spanId}:0:${traceFlags}`; + } + + /** + * @param {string} format - unused. + * @param { [key: string]: unknown } carrier - a carrier from which span context will be constructed. + * @return {SpanContext} - returns a span context extracted from carrier. + **/ + extract( + format: string, + carrier: { [key: string]: unknown } + ): SpanContext | null { + const uberTraceIdHeader = carrier[this._jaegerTraceHeader]; + if (!uberTraceIdHeader) return null; + const uberTraceId = Array.isArray(uberTraceIdHeader) + ? uberTraceIdHeader[0] + : uberTraceIdHeader; + + return deserializeSpanContext(uberTraceId); + } +} + +/** + * @param {string} serializedString - a serialized span context. + * @return {SpanContext} - returns a span context represented by the serializedString. + **/ +function deserializeSpanContext(serializedString: string): SpanContext | null { + let headers = serializedString.split(':'); + if (headers.length !== 4) { + return null; + } + const [traceId, spanId, , flags] = headers; + + const traceFlags = flags.match(/^[0-9a-f]{2}$/i) ? parseInt(flags) & 1 : 1; + + return { traceId, spanId, isRemote: true, traceFlags }; +} diff --git a/packages/opentelemetry-propagator-jaeger/src/index.ts b/packages/opentelemetry-propagator-jaeger/src/index.ts new file mode 100644 index 0000000000..620c254f23 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/src/index.ts @@ -0,0 +1,17 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './JaegerHttpTraceFormat'; diff --git a/packages/opentelemetry-propagator-jaeger/src/version.ts b/packages/opentelemetry-propagator-jaeger/src/version.ts new file mode 100644 index 0000000000..2efbb00dcb --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/src/version.ts @@ -0,0 +1,18 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// this is autogenerated file, see scripts/version-update.js +export const VERSION = '0.3.2'; diff --git a/packages/opentelemetry-propagator-jaeger/test/JaegerHttpTraceFormat.test.ts b/packages/opentelemetry-propagator-jaeger/test/JaegerHttpTraceFormat.test.ts new file mode 100644 index 0000000000..a81dc92ffa --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/test/JaegerHttpTraceFormat.test.ts @@ -0,0 +1,119 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import { + JaegerHttpTraceFormat, + UBER_TRACE_ID_HEADER, +} from '../src/JaegerHttpTraceFormat'; +import { SpanContext, TraceFlags } from '@opentelemetry/types'; + +describe('JaegerHttpTraceFormat', () => { + const jaegerHttpTraceFormat = new JaegerHttpTraceFormat(); + const customHeader = 'new-header'; + const customJaegerHttpTraceFormat = new JaegerHttpTraceFormat(customHeader); + let carrier: { [key: string]: unknown }; + + beforeEach(() => { + carrier = {}; + }); + + describe('.inject()', () => { + it('should set uber trace id header', () => { + const spanContext: SpanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + + jaegerHttpTraceFormat.inject(spanContext, '', carrier); + assert.deepStrictEqual( + carrier[UBER_TRACE_ID_HEADER], + 'd4cda95b652f4a1592b449d5929fda1b:6e0c63257de34c92:0:01' + ); + }); + + it('should use custom header if provided', () => { + const spanContext: SpanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + + customJaegerHttpTraceFormat.inject(spanContext, '', carrier); + assert.deepStrictEqual( + carrier[customHeader], + 'd4cda95b652f4a1592b449d5929fda1b:6e0c63257de34c92:0:01' + ); + }); + }); + + describe('.extract()', () => { + it('should extract context of a sampled span from carrier', () => { + carrier[UBER_TRACE_ID_HEADER] = + 'd4cda95b652f4a1592b449d5929fda1b:6e0c63257de34c92:0:01'; + const extractedSpanContext = jaegerHttpTraceFormat.extract('', carrier); + + assert.deepStrictEqual(extractedSpanContext, { + spanId: '6e0c63257de34c92', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + + it('should extract context of a sampled span from carrier with 1 bit flag', () => { + carrier[UBER_TRACE_ID_HEADER] = + '9c41e35aeb6d1272:45fd2a9709dadcf1:a13699e3fb724f40:1'; + const extractedSpanContext = jaegerHttpTraceFormat.extract('', carrier); + + assert.deepStrictEqual(extractedSpanContext, { + spanId: '45fd2a9709dadcf1', + traceId: '9c41e35aeb6d1272', + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + + it('should use custom header if provided', () => { + carrier[customHeader] = + 'd4cda95b652f4a1592b449d5929fda1b:6e0c63257de34c92:0:01'; + const extractedSpanContext = customJaegerHttpTraceFormat.extract( + '', + carrier + ); + + assert.deepStrictEqual(extractedSpanContext, { + spanId: '6e0c63257de34c92', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + + it('returns null if UBER_TRACE_ID_HEADER header is missing', () => { + assert.deepStrictEqual(jaegerHttpTraceFormat.extract('', carrier), null); + }); + + it('returns null if UBER_TRACE_ID_HEADER header is invalid', () => { + carrier[UBER_TRACE_ID_HEADER] = 'invalid!'; + assert.deepStrictEqual( + jaegerHttpTraceFormat.extract('HttpTraceContext', carrier), + null + ); + }); + }); +}); diff --git a/packages/opentelemetry-propagator-jaeger/test/index-webpack.ts b/packages/opentelemetry-propagator-jaeger/test/index-webpack.ts new file mode 100644 index 0000000000..2709346bf1 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/test/index-webpack.ts @@ -0,0 +1,23 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file is the webpack entry point for the browser Karma tests. It requires +// all modules ending in "test" from the current folder and all its subfolders. +const testsContext = require.context('.', true, /test$/); +testsContext.keys().forEach(testsContext); + +const srcContext = require.context('.', true, /src$/); +srcContext.keys().forEach(srcContext); diff --git a/packages/opentelemetry-propagator-jaeger/tsconfig-release.json b/packages/opentelemetry-propagator-jaeger/tsconfig-release.json new file mode 100644 index 0000000000..ffc0f77968 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/tsconfig-release.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": [] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/opentelemetry-propagator-jaeger/tsconfig.json b/packages/opentelemetry-propagator-jaeger/tsconfig.json new file mode 100644 index 0000000000..a2042cd68b --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base", + "compilerOptions": { + "rootDir": ".", + "outDir": "build" + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/packages/opentelemetry-propagator-jaeger/tslint.json b/packages/opentelemetry-propagator-jaeger/tslint.json new file mode 100644 index 0000000000..0710b135d0 --- /dev/null +++ b/packages/opentelemetry-propagator-jaeger/tslint.json @@ -0,0 +1,4 @@ +{ + "rulesDirectory": ["node_modules/tslint-microsoft-contrib"], + "extends": ["../../tslint.base.js", "./node_modules/tslint-consistent-codestyle"] +} From 8b62485d3ccfa1ae6109dab202380384b223f408 Mon Sep 17 00:00:00 2001 From: Naseem Date: Mon, 20 Jan 2020 15:52:15 -0500 Subject: [PATCH 07/21] fix: to tracer registry (#709) --- getting-started/README.md | 14 +++++++------- getting-started/traced-example/tracing.js | 6 +++--- getting-started/ts-example/tracing.ts | 9 ++++----- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/getting-started/README.md b/getting-started/README.md index e5129c4694..ae6bb5a15b 100644 --- a/getting-started/README.md +++ b/getting-started/README.md @@ -74,13 +74,13 @@ Create a file named `tracing.js` and add the following code: 'use strict'; const opentelemetry = require("@opentelemetry/core"); -const { NodeTracer } = require("@opentelemetry/node"); +const { NodeTracerRegistry } = require("@opentelemetry/node"); -const tracer = new NodeTracer({ +const tracerRegistry = new NodeTracerRegistry({ logLevel: opentelemetry.LogLevel.ERROR }); -opentelemetry.initGlobalTracer(tracer); +opentelemetry.initGlobalTracerRegistry(tracerRegistry); ``` If you run your application now with `node -r ./tracing.js app.js`, your application will create and propagate traces over HTTP. If an already instrumented service that supports [Trace Context](https://www.w3.org/TR/trace-context/) headers calls your application using HTTP, and you call another application using HTTP, the Trace Context headers will be correctly propagated. @@ -110,18 +110,18 @@ After these dependencies are installed, we will need to initialize and register 'use strict'; const opentelemetry = require("@opentelemetry/core"); -const { NodeTracer } = require("@opentelemetry/node"); +const { NodeTracerRegistry } = require("@opentelemetry/node"); const { SimpleSpanProcessor } = require("@opentelemetry/tracing"); const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin"); -const tracer = new NodeTracer({ +const tracerRegistry = new NodeTracerRegistry({ logLevel: opentelemetry.LogLevel.ERROR }); -opentelemetry.initGlobalTracer(tracer); +opentelemetry.initGlobalTracerRegistry(tracerRegistry); -tracer.addSpanProcessor( +tracerRegistry.addSpanProcessor( new SimpleSpanProcessor( new ZipkinExporter({ serviceName: "getting-started", diff --git a/getting-started/traced-example/tracing.js b/getting-started/traced-example/tracing.js index 6a4007f307..4f265d0539 100644 --- a/getting-started/traced-example/tracing.js +++ b/getting-started/traced-example/tracing.js @@ -1,13 +1,13 @@ "use strict"; const opentelemetry = require("@opentelemetry/core"); -const { NodeTracer } = require("@opentelemetry/node"); +const { NodeTracerRegistry } = require("@opentelemetry/node"); const { SimpleSpanProcessor } = require("@opentelemetry/tracing"); const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin"); -const tracer = new NodeTracer({ logLevel: opentelemetry.LogLevel.ERROR }); -opentelemetry.initGlobalTracer(tracer); +const tracerRegistry = new NodeTracerRegistry({ logLevel: opentelemetry.LogLevel.ERROR }); +opentelemetry.initGlobalTracerRegistry(tracerRegistry); tracer.addSpanProcessor( new SimpleSpanProcessor( diff --git a/getting-started/ts-example/tracing.ts b/getting-started/ts-example/tracing.ts index 056217fb76..ddaac1a760 100644 --- a/getting-started/ts-example/tracing.ts +++ b/getting-started/ts-example/tracing.ts @@ -1,16 +1,15 @@ import * as opentelemetry from "@opentelemetry/core"; -import { NodeTracer } from "@opentelemetry/node"; - +import { NodeTracerRegistry } from "@opentelemetry/node"; import { SimpleSpanProcessor } from "@opentelemetry/tracing"; import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"; -const tracer: NodeTracer = new NodeTracer({ +const tracerRegistry: NodeTracerRegistry = new NodeTracerRegistry({ logLevel: opentelemetry.LogLevel.ERROR }); -opentelemetry.initGlobalTracer(tracer); +opentelemetry.initGlobalTracerRegistry(tracerRegistry); -tracer.addSpanProcessor( +tracerRegistry.addSpanProcessor( new SimpleSpanProcessor( new ZipkinExporter({ serviceName: "getting-started" From 96bfebb16c99e81603fd4f521b2fafb6607e185b Mon Sep 17 00:00:00 2001 From: Naseem Date: Tue, 21 Jan 2020 09:20:19 -0500 Subject: [PATCH 08/21] =?UTF-8?q?docs:=20typo=20in=20Ville=20de=20Montr?= =?UTF-8?q?=C3=A9al=20(#716)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cc87cc19b..36c5d8295e 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Approvers ([@open-telemetry/js-approvers](https://github.com/orgs/open-telemetry - [Roch Devost](https://github.com/rochdev), DataDog - [Brandon Gonzalez](https://github.com/bg451), LightStep -- [Olivier Albertini](https://github.com/OlivierAlbertini), VilledeMontreal +- [Olivier Albertini](https://github.com/OlivierAlbertini), Ville de Montréal - [Valentin Marchaud](https://github.com/vmarchaud), Open Source Contributor - [Mark Wolff](https://github.com/markwolff), Microsoft - [Bartlomiej Obecny](https://github.com/obecny), LightStep From 2aa5399f444013edb9c94d6261047bc7fa582c1c Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Tue, 21 Jan 2020 13:29:29 -0500 Subject: [PATCH 09/21] fix: return module exports from ioredis (#714) --- packages/opentelemetry-plugin-ioredis/src/ioredis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-plugin-ioredis/src/ioredis.ts b/packages/opentelemetry-plugin-ioredis/src/ioredis.ts index 428e174057..7d08fd0fed 100644 --- a/packages/opentelemetry-plugin-ioredis/src/ioredis.ts +++ b/packages/opentelemetry-plugin-ioredis/src/ioredis.ts @@ -44,7 +44,7 @@ export class IORedisPlugin extends BasePlugin { this._patchConnection() ); - return this._moduleExports.prototype; + return this._moduleExports; } protected unpatch(): void { From 63748aed257c53d3ad74e7609813ac7e568f9558 Mon Sep 17 00:00:00 2001 From: Xiao Date: Tue, 21 Jan 2020 11:15:20 -0800 Subject: [PATCH 10/21] feat: implement named meter (#700) * feat: implement named meter * chore: update name provider to registry, remove 'default' name on MeterProvider * chore: remove duplicated types import * chore: address comments --- examples/prometheus/index.js | 6 +- getting-started/README.md | 14 ++-- .../monitored-example/monitoring.js | 6 +- getting-started/ts-example/README.md | 32 ++++----- getting-started/ts-example/monitoring.ts | 8 +-- .../src/metrics/NoopMeter.ts | 2 + .../src/metrics/NoopMeterRegistry.ts | 28 ++++++++ .../opentelemetry-core/src/trace/NoopSpan.ts | 3 +- .../test/metrics/NoopMeter.test.ts | 4 +- .../test/trace/globaltracer-utils.test.ts | 3 +- .../README.md | 4 +- .../test/prometheus.test.ts | 11 ++- packages/opentelemetry-metrics/README.md | 8 +-- .../src/MeterRegistry.ts | 46 +++++++++++++ packages/opentelemetry-metrics/src/index.ts | 1 + .../opentelemetry-metrics/test/Meter.test.ts | 5 +- .../test/MeterRegistry.test.ts | 68 +++++++++++++++++++ .../test/export/ConsoleMetricExporter.test.ts | 7 +- .../test/xhr.test.ts | 5 +- .../src/BasicTracerRegistry.ts | 3 +- packages/opentelemetry-tracing/src/Tracer.ts | 16 ++--- packages/opentelemetry-types/src/index.ts | 1 + .../src/metrics/MeterRegistry.ts | 29 ++++++++ 23 files changed, 242 insertions(+), 68 deletions(-) create mode 100644 packages/opentelemetry-core/src/metrics/NoopMeterRegistry.ts create mode 100644 packages/opentelemetry-metrics/src/MeterRegistry.ts create mode 100644 packages/opentelemetry-metrics/test/MeterRegistry.test.ts create mode 100644 packages/opentelemetry-types/src/metrics/MeterRegistry.ts diff --git a/examples/prometheus/index.js b/examples/prometheus/index.js index 166dff8e94..fea1d1c9d8 100644 --- a/examples/prometheus/index.js +++ b/examples/prometheus/index.js @@ -1,9 +1,9 @@ "use strict"; -const { Meter } = require("@opentelemetry/metrics"); -const { PrometheusExporter } = require("@opentelemetry/exporter-prometheus"); +const { MeterRegistry } = require('@opentelemetry/metrics'); +const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('example-prometheus'); const exporter = new PrometheusExporter( { diff --git a/getting-started/README.md b/getting-started/README.md index ae6bb5a15b..61ae41d7e8 100644 --- a/getting-started/README.md +++ b/getting-started/README.md @@ -238,9 +238,9 @@ Create a file named `monitoring.js` and add the following code: ```javascript 'use strict'; -const { Meter } = require("@opentelemetry/metrics"); +const { MeterRegistry } = require('@opentelemetry/metrics'); -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); ``` Now, you can require this file from your application code and use the `Meter` to create and manage metrics. The simplest of these metrics is a counter. Let's create and export from our `monitoring.js` file a middleware function that express can use to count all requests by route. Modify the `monitoring.js` file so that it looks like this: @@ -248,9 +248,9 @@ Now, you can require this file from your application code and use the `Meter` to ```javascript 'use strict'; -const { Meter } = require("@opentelemetry/metrics"); +const { MeterRegistry } = require('@opentelemetry/metrics'); -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); const requestCount = meter.createCounter("requests", { monotonic: true, @@ -301,10 +301,10 @@ Next, modify your `monitoring.js` file to look like this: ```javascript "use strict"; -const { Meter } = require("@opentelemetry/metrics"); -const { PrometheusExporter } = require("@opentelemetry/exporter-prometheus"); +const { MeterRegistry } = require('@opentelemetry/metrics'); +const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); meter.addExporter( new PrometheusExporter( diff --git a/getting-started/monitored-example/monitoring.js b/getting-started/monitored-example/monitoring.js index 8eb1984f68..4567c158d1 100644 --- a/getting-started/monitored-example/monitoring.js +++ b/getting-started/monitored-example/monitoring.js @@ -1,9 +1,9 @@ "use strict"; -const { Meter } = require("@opentelemetry/metrics"); -const { PrometheusExporter } = require("@opentelemetry/exporter-prometheus"); +const { MeterRegistry } = require('@opentelemetry/metrics'); +const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('example-monitored'); meter.addExporter( new PrometheusExporter( diff --git a/getting-started/ts-example/README.md b/getting-started/ts-example/README.md index 0a76f4a658..ac188fcfb4 100644 --- a/getting-started/ts-example/README.md +++ b/getting-started/ts-example/README.md @@ -72,8 +72,8 @@ All tracing initialization should happen before your application’s code runs. Create a file named `tracing.ts` and add the following code: ```typescript -import * as opentelemetry from "@opentelemetry/core"; -import { NodeTracer } from "@opentelemetry/node"; +import * as opentelemetry from '@opentelemetry/core'; +import { NodeTracer } from '@opentelemetry/node'; const tracer: NodeTracer = new NodeTracer({ logLevel: opentelemetry.LogLevel.ERROR @@ -106,13 +106,13 @@ $ # npm install @opentelemetry/exporter-jaeger After these dependencies are installed, we will need to initialize and register them. Modify `tracing.ts` so that it matches the following code snippet, replacing the service name `"getting-started"` with your own service name if you wish. ```typescript -import * as opentelemetry from "@opentelemetry/core"; -import { NodeTracer } from "@opentelemetry/node"; +import * as opentelemetry from '@opentelemetry/core'; +import { NodeTracer } from '@opentelemetry/node'; -import { SimpleSpanProcessor } from "@opentelemetry/tracing"; -import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"; +import { SimpleSpanProcessor } from '@opentelemetry/tracing'; +import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; // For Jaeger, use the following line instead: -// import { JaegerExporter } from "@opentelemetry/exporter-jaeger"; +// import { JaegerExporter } from '@opentelemetry/exporter-jaeger'; const tracer: NodeTracer = new NodeTracer({ logLevel: opentelemetry.LogLevel.ERROR @@ -236,18 +236,18 @@ In order to create and monitor metrics, we will need a `Meter`. In OpenTelemetry Create a file named `monitoring.ts` and add the following code: ```typescript -import { Meter } from "@opentelemetry/metrics"; +import { MeterRegistry } from '@opentelemetry/metrics'; -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); ``` Now, you can require this file from your application code and use the `Meter` to create and manage metrics. The simplest of these metrics is a counter. Let's create and export from our `monitoring.ts` file a middleware function that express can use to count all requests by route. Modify the `monitoring.ts` file so that it looks like this: ```typescript -import { Meter } from "@opentelemetry/metrics"; -import { Metric, BoundCounter } from "@opentelemetry/types"; +import { MeterRegistry } from '@opentelemetry/metrics'; +import { Metric, BoundCounter } from '@opentelemetry/types'; -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); const requestCount: Metric = meter.createCounter("requests", { monotonic: true, @@ -296,11 +296,11 @@ $ npm install @opentelemetry/exporter-prometheus Next, modify your `monitoring.ts` file to look like this: ```typescript -import { Meter } from "@opentelemetry/metrics"; -import { Metric, BoundCounter } from "@opentelemetry/types"; -import { PrometheusExporter } from "@opentelemetry/exporter-prometheus"; +import { MeterRegistry } from '@opentelemetry/metrics'; +import { Metric, BoundCounter } from '@opentelemetry/types'; +import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); meter.addExporter( new PrometheusExporter({ startServer: true }, () => { diff --git a/getting-started/ts-example/monitoring.ts b/getting-started/ts-example/monitoring.ts index e510eccdde..1dd3b57037 100644 --- a/getting-started/ts-example/monitoring.ts +++ b/getting-started/ts-example/monitoring.ts @@ -1,8 +1,8 @@ -import { Meter } from "@opentelemetry/metrics"; -import { Metric, BoundCounter } from "@opentelemetry/types"; -import { PrometheusExporter } from "@opentelemetry/exporter-prometheus"; +import { MeterRegistry } from '@opentelemetry/metrics'; +import { Metric, BoundCounter } from '@opentelemetry/types'; +import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('example-ts'); meter.addExporter( new PrometheusExporter({ startServer: true }, () => { diff --git a/packages/opentelemetry-core/src/metrics/NoopMeter.ts b/packages/opentelemetry-core/src/metrics/NoopMeter.ts index 4f67d4c2d6..5e63905ae5 100644 --- a/packages/opentelemetry-core/src/metrics/NoopMeter.ts +++ b/packages/opentelemetry-core/src/metrics/NoopMeter.ts @@ -165,6 +165,8 @@ export class NoopBoundMeasure implements BoundMeasure { } } +export const noopMeter = new NoopMeter(); + export const NOOP_BOUND_GAUGE = new NoopBoundGauge(); export const NOOP_GAUGE_METRIC = new NoopGaugeMetric(NOOP_BOUND_GAUGE); diff --git a/packages/opentelemetry-core/src/metrics/NoopMeterRegistry.ts b/packages/opentelemetry-core/src/metrics/NoopMeterRegistry.ts new file mode 100644 index 0000000000..ab2a28e0ec --- /dev/null +++ b/packages/opentelemetry-core/src/metrics/NoopMeterRegistry.ts @@ -0,0 +1,28 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as types from '@opentelemetry/types'; +import { noopMeter } from './NoopMeter'; + +/** + * An implementation of the {@link MeterRegistry} which returns an impotent Meter + * for all calls to `getMeter` + */ +export class NoopMeterRegistry implements types.MeterRegistry { + getMeter(_name?: string, _version?: string): types.Meter { + return noopMeter; + } +} diff --git a/packages/opentelemetry-core/src/trace/NoopSpan.ts b/packages/opentelemetry-core/src/trace/NoopSpan.ts index 84a5cf4241..a6fb36da30 100644 --- a/packages/opentelemetry-core/src/trace/NoopSpan.ts +++ b/packages/opentelemetry-core/src/trace/NoopSpan.ts @@ -15,7 +15,6 @@ */ import * as types from '@opentelemetry/types'; -import { SpanContext } from '@opentelemetry/types'; import { INVALID_SPAN_CONTEXT } from '../trace/spancontext-utils'; /** @@ -25,7 +24,7 @@ import { INVALID_SPAN_CONTEXT } from '../trace/spancontext-utils'; */ export class NoopSpan implements types.Span { constructor( - private readonly _spanContext: SpanContext = INVALID_SPAN_CONTEXT + private readonly _spanContext: types.SpanContext = INVALID_SPAN_CONTEXT ) {} // Returns a SpanContext. diff --git a/packages/opentelemetry-core/test/metrics/NoopMeter.test.ts b/packages/opentelemetry-core/test/metrics/NoopMeter.test.ts index ade4d93025..1e15fdb6b5 100644 --- a/packages/opentelemetry-core/test/metrics/NoopMeter.test.ts +++ b/packages/opentelemetry-core/test/metrics/NoopMeter.test.ts @@ -16,7 +16,6 @@ import * as assert from 'assert'; import { - NoopMeter, NOOP_BOUND_GAUGE, NOOP_GAUGE_METRIC, NOOP_BOUND_COUNTER, @@ -25,10 +24,11 @@ import { NOOP_MEASURE_METRIC, } from '../../src/metrics/NoopMeter'; import { Labels } from '@opentelemetry/types'; +import { NoopMeterRegistry } from '../../src/metrics/NoopMeterRegistry'; describe('NoopMeter', () => { it('should not crash', () => { - const meter = new NoopMeter(); + const meter = new NoopMeterRegistry().getMeter('test-noop'); const counter = meter.createCounter('some-name'); const labels = {} as Labels; const labelSet = meter.labels(labels); diff --git a/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts b/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts index 4fc5ae0c05..71f1051eba 100644 --- a/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts +++ b/packages/opentelemetry-core/test/trace/globaltracer-utils.test.ts @@ -21,7 +21,6 @@ import { initGlobalTracerRegistry, } from '../../src/trace/globaltracer-utils'; import { NoopTracer, NoopSpan } from '../../src'; -import { TraceFlags } from '@opentelemetry/types'; import { NoopTracerRegistry } from '../../src/trace/NoopTracerRegistry'; describe('globaltracer-utils', () => { @@ -43,7 +42,7 @@ describe('globaltracer-utils', () => { const spanContext = { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.UNSAMPLED, + traceFlags: types.TraceFlags.UNSAMPLED, }; const dummySpan = new NoopSpan(spanContext); diff --git a/packages/opentelemetry-exporter-prometheus/README.md b/packages/opentelemetry-exporter-prometheus/README.md index 67cb64ba4d..0fddcd8bfc 100644 --- a/packages/opentelemetry-exporter-prometheus/README.md +++ b/packages/opentelemetry-exporter-prometheus/README.md @@ -22,14 +22,14 @@ Create & register the exporter on your application. ```js const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); -const { Meter } = require('@opentelemetry/metrics'); +const { MeterRegistry } = require('@opentelemetry/metrics'); // Add your port and startServer to the Prometheus options const options = {port: 9464, startServer: true}; const exporter = new PrometheusExporter(options); // Register the exporter -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('exporter-prometheus'); meter.addExporter(exporter); // Now, start recording data diff --git a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts index 16cec53885..a019dc8420 100644 --- a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts +++ b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { CounterMetric, GaugeMetric, Meter } from '@opentelemetry/metrics'; +import { + CounterMetric, + GaugeMetric, + Meter, + MeterRegistry, +} from '@opentelemetry/metrics'; import * as assert from 'assert'; import * as http from 'http'; import { PrometheusExporter } from '../src'; @@ -166,7 +171,7 @@ describe('PrometheusExporter', () => { beforeEach(done => { exporter = new PrometheusExporter(); - meter = new Meter(); + meter = new MeterRegistry().getMeter('test-prometheus'); exporter.startServer(done); }); @@ -382,7 +387,7 @@ describe('PrometheusExporter', () => { let exporter: PrometheusExporter | undefined; beforeEach(() => { - meter = new Meter(); + meter = new MeterRegistry().getMeter('test-prometheus'); gauge = meter.createGauge('gauge') as GaugeMetric; gauge.bind(meter.labels({ key1: 'labelValue1' })).set(10); }); diff --git a/packages/opentelemetry-metrics/README.md b/packages/opentelemetry-metrics/README.md index a1c3b7a11b..c48868ced6 100644 --- a/packages/opentelemetry-metrics/README.md +++ b/packages/opentelemetry-metrics/README.md @@ -19,10 +19,10 @@ npm install --save @opentelemetry/metrics Choose this kind of metric when the value is a quantity, the sum is of primary interest, and the event count and value distribution are not of primary interest. Counters are defined as `Monotonic = true` by default, meaning that positive values are expected. ```js -const { Meter } = require('@opentelemetry/metrics'); +const { MeterRegistry } = require('@opentelemetry/metrics'); // Initialize the Meter to capture measurements in various ways. -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); const counter = meter.createCounter('metric_name', { labelKeys: ["pid"], @@ -40,10 +40,10 @@ boundCounter.add(10); Gauge metrics express a pre-calculated value. Generally, this kind of metric should be used when the metric cannot be expressed as a sum or because the measurement interval is arbitrary. Use this kind of metric when the measurement is not a quantity, and the sum and event count are not of interest. Gauges are defined as `Monotonic = false` by default, meaning that new values are permitted to make positive or negative changes to the gauge. There is no restriction on the sign of the input for gauges. ```js -const { Meter } = require('@opentelemetry/metrics'); +const { MeterRegistry } = require('@opentelemetry/metrics'); // Initialize the Meter to capture measurements in various ways. -const meter = new Meter(); +const meter = new MeterRegistry().getMeter('your-meter-name'); const gauge = meter.createGauge('metric_name', { labelKeys: ["pid"], diff --git a/packages/opentelemetry-metrics/src/MeterRegistry.ts b/packages/opentelemetry-metrics/src/MeterRegistry.ts new file mode 100644 index 0000000000..c9eb683663 --- /dev/null +++ b/packages/opentelemetry-metrics/src/MeterRegistry.ts @@ -0,0 +1,46 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConsoleLogger } from '@opentelemetry/core'; +import * as types from '@opentelemetry/types'; +import { Meter } from '.'; +import { DEFAULT_CONFIG, MeterConfig } from './types'; + +/** + * This class represents a meter registry which platform libraries can extend + */ +export class MeterRegistry implements types.MeterRegistry { + private readonly _meters: Map = new Map(); + readonly logger: types.Logger; + + constructor(private _config: MeterConfig = DEFAULT_CONFIG) { + this.logger = _config.logger || new ConsoleLogger(_config.logLevel); + } + + /** + * Returns a Meter, creating one if one with the given name and version is not already created + * + * @returns Meter A Meter with the given name and version + */ + getMeter(name: string, version = '*', config?: MeterConfig): Meter { + const key = `${name}@${version}`; + if (!this._meters.has(key)) { + this._meters.set(key, new Meter(config || this._config)); + } + + return this._meters.get(key)!; + } +} diff --git a/packages/opentelemetry-metrics/src/index.ts b/packages/opentelemetry-metrics/src/index.ts index 0cd212f0b5..9a8b7f26d2 100644 --- a/packages/opentelemetry-metrics/src/index.ts +++ b/packages/opentelemetry-metrics/src/index.ts @@ -17,5 +17,6 @@ export * from './BoundInstrument'; export * from './Meter'; export * from './Metric'; +export * from './MeterRegistry'; export * from './export/ConsoleMetricExporter'; export * from './export/types'; diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts index 57e19ceffe..f0269a5b24 100644 --- a/packages/opentelemetry-metrics/test/Meter.test.ts +++ b/packages/opentelemetry-metrics/test/Meter.test.ts @@ -31,6 +31,7 @@ import { hrTimeToMilliseconds, } from '@opentelemetry/core'; import { NoopExporter } from './mocks/Exporter'; +import { MeterRegistry } from '../src/MeterRegistry'; const performanceTimeOrigin = hrTime(); @@ -43,9 +44,9 @@ describe('Meter', () => { const hrTime: types.HrTime = [22, 400000000]; beforeEach(() => { - meter = new Meter({ + meter = new MeterRegistry({ logger: new NoopLogger(), - }); + }).getMeter('test-meter'); labelSet = meter.labels(labels); }); diff --git a/packages/opentelemetry-metrics/test/MeterRegistry.test.ts b/packages/opentelemetry-metrics/test/MeterRegistry.test.ts new file mode 100644 index 0000000000..e90b6c9baa --- /dev/null +++ b/packages/opentelemetry-metrics/test/MeterRegistry.test.ts @@ -0,0 +1,68 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NoopLogger } from '@opentelemetry/core'; +import * as assert from 'assert'; +import { MeterRegistry, Meter } from '../src'; + +describe('MeterRegistry', () => { + describe('constructor', () => { + it('should construct an instance without any options', () => { + const registry = new MeterRegistry(); + assert.ok(registry instanceof MeterRegistry); + }); + + it('should construct an instance with logger', () => { + const registry = new MeterRegistry({ + logger: new NoopLogger(), + }); + assert.ok(registry instanceof MeterRegistry); + }); + }); + + describe('getMeter', () => { + it('should return an instance of Meter', () => { + const meter = new MeterRegistry().getMeter('test-meter-registry'); + assert.ok(meter instanceof Meter); + }); + + it('should return the meter with default version without a version option', () => { + const registry = new MeterRegistry(); + const meter1 = registry.getMeter('default'); + const meter2 = registry.getMeter('default', '*'); + assert.deepEqual(meter1, meter2); + }); + + it('should return the same Meter instance with same name & version', () => { + const registry = new MeterRegistry(); + const meter1 = registry.getMeter('meter1', 'ver1'); + const meter2 = registry.getMeter('meter1', 'ver1'); + assert.deepEqual(meter1, meter2); + }); + + it('should return different Meter instance with different name or version', () => { + const registry = new MeterRegistry(); + + const meter1 = registry.getMeter('meter1', 'ver1'); + const meter2 = registry.getMeter('meter1'); + assert.notEqual(meter1, meter2); + + const meter3 = registry.getMeter('meter2', 'ver2'); + const meter4 = registry.getMeter('meter3', 'ver2'); + assert.notEqual(meter3, meter4); + }); + }); +}); diff --git a/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts b/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts index 996b423853..4c9e17e001 100644 --- a/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts +++ b/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts @@ -16,7 +16,8 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { ConsoleMetricExporter, Meter } from '../../src'; +import { ConsoleMetricExporter } from '../../src'; +import { MeterRegistry } from '../../src/MeterRegistry'; describe('ConsoleMetricExporter', () => { let consoleExporter: ConsoleMetricExporter; @@ -36,7 +37,9 @@ describe('ConsoleMetricExporter', () => { it('should export information about metrics', () => { const spyConsole = sinon.spy(console, 'log'); - const meter = new Meter(); + const meter = new MeterRegistry().getMeter( + 'test-console-metric-exporter' + ); meter.addExporter(consoleExporter); const gauge = meter.createGauge('gauge', { description: 'a test description', diff --git a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts index 931e31756d..d21736db08 100644 --- a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts +++ b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts @@ -34,7 +34,6 @@ import { import { AttributeNames } from '../src/enums/AttributeNames'; import { EventNames } from '../src/enums/EventNames'; import { XMLHttpRequestPlugin } from '../src/xhr'; -import { Tracer } from '@opentelemetry/types'; class DummySpanExporter implements tracing.SpanExporter { export(spans: any) {} @@ -101,7 +100,7 @@ describe('xhr', () => { let clearData: any; describe('when request is successful', () => { - let webTracerWithZone: Tracer; + let webTracerWithZone: types.Tracer; let webTracerRegistryWithZone: WebTracerRegistry; let dummySpanExporter: DummySpanExporter; let exportSpy: any; @@ -410,7 +409,7 @@ describe('xhr', () => { describe('when request is NOT successful', () => { let webTracerWithZoneRegistry: WebTracerRegistry; - let webTracerWithZone: Tracer; + let webTracerWithZone: types.Tracer; let dummySpanExporter: DummySpanExporter; let exportSpy: any; let rootSpan: types.Span; diff --git a/packages/opentelemetry-tracing/src/BasicTracerRegistry.ts b/packages/opentelemetry-tracing/src/BasicTracerRegistry.ts index b646b4a516..64b0984304 100644 --- a/packages/opentelemetry-tracing/src/BasicTracerRegistry.ts +++ b/packages/opentelemetry-tracing/src/BasicTracerRegistry.ts @@ -16,7 +16,6 @@ import { ConsoleLogger } from '@opentelemetry/core'; import * as types from '@opentelemetry/types'; -import { Logger } from '@opentelemetry/types'; import { SpanProcessor, Tracer } from '.'; import { DEFAULT_CONFIG } from './config'; import { MultiSpanProcessor } from './MultiSpanProcessor'; @@ -31,7 +30,7 @@ export class BasicTracerRegistry implements types.TracerRegistry { private readonly _tracers: Map = new Map(); activeSpanProcessor = new NoopSpanProcessor(); - readonly logger: Logger; + readonly logger: types.Logger; constructor(private _config: TracerConfig = DEFAULT_CONFIG) { this.logger = _config.logger || new ConsoleLogger(_config.logLevel); diff --git a/packages/opentelemetry-tracing/src/Tracer.ts b/packages/opentelemetry-tracing/src/Tracer.ts index 504df8b588..00debfb803 100644 --- a/packages/opentelemetry-tracing/src/Tracer.ts +++ b/packages/opentelemetry-tracing/src/Tracer.ts @@ -22,12 +22,6 @@ import { NoRecordingSpan, ConsoleLogger, } from '@opentelemetry/core'; -import { - BinaryFormat, - HttpTextFormat, - TraceFlags, - Logger, -} from '@opentelemetry/types'; import { TracerConfig, TraceParams } from './types'; import { ScopeManager } from '@opentelemetry/scope-base'; import { Span } from './Span'; @@ -45,7 +39,7 @@ export class Tracer implements types.Tracer { private readonly _sampler: types.Sampler; private readonly _scopeManager: ScopeManager; private readonly _traceParams: TraceParams; - readonly logger: Logger; + readonly logger: types.Logger; /** * Constructs a new Tracer instance. @@ -84,8 +78,8 @@ export class Tracer implements types.Tracer { traceState = parentContext.traceState; } const traceFlags = samplingDecision - ? TraceFlags.SAMPLED - : TraceFlags.UNSAMPLED; + ? types.TraceFlags.SAMPLED + : types.TraceFlags.UNSAMPLED; const spanContext = { traceId, spanId, traceFlags, traceState }; const recordEvents = options.isRecording || false; if (!recordEvents && !samplingDecision) { @@ -145,14 +139,14 @@ export class Tracer implements types.Tracer { /** * Returns the binary format interface which can serialize/deserialize Spans. */ - getBinaryFormat(): BinaryFormat { + getBinaryFormat(): types.BinaryFormat { return this._binaryFormat; } /** * Returns the HTTP text format interface which can inject/extract Spans. */ - getHttpTextFormat(): HttpTextFormat { + getHttpTextFormat(): types.HttpTextFormat { return this._httpTextFormat; } diff --git a/packages/opentelemetry-types/src/index.ts b/packages/opentelemetry-types/src/index.ts index e73c896f2e..e8f77714d3 100644 --- a/packages/opentelemetry-types/src/index.ts +++ b/packages/opentelemetry-types/src/index.ts @@ -22,6 +22,7 @@ export * from './distributed_context/DistributedContext'; export * from './distributed_context/EntryValue'; export * from './metrics/BoundInstrument'; export * from './metrics/Meter'; +export * from './metrics/MeterRegistry'; export * from './metrics/Metric'; export * from './trace/attributes'; export * from './trace/Event'; diff --git a/packages/opentelemetry-types/src/metrics/MeterRegistry.ts b/packages/opentelemetry-types/src/metrics/MeterRegistry.ts new file mode 100644 index 0000000000..75a36272da --- /dev/null +++ b/packages/opentelemetry-types/src/metrics/MeterRegistry.ts @@ -0,0 +1,29 @@ +/*! + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Meter } from './Meter'; + +/** + * MeterRegistry provides an interface for creating {@link Meter}s + */ +export interface MeterRegistry { + /** + * Returns a Meter, creating one if one with the given name and version is not already created + * + * @returns Meter A Meter with the given name and version + */ + getMeter(name: string, version?: string): Meter; +} From 104ce024d2c74b71295424c68be145176cda3b22 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Tue, 21 Jan 2020 17:58:11 -0500 Subject: [PATCH 11/21] chore: update readme.md to include documentation link (#710) --- README.md | 90 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 36c5d8295e..18577e923d 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,21 @@ This is the JavaScript version of [OpenTelemetry](https://opentelemetry.io/), a > This project is currently in Alpha stage. Its APIs can change at any time and it is not intended to be used in production scenarios! +## Documentation + +For detailed documentation of the OpenTelemetry API, see [the documentation][docs]. + ## Quick start -To get started tracing your own application, see the [Getting Started Guide](getting-started/README.md). +### Application Owner + +To get started tracing your own application, see the [Getting Started Guide](getting-started/README.md). For more information about automatic instrumentation see [@opentelemetry/node][otel-node], which provides auto-instrumentation for Node.js applications. If the automatic instrumentation does not suit your needs, or you would like to create manual traces, see [@opentelemetry/tracing][otel-tracing] -For more information, see [@opentelemetry/tracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing) for an SDK that supports creating traces through manual instrumentation or [@opentelemetry/node](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node) which provides auto-instrumentation for Node.js applications. +For more information, see [@opentelemetry/tracing][otel-tracing] for an SDK that supports creating traces through manual instrumentation or [@opentelemetry/node][otel-node] which provides auto-instrumentation for Node.js applications. + +### Library Author + +If you are a library author looking to build OpenTelemetry into your library, please see [the documentation][docs]. As a library author, it is important that you only depend on properties and methods published on the public API. If you use any properties or methods from the SDK that are not officially a part of the public API, your library may break if an [Application Owner](#application-owner) uses a different SDK implementation. ## Release Schedule @@ -88,51 +98,51 @@ Maintainers ([@open-telemetry/js-maintainers](https://github.com/orgs/open-telem | Package | Description | | ----------------------- | -----------------| -| [@opentelemetry/types](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-types) | This package provides TypeScript interfaces and enums for the OpenTelemetry core trace and metrics model. It is intended for use both on the server and in the browser. | -| [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-core) | This package provides default and no-op implementations of the OpenTelemetry types for trace and metrics. It's intended for use both on the server and in the browser. | +| [@opentelemetry/types][otel-types] | This package provides TypeScript interfaces and enums for the OpenTelemetry core trace and metrics model. It is intended for use both on the server and in the browser. | +| [@opentelemetry/core][otel-core] | This package provides default and no-op implementations of the OpenTelemetry types for trace and metrics. It's intended for use both on the server and in the browser. | ### Implementation / SDKs | Package | Description | | ----------------------- | -----------------| -| [@opentelemetry/tracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing) | This module provides a full control over instrumentation and span creation. It doesn't load [`async_hooks`](https://nodejs.org/api/async_hooks.html) or any instrumentation plugin by default. It is intended for use both on the server and in the browser. | -| [@opentelemetry/metrics](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-metrics) | This module provides instruments and meters for reporting of time series data. | -| [@opentelemetry/node](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node) | This module provides automatic tracing for Node.js applications. It is intended for use on the server only. | -| [@opentelemetry/web](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-web) | This module provides automated instrumentation and tracing for Web applications. It is intended for use in the browser only. | -| [@opentelemetry/base](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-base) | This package provides base code for the SDK packages (tracing and metrics). | +| [@opentelemetry/tracing][otel-tracing] | This module provides a full control over instrumentation and span creation. It doesn't load [`async_hooks`](https://nodejs.org/api/async_hooks.html) or any instrumentation plugin by default. It is intended for use both on the server and in the browser. | +| [@opentelemetry/metrics][otel-metrics] | This module provides instruments and meters for reporting of time series data. | +| [@opentelemetry/node][otel-node] | This module provides automatic tracing for Node.js applications. It is intended for use on the server only. | +| [@opentelemetry/web][otel-web] | This module provides automated instrumentation and tracing for Web applications. It is intended for use in the browser only. | +| [@opentelemetry/base][otel-base] | This package provides base code for the SDK packages (tracing and metrics). | ### Exporters OpenTelemetry is vendor-agnostic and can upload data to any backend with various exporter implementations. Even though, OpenTelemetry provides support for many backends, vendors/users can also implement their own exporters for proprietary and unofficially supported backends. Currently, OpenTelemetry supports: #### Trace Exporters -- [@opentelemetry/exporter-jaeger](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-jaeger) -- [@opentelemetry/exporter-zipkin](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-zipkin) -- [@opentelemetry/exporter-stackdriver-trace](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-stackdriver-trace) -- [@opentelemetry/exporter-collector](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-collector) +- [@opentelemetry/exporter-jaeger][otel-exporter-jaeger] +- [@opentelemetry/exporter-zipkin][otel-exporter-zipkin] +- [@opentelemetry/exporter-stackdriver-trace][otel-exporter-stackdriver-trace] +- [@opentelemetry/exporter-collector][otel-exporter-collector] #### Metric Exporters -- [@opentelemetry/exporter-prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-prometheus) +- [@opentelemetry/exporter-prometheus][otel-exporter-prometheus] ### Plugins OpenTelemetry can collect tracing data automatically using plugins. Vendors/Users can also create and use their own. Currently, OpenTelemetry supports automatic tracing for: #### Node Plugins -- [@opentelemetry/plugin-grpc](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-grpc) -- [@opentelemetry/plugin-http](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-http) -- [@opentelemetry/plugin-https](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-https) -- [@opentelemetry/plugin-mongodb](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-mongodb) -- [@opentelemetry/plugin-mysql](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-mysql) -- [@opentelemetry/plugin-pg](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg) -- [@opentelemetry/plugin-pg-pool](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool) -- [@opentelemetry/plugin-redis](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-redis) -- [@opentelemetry/plugin-ioredis](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-ioredis) -- [@opentelemetry/plugin-dns](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-dns) - By default, this plugin is not loaded [#612](https://github.com/open-telemetry/opentelemetry-js/issues/612) +- [@opentelemetry/plugin-grpc][otel-plugin-grpc] +- [@opentelemetry/plugin-http][otel-plugin-http] +- [@opentelemetry/plugin-https][otel-plugin-https] +- [@opentelemetry/plugin-mongodb][otel-plugin-mongodb] +- [@opentelemetry/plugin-mysql][otel-plugin-mysql] +- [@opentelemetry/plugin-pg][otel-plugin-pg] +- [@opentelemetry/plugin-pg-pool][otel-plugin-pg-pool] +- [@opentelemetry/plugin-redis][otel-plugin-redis] +- [@opentelemetry/plugin-ioredis][otel-plugin-ioredis] +- [@opentelemetry/plugin-dns][otel-plugin-dns] - By default, this plugin is not loaded [#612](https://github.com/open-telemetry/opentelemetry-js/issues/612) #### Web Plugins -- [@opentelemetry/plugin-document-load](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-document-load) -- [@opentelemetry/plugin-xml-http-request](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-xml-http-request) +- [@opentelemetry/plugin-document-load][otel-plugin-document-load] +- [@opentelemetry/plugin-xml-http-request][otel-plugin-xml-http-request] To request automatic tracing support for a module not on this list, please [file an issue](https://github.com/open-telemetry/opentelemetry-js/issues). Alternatively, you can [write a plugin yourself](https://github.com/open-telemetry/opentelemetry-js/blob/master/doc/plugin-guide.md). @@ -140,7 +150,7 @@ To request automatic tracing support for a module not on this list, please [file | Package | Description | | ----------------------- | -----------------| -| [@opentelemetry/shim-opentracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-shim-opentracing) | OpenTracing shim allows existing OpenTracing instrumentation to report to OpenTelemetry | +| [@opentelemetry/shim-opentracing][otel-shim-opentracing] | OpenTracing shim allows existing OpenTracing instrumentation to report to OpenTelemetry | ## Useful links @@ -161,3 +171,29 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [codecov-image]: https://codecov.io/gh/open-telemetry/opentelemetry-js/branch/master/graph/badge.svg [codecov-url]: https://codecov.io/gh/open-telemetry/opentelemetry-js/branch/master/ [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat + +[docs]: https://open-telemetry.github.io/opentelemetry-js + +[otel-base]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-base +[otel-exporter-collector]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-collector +[otel-exporter-jaeger]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-jaeger +[otel-exporter-prometheus]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-prometheus +[otel-exporter-stackdriver-trace]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-stackdriver-trace +[otel-exporter-zipkin]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-zipkin +[otel-metrics]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-metrics +[otel-node]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node +[otel-plugin-dns]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-dns +[otel-plugin-document-load]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-document-load +[otel-plugin-grpc]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-grpc +[otel-plugin-http]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-http +[otel-plugin-https]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-https +[otel-plugin-ioredis]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-ioredis +[otel-plugin-mongodb]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-mongodb +[otel-plugin-mysql]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-mysql +[otel-plugin-pg-pool]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool +[otel-plugin-pg]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg +[otel-plugin-redis]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-redis +[otel-plugin-xml-http-request]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-xml-http-request +[otel-shim-opentracing]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-shim-opentracing +[otel-tracing]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing +[otel-web]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-web From 8496fb2a9369521506db461f71bee5843592d468 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Wed, 22 Jan 2020 09:15:49 -0500 Subject: [PATCH 12/21] Simplify and speed up trace context parsing (#708) * chore: simplify and speed up trace context parsing --- .../context/propagation/HttpTraceContext.ts | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts b/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts index b003572f2b..9e42f991c2 100644 --- a/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts +++ b/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts @@ -14,15 +14,12 @@ * limitations under the License. */ -import { SpanContext, HttpTextFormat, TraceFlags } from '@opentelemetry/types'; +import { HttpTextFormat, SpanContext, TraceFlags } from '@opentelemetry/types'; import { TraceState } from '../../trace/TraceState'; export const TRACE_PARENT_HEADER = 'traceparent'; export const TRACE_STATE_HEADER = 'tracestate'; -const VALID_TRACE_PARENT_REGEX = /^[\da-f]{2}-[\da-f]{32}-[\da-f]{16}-[\da-f]{2}$/; -const VALID_TRACEID_REGEX = /^[0-9a-f]{32}$/i; -const VALID_SPANID_REGEX = /^[0-9a-f]{16}$/i; -const INVALID_ID_REGEX = /^0+$/i; +const VALID_TRACE_PARENT_REGEX = /^00-([\da-f]{32})-([\da-f]{16})-([\da-f]{2})$/; const VERSION = '00'; /** @@ -37,33 +34,19 @@ const VERSION = '00'; */ export function parseTraceParent(traceParent: string): SpanContext | null { const match = traceParent.match(VALID_TRACE_PARENT_REGEX); - if (!match) return null; - const parts = traceParent.split('-'); - const [version, traceId, spanId, option] = parts; - // tslint:disable-next-line:ban Needed to parse hexadecimal. - const traceFlags = parseInt(option, 16); - if ( - !isValidVersion(version) || - !isValidTraceId(traceId) || - !isValidSpanId(spanId) + !match || + match[1] === '00000000000000000000000000000000' || + match[2] === '0000000000000000' ) { return null; } - return { traceId, spanId, traceFlags }; -} - -function isValidVersion(version: string): boolean { - return version === VERSION; -} - -function isValidTraceId(traceId: string): boolean { - return VALID_TRACEID_REGEX.test(traceId) && !INVALID_ID_REGEX.test(traceId); -} - -function isValidSpanId(spanId: string): boolean { - return VALID_SPANID_REGEX.test(spanId) && !INVALID_ID_REGEX.test(spanId); + return { + traceId: match[1], + spanId: match[2], + traceFlags: parseInt(match[3], 16), + }; } /** From 0ad90e7ed99bf329c0c987a6742edefe584ecc77 Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Wed, 22 Jan 2020 11:12:15 -0800 Subject: [PATCH 13/21] Release process (#661) * chore: update RELEASING.md * chore: update RELEASING.md * chore: add known issue Co-authored-by: Daniel Dyla --- RELEASING.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index a2073a8fee..68b2f6c956 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,6 +2,34 @@ This document explains how to publish all OT modules at version x.y.z. Ensure that you’re following semver when choosing a version number. +Release Process: + +* [Update to latest locally](#update-to-latest-locally) +* [Create a new branch](#create-a-new-branch) +* [Prepare each package for release](#prepare-each-package-for-release) +* [Use the Changelog to create a GitHub Release](#use-the-changelog-to-create-a-github-release) +* [Create a new PR](#create-a-new-pr) +* [Merge and pull](#merge-and-pull) +* [Publish all packages](#publish-all-packages) +* [Publish the GitHub Release](#publish-the-github-release) +* [Update CHANGELOG](#update-changelog) + +## Update to latest locally + +Use `git fetch` and `git checkout origin/master` to ensure you’re on the latest commit. Make sure you have no unstaged changes. Ideally, also use `git clean -dfx` to remove all ignored and untracked files. + +## Create a new branch + +Create a new branch called `x.y.z-proposal` from the current commit. + +## Prepare each package for release + +Decide on the next `major.minor.patch` release number based on [semver](http://semver.org/) guidelines. + +* Use `npm install` command to initialize all package directories +* Use `lerna publish --skip-npm --no-git-tag-version --no-push` to bump the version in all `package.json` +* Use `npm run bootstrap` to generate latest `version.ts` files + ## Use the Changelog to create a GitHub Release ### Generate the changelog with lerna @@ -9,7 +37,7 @@ This document explains how to publish all OT modules at version x.y.z. Ensure th Since we use `lerna`, we can use [lerna-changelog](https://github.com/lerna/lerna-changelog#lerna-changelog) #### How to use -Pass your [github token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) to generate the changelog automatically. +Pass your [github token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) to generate the changelog automatically. For security reasons, when you create a Github token, select the permissions: under **repo**, select **Access public repositories**, **commit status**. In your terminal, execute the following command: @@ -41,3 +69,43 @@ GITHUB_AUTH=xxxxx lerna-changelog --from=v1.0.0 --to=v2.0.0 #### Update Changelog file From what `lerna-changelog` has generated, starts new Unreleased label. Follow the example set by recent Released label. + +On [GitHub Releases](https://github.com/open-telemetry/opentelemetry-js/releases), follow the example set by recent releases to populate a summary of changes, as well as a list of commits that were applied since the last release. Save it as a draft, don’t publish it. Don’t forget the tag -- call it `vx.y.z` and leave it pointing at `master` for now (this can be changed as long as the GitHub release isn’t published). + +## Create a new PR + +Create a pull request titled `chore: x.y.z release proposal`. The commit body should just be a link to the draft notes. Someone who can access draft notes should approve it, looking in particular for test passing, and whether the draft notes are satisfactory. + +## Merge and pull + +Merge the PR, and pull the changes locally (using the commands in the first step). Ensure that `chore: x.y.z release proposal` is the most recent commit. + +## Publish all packages + +Go into each directory and use `npm publish` (requires permissions) to publish the package. You can use the following script to automate this. + +```bash +#!/bin/bash + +for dir in $(ls packages); do + pushd packages/$dir + npm publish + popd +done +``` + +Check your e-mail and make sure the number of “you’ve published this module” emails matches the number you expect. + +## Publish the GitHub Release +Publish the GitHub release, ensuring that the tag points to the newly landed commit corresponding to release proposal `x.y.z`. + +## Update CHANGELOG + +* After releasing is done, update the [CHANGELOG.md](https://github.com/open-telemetry/opentelemetry-js/blob/master/CHANGELOG.md) and start new Unreleased label. +* Create a new commit with the exact title: `Post Release: update CHANGELOG.md`. +* Go through PR review and merge it to GitHub master branch. + + +## Known Issues + +* The `examples/` and `getting-started/` folders are not part of lerna packages, we need to manually bump the version in `package.json`. From 74abed325a3a483790b510ab19dc4e7bc13da98a Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Wed, 22 Jan 2020 13:34:09 -0800 Subject: [PATCH 14/21] fix: package link in README (#720) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 18577e923d..f295b311b3 100644 --- a/README.md +++ b/README.md @@ -197,3 +197,5 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [otel-shim-opentracing]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-shim-opentracing [otel-tracing]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing [otel-web]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-web +[otel-types]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-types +[otel-core]: https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-core From cb677c05393cbe7f1a86a9235f069813ae1cae5e Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Wed, 22 Jan 2020 14:43:33 -0800 Subject: [PATCH 15/21] chore: 0.3.3 release proposal (#683) * chore: 0.3.3 release proposal * chore: bump propagator-jaeger and exporter-stackdriver-trace version * remove examples/ from lerna --- examples/basic-tracer-node/package.json | 12 +++++------ examples/dns/package.json | 14 ++++++------- examples/grpc/package.json | 14 ++++++------- examples/grpc_dynamic_codegen/package.json | 16 +++++++-------- examples/http/package.json | 14 ++++++------- examples/https/package.json | 14 ++++++------- examples/mysql/package.json | 16 +++++++-------- examples/opentracing-shim/package.json | 10 +++++----- examples/postgres/package.json | 20 +++++++++---------- examples/prometheus/package.json | 6 +++--- examples/redis/package.json | 18 ++++++++--------- examples/stackdriver-trace/package.json | 10 +++++----- examples/tracer-web/package.json | 14 ++++++------- .../monitored-example/package.json | 2 +- getting-started/traced-example/package.json | 10 +++++----- getting-started/ts-example/package.json | 16 +++++++-------- lerna.json | 2 +- package.json | 2 +- packages/opentelemetry-base/package.json | 2 +- packages/opentelemetry-base/src/version.ts | 2 +- packages/opentelemetry-core/package.json | 4 ++-- packages/opentelemetry-core/src/version.ts | 2 +- .../package.json | 10 +++++----- .../src/version.ts | 2 +- .../package.json | 10 +++++----- .../src/version.ts | 2 +- .../package.json | 10 +++++----- .../src/version.ts | 2 +- .../package.json | 10 +++++----- .../src/version.ts | 2 +- .../package.json | 10 +++++----- .../src/version.ts | 2 +- packages/opentelemetry-metrics/package.json | 8 ++++---- packages/opentelemetry-metrics/src/version.ts | 2 +- packages/opentelemetry-node/package.json | 10 +++++----- packages/opentelemetry-node/src/version.ts | 2 +- .../opentelemetry-plugin-dns/package.json | 10 +++++----- .../opentelemetry-plugin-dns/src/version.ts | 2 +- .../package.json | 10 +++++----- .../src/version.ts | 2 +- .../opentelemetry-plugin-grpc/package.json | 10 +++++----- .../opentelemetry-plugin-grpc/src/version.ts | 2 +- .../opentelemetry-plugin-http/package.json | 12 +++++------ .../opentelemetry-plugin-http/src/version.ts | 2 +- .../opentelemetry-plugin-https/package.json | 14 ++++++------- .../opentelemetry-plugin-https/src/version.ts | 2 +- .../opentelemetry-plugin-ioredis/package.json | 12 +++++------ .../src/version.ts | 2 +- .../opentelemetry-plugin-mongodb/package.json | 10 +++++----- .../src/version.ts | 2 +- .../opentelemetry-plugin-mysql/package.json | 12 +++++------ .../opentelemetry-plugin-mysql/src/version.ts | 2 +- .../opentelemetry-plugin-pg-pool/package.json | 14 ++++++------- .../src/version.ts | 2 +- .../opentelemetry-plugin-pg/package.json | 12 +++++------ .../opentelemetry-plugin-pg/src/version.ts | 2 +- .../opentelemetry-plugin-redis/package.json | 12 +++++------ .../opentelemetry-plugin-redis/src/version.ts | 2 +- .../package.json | 14 ++++++------- .../src/version.ts | 2 +- .../package.json | 4 ++-- .../src/version.ts | 2 +- .../package.json | 4 ++-- .../src/version.ts | 2 +- .../opentelemetry-scope-base/package.json | 2 +- .../opentelemetry-scope-base/src/version.ts | 2 +- .../package.json | 4 ++-- .../src/version.ts | 2 +- .../opentelemetry-scope-zone/package.json | 4 ++-- .../opentelemetry-scope-zone/src/version.ts | 2 +- .../package.json | 8 ++++---- .../src/version.ts | 2 +- .../opentelemetry-test-utils/package.json | 8 ++++---- packages/opentelemetry-tracing/package.json | 10 +++++----- packages/opentelemetry-tracing/src/version.ts | 2 +- packages/opentelemetry-types/package.json | 2 +- packages/opentelemetry-types/src/version.ts | 2 +- packages/opentelemetry-web/package.json | 12 +++++------ packages/opentelemetry-web/src/version.ts | 2 +- 79 files changed, 272 insertions(+), 272 deletions(-) diff --git a/examples/basic-tracer-node/package.json b/examples/basic-tracer-node/package.json index b375713a44..4968965c42 100644 --- a/examples/basic-tracer-node/package.json +++ b/examples/basic-tracer-node/package.json @@ -1,7 +1,7 @@ { "name": "example-basic-tracer-node", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of using @opentelemetry/tracing in Node.js", "main": "index.js", "scripts": { @@ -30,11 +30,11 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-collector": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2" + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-collector": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { diff --git a/examples/dns/package.json b/examples/dns/package.json index bc2f8c931f..3f6a5db637 100644 --- a/examples/dns/package.json +++ b/examples/dns/package.json @@ -1,7 +1,7 @@ { "name": "dns-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of DNS integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -26,12 +26,12 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-dns": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2" + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-dns": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { diff --git a/examples/grpc/package.json b/examples/grpc/package.json index 72771fa977..ce52106694 100644 --- a/examples/grpc/package.json +++ b/examples/grpc/package.json @@ -1,7 +1,7 @@ { "name": "grpc-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of gRPC integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -28,12 +28,12 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-grpc": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-grpc": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "google-protobuf": "^3.9.2", "grpc": "^1.23.3", "node-pre-gyp": "0.12.0" diff --git a/examples/grpc_dynamic_codegen/package.json b/examples/grpc_dynamic_codegen/package.json index a4321a0169..8d91d64ee2 100644 --- a/examples/grpc_dynamic_codegen/package.json +++ b/examples/grpc_dynamic_codegen/package.json @@ -1,6 +1,6 @@ { "name": "grpc-dynamic-codegen-example", - "version": "0.3.2", + "version": "0.3.3", "description": "Example of gRPC integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -28,13 +28,13 @@ }, "dependencies": { "@grpc/proto-loader": "^0.4.0", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-grpc": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-grpc": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "grpc": "^1.23.3", "node-pre-gyp": "0.12.0" }, diff --git a/examples/http/package.json b/examples/http/package.json index 6ee9a5f01e..98503047c4 100644 --- a/examples/http/package.json +++ b/examples/http/package.json @@ -1,7 +1,7 @@ { "name": "http-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of HTTP integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -28,12 +28,12 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-http": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2" + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-http": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { diff --git a/examples/https/package.json b/examples/https/package.json index 2d90f3ae14..cb46108550 100644 --- a/examples/https/package.json +++ b/examples/https/package.json @@ -1,7 +1,7 @@ { "name": "https-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of HTTPs integration with OpenTelemetry", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -29,12 +29,12 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-https": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2" + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-https": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { diff --git a/examples/mysql/package.json b/examples/mysql/package.json index 916c5b4910..09e3e92eb5 100644 --- a/examples/mysql/package.json +++ b/examples/mysql/package.json @@ -1,7 +1,7 @@ { "name": "mysql-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of mysql integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -28,13 +28,13 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-http": "^0.3.2", - "@opentelemetry/plugin-mysql": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-http": "^0.3.3", + "@opentelemetry/plugin-mysql": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "mysql": "*" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", diff --git a/examples/opentracing-shim/package.json b/examples/opentracing-shim/package.json index 2ff92279f0..3762aca0c7 100644 --- a/examples/opentracing-shim/package.json +++ b/examples/opentracing-shim/package.json @@ -1,7 +1,7 @@ { "name": "opentracing-shim", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of using @opentelemetry/shim-opentracing in Node.js", "main": "index.js", "scripts": { @@ -29,10 +29,10 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/shim-opentracing": "^0.3.2", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/shim-opentracing": "^0.3.3", "opentracing": "^0.14.4" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", diff --git a/examples/postgres/package.json b/examples/postgres/package.json index 8907e3406c..eff66b1161 100644 --- a/examples/postgres/package.json +++ b/examples/postgres/package.json @@ -1,7 +1,7 @@ { "name": "postgres-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of Postgres integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -30,15 +30,15 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.2.0", - "@opentelemetry/exporter-jaeger": "^0.2.0", - "@opentelemetry/exporter-zipkin": "^0.2.0", - "@opentelemetry/node": "^0.2.0", - "@opentelemetry/plugin-http": "^0.2.0", - "@opentelemetry/plugin-pg": "^0.2.0", - "@opentelemetry/plugin-pg-pool": "^0.2.0", - "@opentelemetry/tracing": "^0.2.0", - "@opentelemetry/types": "^0.2.0", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-http": "^0.3.3", + "@opentelemetry/plugin-pg": "^0.3.3", + "@opentelemetry/plugin-pg-pool": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "express": "^4.17.1" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", diff --git a/examples/prometheus/package.json b/examples/prometheus/package.json index e95aa9c9ad..08bca5e867 100644 --- a/examples/prometheus/package.json +++ b/examples/prometheus/package.json @@ -1,6 +1,6 @@ { "name": "prometheus-example", - "version": "0.3.2", + "version": "0.3.3", "description": "Example of using @opentelemetry/metrics and @opentelemetry/exporter-prometheus", "main": "index.js", "scripts": { @@ -9,7 +9,7 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/exporter-prometheus": "^0.3.2", - "@opentelemetry/metrics": "^0.3.2" + "@opentelemetry/exporter-prometheus": "^0.3.3", + "@opentelemetry/metrics": "^0.3.3" } } diff --git a/examples/redis/package.json b/examples/redis/package.json index 91bc0a715a..538dab4d12 100644 --- a/examples/redis/package.json +++ b/examples/redis/package.json @@ -1,7 +1,7 @@ { "name": "redis-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of HTTP integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -30,14 +30,14 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-jaeger": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-http": "^0.3.2", - "@opentelemetry/plugin-redis": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-jaeger": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-http": "^0.3.3", + "@opentelemetry/plugin-redis": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "axios": "^0.19.0", "express": "^4.17.1", "redis": "^2.8.0" diff --git a/examples/stackdriver-trace/package.json b/examples/stackdriver-trace/package.json index d464707d56..a670009ef6 100644 --- a/examples/stackdriver-trace/package.json +++ b/examples/stackdriver-trace/package.json @@ -1,7 +1,7 @@ { "name": "example-stackdriver-trace", "private": true, - "version": "0.3.1", + "version": "0.3.3", "description": "Example of using @opentelemetry/exporter-stackdriver-trace in Node.js", "main": "index.js", "scripts": { @@ -25,10 +25,10 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/core": "^0.3.1", - "@opentelemetry/exporter-stackdriver-trace": "^0.3.1", - "@opentelemetry/tracing": "^0.3.1", - "@opentelemetry/types": "^0.3.1" + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-stackdriver-trace": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { diff --git a/examples/tracer-web/package.json b/examples/tracer-web/package.json index f4b3024867..ac246d9c1a 100644 --- a/examples/tracer-web/package.json +++ b/examples/tracer-web/package.json @@ -1,7 +1,7 @@ { "name": "web-tracer-example", "private": true, - "version": "0.3.2", + "version": "0.3.3", "description": "Example of using @opentelemetry/web in browser", "main": "index.js", "scripts": { @@ -34,12 +34,12 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@opentelemetry/exporter-collector": "^0.3.2", - "@opentelemetry/plugin-document-load": "^0.3.2", - "@opentelemetry/plugin-xml-http-request": "^0.3.2", - "@opentelemetry/scope-zone": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/web": "^0.3.2" + "@opentelemetry/exporter-collector": "^0.3.3", + "@opentelemetry/plugin-document-load": "^0.3.3", + "@opentelemetry/plugin-xml-http-request": "^0.3.3", + "@opentelemetry/scope-zone": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/web": "^0.3.3" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme" } diff --git a/getting-started/monitored-example/package.json b/getting-started/monitored-example/package.json index df4bedf42d..17e17a8c89 100644 --- a/getting-started/monitored-example/package.json +++ b/getting-started/monitored-example/package.json @@ -9,7 +9,7 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/metrics": "^0.3.2", + "@opentelemetry/metrics": "^0.3.3", "axios": "^0.19.0", "express": "^4.17.1" } diff --git a/getting-started/traced-example/package.json b/getting-started/traced-example/package.json index 3114281b73..2161b7bd20 100644 --- a/getting-started/traced-example/package.json +++ b/getting-started/traced-example/package.json @@ -9,11 +9,11 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-http": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-http": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "axios": "^0.19.0", "express": "^4.17.1" } diff --git a/getting-started/ts-example/package.json b/getting-started/ts-example/package.json index 20c0da256b..cb80ec9ead 100644 --- a/getting-started/ts-example/package.json +++ b/getting-started/ts-example/package.json @@ -13,14 +13,14 @@ "ts-node": "^8.6.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/exporter-prometheus": "^0.3.2", - "@opentelemetry/exporter-zipkin": "^0.3.2", - "@opentelemetry/metrics": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/plugin-http": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/exporter-prometheus": "^0.3.3", + "@opentelemetry/exporter-zipkin": "^0.3.3", + "@opentelemetry/metrics": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/plugin-http": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "axios": "^0.19.1", "express": "^4.17.1" } diff --git a/lerna.json b/lerna.json index 2af68777c0..80588a388b 100644 --- a/lerna.json +++ b/lerna.json @@ -6,7 +6,7 @@ "packages/*", "packages/opentelemetry-plugin-postgres/*" ], - "version": "0.3.2", + "version": "0.3.3", "changelog": { "repo": "open-telemetry/opentelemetry-js", "labels": { diff --git a/package.json b/package.json index 052b201c21..f2c261d056 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opentelemetry-base", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry is a distributed tracing and stats collection framework.", "main": "build/src/index.js", "types": "build/src/index.d.ts", diff --git a/packages/opentelemetry-base/package.json b/packages/opentelemetry-base/package.json index 5d5d79e1f2..927eaf09a7 100644 --- a/packages/opentelemetry-base/package.json +++ b/packages/opentelemetry-base/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/base", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry base provides base code for the SDK packages", "main": "build/src/index.js", "types": "build/src/index.d.ts", diff --git a/packages/opentelemetry-base/src/version.ts b/packages/opentelemetry-base/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-base/src/version.ts +++ b/packages/opentelemetry-base/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index a4fdab6e2c..a96e8caea4 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/core", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Core provides default and no-op implementations of the OpenTelemetry types for trace and metrics", "main": "build/src/index.js", "browser": { @@ -78,7 +78,7 @@ "webpack": "^4.35.2" }, "dependencies": { - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/types": "^0.3.3", "semver": "^6.3.0" } } diff --git a/packages/opentelemetry-core/src/version.ts b/packages/opentelemetry-core/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-core/src/version.ts +++ b/packages/opentelemetry-core/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-exporter-collector/package.json b/packages/opentelemetry-exporter-collector/package.json index 93a25ac5eb..7f978bfcb6 100644 --- a/packages/opentelemetry-exporter-collector/package.json +++ b/packages/opentelemetry-exporter-collector/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-collector", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Collector Exporter allows user to send collected traces to the OpenTelemetry Collector", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -79,9 +79,9 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.2", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2" + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3" } } diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-exporter-collector/src/version.ts +++ b/packages/opentelemetry-exporter-collector/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-exporter-jaeger/package.json b/packages/opentelemetry-exporter-jaeger/package.json index 6779c9a5cb..a1285e8b90 100644 --- a/packages/opentelemetry-exporter-jaeger/package.json +++ b/packages/opentelemetry-exporter-jaeger/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-jaeger", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Exporter Jaeger allows user to send collected traces to Jaeger", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -54,10 +54,10 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.2", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "jaeger-client": "^3.15.0" } } diff --git a/packages/opentelemetry-exporter-jaeger/src/version.ts b/packages/opentelemetry-exporter-jaeger/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-exporter-jaeger/src/version.ts +++ b/packages/opentelemetry-exporter-jaeger/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-exporter-prometheus/package.json b/packages/opentelemetry-exporter-prometheus/package.json index ab4e1cf4d8..b0d657da60 100644 --- a/packages/opentelemetry-exporter-prometheus/package.json +++ b/packages/opentelemetry-exporter-prometheus/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-prometheus", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Exporter Prometheus provides a metrics endpoint for Prometheus", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -53,10 +53,10 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.2", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/metrics": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/metrics": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "prom-client": "^11.5.3" } } diff --git a/packages/opentelemetry-exporter-prometheus/src/version.ts b/packages/opentelemetry-exporter-prometheus/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-exporter-prometheus/src/version.ts +++ b/packages/opentelemetry-exporter-prometheus/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-exporter-stackdriver-trace/package.json b/packages/opentelemetry-exporter-stackdriver-trace/package.json index a6d0e2b5d9..d914773f51 100644 --- a/packages/opentelemetry-exporter-stackdriver-trace/package.json +++ b/packages/opentelemetry-exporter-stackdriver-trace/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-stackdriver-trace", - "version": "0.3.1", + "version": "0.3.3", "description": "OpenTelemetry StackDriver Trace Exporter allows the user to send collected traces to StackDriver Trace.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -59,10 +59,10 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.1", - "@opentelemetry/core": "^0.3.1", - "@opentelemetry/tracing": "^0.3.1", - "@opentelemetry/types": "^0.3.1", + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "google-auth-library": "^5.7.0", "googleapis": "^46.0.0" } diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts index 60ca3ab548..d2d10b02a6 100644 --- a/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts +++ b/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.1'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-exporter-zipkin/package.json b/packages/opentelemetry-exporter-zipkin/package.json index b323a0dd9b..ba53d81b70 100644 --- a/packages/opentelemetry-exporter-zipkin/package.json +++ b/packages/opentelemetry-exporter-zipkin/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-zipkin", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Zipkin Exporter allows the user to send collected traces to Zipkin.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -55,9 +55,9 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.2", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2" + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3" } } diff --git a/packages/opentelemetry-exporter-zipkin/src/version.ts b/packages/opentelemetry-exporter-zipkin/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-exporter-zipkin/src/version.ts +++ b/packages/opentelemetry-exporter-zipkin/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-metrics/package.json b/packages/opentelemetry-metrics/package.json index 967fc53abe..6336ad4c9e 100644 --- a/packages/opentelemetry-metrics/package.json +++ b/packages/opentelemetry-metrics/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/metrics", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry metrics SDK", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -56,8 +56,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.2", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2" + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3" } } diff --git a/packages/opentelemetry-metrics/src/version.ts b/packages/opentelemetry-metrics/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-metrics/src/version.ts +++ b/packages/opentelemetry-metrics/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index cd8f8f8577..ab112d2f3a 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/node", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Node SDK provides automatic telemetry (tracing, metrics, etc) for Node.js applications", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -58,10 +58,10 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/scope-async-hooks": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/scope-async-hooks": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "require-in-the-middle": "^5.0.0", "semver": "^6.2.0" } diff --git a/packages/opentelemetry-node/src/version.ts b/packages/opentelemetry-node/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-node/src/version.ts +++ b/packages/opentelemetry-node/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-dns/package.json b/packages/opentelemetry-plugin-dns/package.json index 428b6dc127..453d64d186 100644 --- a/packages/opentelemetry-plugin-dns/package.json +++ b/packages/opentelemetry-plugin-dns/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-dns", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry dns automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -41,8 +41,8 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.7", "@types/node": "^12.7.12", "@types/semver": "^6.2.0", @@ -61,8 +61,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "semver": "^6.3.0", "shimmer": "^1.2.1" } diff --git a/packages/opentelemetry-plugin-dns/src/version.ts b/packages/opentelemetry-plugin-dns/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-dns/src/version.ts +++ b/packages/opentelemetry-plugin-dns/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-document-load/package.json b/packages/opentelemetry-plugin-document-load/package.json index 5c0baa62f7..d2a4116b97 100644 --- a/packages/opentelemetry-plugin-document-load/package.json +++ b/packages/opentelemetry-plugin-document-load/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-document-load", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry document-load automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -72,9 +72,9 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", - "@opentelemetry/web": "^0.3.2" + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", + "@opentelemetry/web": "^0.3.3" } } diff --git a/packages/opentelemetry-plugin-document-load/src/version.ts b/packages/opentelemetry-plugin-document-load/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-document-load/src/version.ts +++ b/packages/opentelemetry-plugin-document-load/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-grpc/package.json b/packages/opentelemetry-plugin-grpc/package.json index 5553e7fdfc..d8340c5fd3 100644 --- a/packages/opentelemetry-plugin-grpc/package.json +++ b/packages/opentelemetry-plugin-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-grpc", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry grpc automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -41,8 +41,8 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.7", "@types/node": "^12.6.9", "@types/semver": "^6.2.0", @@ -63,8 +63,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-grpc/src/version.ts b/packages/opentelemetry-plugin-grpc/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-grpc/src/version.ts +++ b/packages/opentelemetry-plugin-grpc/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-http/package.json b/packages/opentelemetry-plugin-http/package.json index 4ecd916145..adeee6a588 100644 --- a/packages/opentelemetry-plugin-http/package.json +++ b/packages/opentelemetry-plugin-http/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-http", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry http automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -41,9 +41,9 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/scope-base": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/scope-base": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/got": "^9.6.7", "@types/mocha": "^5.2.7", "@types/nock": "^11.1.0", @@ -72,8 +72,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "semver": "^6.3.0", "shimmer": "^1.2.1" } diff --git a/packages/opentelemetry-plugin-http/src/version.ts b/packages/opentelemetry-plugin-http/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-http/src/version.ts +++ b/packages/opentelemetry-plugin-http/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-https/package.json b/packages/opentelemetry-plugin-https/package.json index 512779adff..d4ef6e79b9 100644 --- a/packages/opentelemetry-plugin-https/package.json +++ b/packages/opentelemetry-plugin-https/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-https", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry https automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -41,9 +41,9 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/scope-base": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/scope-base": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/got": "^9.6.7", "@types/mocha": "^5.2.7", "@types/nock": "^11.1.0", @@ -72,9 +72,9 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/plugin-http": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/plugin-http": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "semver": "^6.3.0", "shimmer": "^1.2.1" } diff --git a/packages/opentelemetry-plugin-https/src/version.ts b/packages/opentelemetry-plugin-https/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-https/src/version.ts +++ b/packages/opentelemetry-plugin-https/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-ioredis/package.json b/packages/opentelemetry-plugin-ioredis/package.json index e226ec68f5..ec16e8cd3c 100644 --- a/packages/opentelemetry-plugin-ioredis/package.json +++ b/packages/opentelemetry-plugin-ioredis/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-ioredis", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry ioredis automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -44,9 +44,9 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/test-utils": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/test-utils": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/ioredis": "^4.14.3", "@types/mocha": "^5.2.7", "@types/node": "^13.1.0", @@ -65,8 +65,8 @@ "typescript": "3.7.4" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-ioredis/src/version.ts b/packages/opentelemetry-plugin-ioredis/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-ioredis/src/version.ts +++ b/packages/opentelemetry-plugin-ioredis/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-mongodb/package.json b/packages/opentelemetry-plugin-mongodb/package.json index 3adcd2db51..a1cfd453f2 100644 --- a/packages/opentelemetry-plugin-mongodb/package.json +++ b/packages/opentelemetry-plugin-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-mongodb", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry mongodb automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -41,8 +41,8 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.7", "@types/mongodb": "^3.2.3", "@types/node": "^12.7.2", @@ -60,8 +60,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-mongodb/src/version.ts b/packages/opentelemetry-plugin-mongodb/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-mongodb/src/version.ts +++ b/packages/opentelemetry-plugin-mongodb/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-mysql/package.json b/packages/opentelemetry-plugin-mysql/package.json index 119acf60b4..8cf5529ced 100644 --- a/packages/opentelemetry-plugin-mysql/package.json +++ b/packages/opentelemetry-plugin-mysql/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-mysql", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry mysql automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -41,9 +41,9 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/test-utils": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/test-utils": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.7", "@types/mysql": "^2.15.4", "@types/node": "^12.6.9", @@ -61,8 +61,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-mysql/src/version.ts b/packages/opentelemetry-plugin-mysql/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-mysql/src/version.ts +++ b/packages/opentelemetry-plugin-mysql/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json index 0e11029dcd..03058deffd 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-pg-pool", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry postgres pool automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -46,8 +46,8 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/plugin-pg": "^0.3.2", - "@opentelemetry/test-utils": "^0.3.2", + "@opentelemetry/plugin-pg": "^0.3.3", + "@opentelemetry/test-utils": "^0.3.3", "@types/mocha": "^5.2.7", "@types/node": "^12.6.9", "@types/pg": "^7.11.2", @@ -68,10 +68,10 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/version.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/version.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json index d21f8ef29e..eecd8bc925 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-pg", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry postgres automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -45,9 +45,9 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/test-utils": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/test-utils": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.7", "@types/node": "^12.6.9", "@types/pg": "^7.11.2", @@ -65,8 +65,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/version.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/version.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-redis/package.json b/packages/opentelemetry-plugin-redis/package.json index e3dd7db93f..675b5afdb3 100644 --- a/packages/opentelemetry-plugin-redis/package.json +++ b/packages/opentelemetry-plugin-redis/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-redis", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry redis automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -43,9 +43,9 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/node": "^0.3.2", - "@opentelemetry/test-utils": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/node": "^0.3.3", + "@opentelemetry/test-utils": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.7", "@types/node": "^12.6.9", "@types/redis": "^2.8.14", @@ -64,8 +64,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-redis/src/version.ts b/packages/opentelemetry-plugin-redis/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-redis/src/version.ts +++ b/packages/opentelemetry-plugin-redis/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-plugin-xml-http-request/package.json b/packages/opentelemetry-plugin-xml-http-request/package.json index 099aa10535..3f7124d397 100644 --- a/packages/opentelemetry-plugin-xml-http-request/package.json +++ b/packages/opentelemetry-plugin-xml-http-request/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/plugin-xml-http-request", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry XMLHttpRequest automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -44,8 +44,8 @@ }, "devDependencies": { "@babel/core": "^7.6.0", - "@opentelemetry/scope-zone": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/scope-zone": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.5", "@types/node": "^12.6.8", "@types/shimmer": "^1.0.1", @@ -76,10 +76,10 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.2", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", - "@opentelemetry/web": "^0.3.2", + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", + "@opentelemetry/web": "^0.3.3", "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-xml-http-request/src/version.ts b/packages/opentelemetry-plugin-xml-http-request/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-plugin-xml-http-request/src/version.ts +++ b/packages/opentelemetry-plugin-xml-http-request/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-propagator-jaeger/package.json b/packages/opentelemetry-propagator-jaeger/package.json index fd7d45f807..7d77e5a5f2 100644 --- a/packages/opentelemetry-propagator-jaeger/package.json +++ b/packages/opentelemetry-propagator-jaeger/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/propagator-jaeger", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Jaeger propagator provides HTTP header propagation for systems that are using Jaeger HTTP header format.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -70,6 +70,6 @@ "webpack": "^4.35.2" }, "dependencies": { - "@opentelemetry/types": "^0.3.2" + "@opentelemetry/types": "^0.3.3" } } diff --git a/packages/opentelemetry-propagator-jaeger/src/version.ts b/packages/opentelemetry-propagator-jaeger/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-propagator-jaeger/src/version.ts +++ b/packages/opentelemetry-propagator-jaeger/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-scope-async-hooks/package.json b/packages/opentelemetry-scope-async-hooks/package.json index 2cbaf8b53d..0928607c9d 100644 --- a/packages/opentelemetry-scope-async-hooks/package.json +++ b/packages/opentelemetry-scope-async-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/scope-async-hooks", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry AsyncHooks-based Scope Manager", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -56,6 +56,6 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/scope-base": "^0.3.2" + "@opentelemetry/scope-base": "^0.3.3" } } diff --git a/packages/opentelemetry-scope-async-hooks/src/version.ts b/packages/opentelemetry-scope-async-hooks/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-scope-async-hooks/src/version.ts +++ b/packages/opentelemetry-scope-async-hooks/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-scope-base/package.json b/packages/opentelemetry-scope-base/package.json index b85877885a..c0a3f87bd6 100644 --- a/packages/opentelemetry-scope-base/package.json +++ b/packages/opentelemetry-scope-base/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/scope-base", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Base Scope Manager", "main": "build/src/index.js", "types": "build/src/index.d.ts", diff --git a/packages/opentelemetry-scope-base/src/version.ts b/packages/opentelemetry-scope-base/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-scope-base/src/version.ts +++ b/packages/opentelemetry-scope-base/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-scope-zone-peer-dep/package.json b/packages/opentelemetry-scope-zone-peer-dep/package.json index 5c8e3b24f9..48d3cc7b1e 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/package.json +++ b/packages/opentelemetry-scope-zone-peer-dep/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/scope-zone-peer-dep", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Scope Zone with peer dependency for zone.js", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -72,7 +72,7 @@ "zone.js": "^0.10.2" }, "dependencies": { - "@opentelemetry/scope-base": "^0.3.2" + "@opentelemetry/scope-base": "^0.3.3" }, "peerDependencies": { "zone.js": "^0.10.2" diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/version.ts b/packages/opentelemetry-scope-zone-peer-dep/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/version.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-scope-zone/package.json b/packages/opentelemetry-scope-zone/package.json index d8789b6967..f1cfd53956 100644 --- a/packages/opentelemetry-scope-zone/package.json +++ b/packages/opentelemetry-scope-zone/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/scope-zone", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Scope Zone", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -66,7 +66,7 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@opentelemetry/scope-zone-peer-dep": "^0.3.2", + "@opentelemetry/scope-zone-peer-dep": "^0.3.3", "zone.js": "^0.10.2" }, "sideEffects": true diff --git a/packages/opentelemetry-scope-zone/src/version.ts b/packages/opentelemetry-scope-zone/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-scope-zone/src/version.ts +++ b/packages/opentelemetry-scope-zone/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-shim-opentracing/package.json b/packages/opentelemetry-shim-opentracing/package.json index a36d7a53a7..c931c3c376 100644 --- a/packages/opentelemetry-shim-opentracing/package.json +++ b/packages/opentelemetry-shim-opentracing/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/shim-opentracing", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTracing to OpenTelemetry shim", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -39,7 +39,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/tracing": "^0.3.2", + "@opentelemetry/tracing": "^0.3.3", "@types/mocha": "^5.2.7", "@types/node": "^12.6.9", "codecov": "^3.6.1", @@ -54,8 +54,8 @@ "typescript": "3.7.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "opentracing": "^0.14.4" } } diff --git a/packages/opentelemetry-shim-opentracing/src/version.ts b/packages/opentelemetry-shim-opentracing/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-shim-opentracing/src/version.ts +++ b/packages/opentelemetry-shim-opentracing/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-test-utils/package.json b/packages/opentelemetry-test-utils/package.json index ba130f1a4b..b79f45943e 100644 --- a/packages/opentelemetry-test-utils/package.json +++ b/packages/opentelemetry-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/test-utils", - "version": "0.3.2", + "version": "0.3.3", "description": "Test utilities.", "main": "build/testUtils.js", "scripts": { @@ -22,9 +22,9 @@ }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", "devDependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3", "gts": "^1.1.2", "ts-node": "^8.6.2", "tslint-consistent-codestyle": "^1.16.0", diff --git a/packages/opentelemetry-tracing/package.json b/packages/opentelemetry-tracing/package.json index c6c98f187e..179ca5d330 100644 --- a/packages/opentelemetry-tracing/package.json +++ b/packages/opentelemetry-tracing/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/tracing", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Tracing", "main": "build/src/index.js", "browser": { @@ -73,9 +73,9 @@ "webpack": "^4.35.2" }, "dependencies": { - "@opentelemetry/base": "^0.3.2", - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/scope-base": "^0.3.2", - "@opentelemetry/types": "^0.3.2" + "@opentelemetry/base": "^0.3.3", + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/scope-base": "^0.3.3", + "@opentelemetry/types": "^0.3.3" } } diff --git a/packages/opentelemetry-tracing/src/version.ts b/packages/opentelemetry-tracing/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-tracing/src/version.ts +++ b/packages/opentelemetry-tracing/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-types/package.json b/packages/opentelemetry-types/package.json index 82466c636b..8c8ae8bd88 100644 --- a/packages/opentelemetry-types/package.json +++ b/packages/opentelemetry-types/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/types", - "version": "0.3.2", + "version": "0.3.3", "description": "TypeScript types for OpenTelemetry", "main": "build/src/index.js", "types": "build/src/index.d.ts", diff --git a/packages/opentelemetry-types/src/version.ts b/packages/opentelemetry-types/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-types/src/version.ts +++ b/packages/opentelemetry-types/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; diff --git a/packages/opentelemetry-web/package.json b/packages/opentelemetry-web/package.json index e49f917474..968a5e91b7 100644 --- a/packages/opentelemetry-web/package.json +++ b/packages/opentelemetry-web/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/web", - "version": "0.3.2", + "version": "0.3.3", "description": "OpenTelemetry Web Tracer", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -43,7 +43,7 @@ }, "devDependencies": { "@babel/core": "^7.6.0", - "@opentelemetry/scope-zone": "^0.3.2", + "@opentelemetry/scope-zone": "^0.3.3", "@types/mocha": "^5.2.5", "@types/node": "^12.6.8", "@types/sinon": "^7.0.13", @@ -73,9 +73,9 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@opentelemetry/core": "^0.3.2", - "@opentelemetry/scope-base": "^0.3.2", - "@opentelemetry/tracing": "^0.3.2", - "@opentelemetry/types": "^0.3.2" + "@opentelemetry/core": "^0.3.3", + "@opentelemetry/scope-base": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", + "@opentelemetry/types": "^0.3.3" } } diff --git a/packages/opentelemetry-web/src/version.ts b/packages/opentelemetry-web/src/version.ts index 2efbb00dcb..d2d10b02a6 100644 --- a/packages/opentelemetry-web/src/version.ts +++ b/packages/opentelemetry-web/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.3.2'; +export const VERSION = '0.3.3'; From 14e4ef461a377a076ecaa2561f042e65d89c924f Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Thu, 23 Jan 2020 08:08:23 -0800 Subject: [PATCH 16/21] Post Release: update CHANGELOG.md (#725) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8b7e8c2d2..873d464157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file. ## Unreleased +## 0.3.3 + +Released 2020-01-22 + +#### :rocket: (Enhancement) +* `opentelemetry-core`, `opentelemetry-exporter-collector`, `opentelemetry-exporter-zipkin`, `opentelemetry-node`, `opentelemetry-plugin-dns`, `opentelemetry-plugin-document-load`, `opentelemetry-plugin-grpc`, `opentelemetry-plugin-http`, `opentelemetry-plugin-https`, `opentelemetry-plugin-ioredis`, `opentelemetry-plugin-mongodb`, `opentelemetry-plugin-mysql`, `opentelemetry-plugin-postgres`, `opentelemetry-plugin-redis`, `opentelemetry-plugin-xml-http-request`, `opentelemetry-shim-opentracing`, `opentelemetry-tracing`, `opentelemetry-types`, `opentelemetry-web` + * [#582](https://github.com/open-telemetry/opentelemetry-js/pull/582) Named Tracers / Tracer Registry +* `opentelemetry-node`, `opentelemetry-plugin-postgres` + * [#662](https://github.com/open-telemetry/opentelemetry-js/pull/662) feat: add pg-pool to default list of instrumented plugins + * [#708](https://github.com/open-telemetry/opentelemetry-js/pull/708) Simplify and speed up trace context parsing +* `opentelemetry-metrics` + * [#700](https://github.com/open-telemetry/opentelemetry-js/pull/700) implement named meter + +#### :sparkles: (Feature) +* `opentelemetry-propagator-jaeger` + * [#701](https://github.com/open-telemetry/opentelemetry-js/pull/701) add jaeger http trace format +* `opentelemetry-exporter-stackdriver-trace` + * [#648](https://github.com/open-telemetry/opentelemetry-js/pull/648) Stackdriver Trace exporter + +#### :books: (Refine Doc) + * [#673](https://github.com/open-telemetry/opentelemetry-js/pull/673) chore(getting-started): Added a TypeScript version for Getting Started Guide + +#### :bug: (Bug Fix) +* `opentelemetry-plugin-ioredis` + * [#714](https://github.com/open-telemetry/opentelemetry-js/pull/714) fix: return module exports from ioredis + ## 0.3.2 Released 2020-01-03 From 230842f0e1b25ffa5e530088a8a4b7fb346fff23 Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Thu, 23 Jan 2020 13:21:58 -0800 Subject: [PATCH 17/21] chore: remove redundant statement (#726) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index f295b311b3..525e3535db 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ For detailed documentation of the OpenTelemetry API, see [the documentation][doc To get started tracing your own application, see the [Getting Started Guide](getting-started/README.md). For more information about automatic instrumentation see [@opentelemetry/node][otel-node], which provides auto-instrumentation for Node.js applications. If the automatic instrumentation does not suit your needs, or you would like to create manual traces, see [@opentelemetry/tracing][otel-tracing] -For more information, see [@opentelemetry/tracing][otel-tracing] for an SDK that supports creating traces through manual instrumentation or [@opentelemetry/node][otel-node] which provides auto-instrumentation for Node.js applications. - ### Library Author If you are a library author looking to build OpenTelemetry into your library, please see [the documentation][docs]. As a library author, it is important that you only depend on properties and methods published on the public API. If you use any properties or methods from the SDK that are not officially a part of the public API, your library may break if an [Application Owner](#application-owner) uses a different SDK implementation. From d7f4fe29db6c5fb431fa26816541eb46fb1567d9 Mon Sep 17 00:00:00 2001 From: Naseem Date: Fri, 24 Jan 2020 08:03:04 -0500 Subject: [PATCH 18/21] feat: lint examples (#649) * refactor: use a single eslintrc for all examples folders furthermore, override the strict rule that ships with airbnb * fix: turn off no-use-before-define * fix: install eslint in ci container Co-Authored-By: Daniel Dyla * fix: ignore uninstalled packages lint errors We will not want to install all examples in CI Co-authored-by: Daniel Dyla Co-authored-by: Mayur Kale --- .circleci/config.yml | 6 + examples/.eslintrc | 16 +++ examples/basic-tracer-node/index.js | 22 ++-- examples/basic-tracer-node/multi_exporter.js | 31 +++-- examples/dns/client.js | 56 ++++---- examples/dns/setup.js | 39 ------ examples/dns/tracer.js | 40 ++++++ examples/grpc/client.js | 21 +-- examples/grpc/helloworld_grpc_pb.js | 25 +++- examples/grpc/helloworld_pb.js | 121 +++++++++-------- examples/grpc/server.js | 19 +-- examples/grpc/{setup.js => tracer.js} | 18 +-- .../grpc_dynamic_codegen/capitalize_client.js | 21 +-- .../grpc_dynamic_codegen/capitalize_server.js | 23 ++-- .../{setup.js => tracer.js} | 17 +-- examples/http/client.js | 58 ++++---- examples/http/server.js | 27 ++-- examples/{redis/setup.js => http/tracer.js} | 11 +- examples/https/client.js | 55 ++++---- examples/https/server.js | 27 ++-- examples/https/{setup.js => tracer.js} | 12 +- examples/mysql/client.js | 124 ++++++++---------- examples/mysql/server.js | 104 +++++++-------- examples/mysql/{setup.js => tracer.js} | 18 +-- examples/opentracing-shim/client.js | 34 ++--- examples/opentracing-shim/package.json | 1 + examples/opentracing-shim/server.js | 34 ++--- examples/opentracing-shim/shim.js | 18 +-- examples/opentracing-shim/utils.js | 4 +- examples/postgres/client.js | 45 +++---- examples/postgres/server.js | 20 +-- examples/postgres/setupPsql.js | 37 +++--- examples/postgres/{setup.js => tracer.js} | 28 ++-- examples/prometheus/index.js | 32 ++--- examples/redis/client.js | 40 +++--- examples/redis/express-tracer-handlers.js | 26 ++-- examples/redis/server.js | 23 ++-- examples/redis/setup-redis.js | 6 +- examples/{http/setup.js => redis/tracer.js} | 12 +- examples/stackdriver-trace/index.js | 20 +-- examples/tracer-web/.eslintrc | 16 +++ .../examples/document-load/index.js | 55 ++++---- .../examples/xml-http-request/index.js | 42 +++--- examples/tracer-web/webpack.config.js | 31 ++--- package.json | 7 +- 45 files changed, 727 insertions(+), 715 deletions(-) create mode 100644 examples/.eslintrc delete mode 100644 examples/dns/setup.js create mode 100644 examples/dns/tracer.js rename examples/grpc/{setup.js => tracer.js} (72%) rename examples/grpc_dynamic_codegen/{setup.js => tracer.js} (82%) rename examples/{redis/setup.js => http/tracer.js} (83%) rename examples/https/{setup.js => tracer.js} (84%) rename examples/mysql/{setup.js => tracer.js} (75%) rename examples/postgres/{setup.js => tracer.js} (68%) rename examples/{http/setup.js => redis/tracer.js} (83%) create mode 100644 examples/tracer-web/.eslintrc diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f7a3a4598..c9017c895a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -172,6 +172,12 @@ jobs: root: packages/opentelemetry-types/docs paths: - out + - run: + name: Install minimal modules to lint examples + command: npm i -D eslint eslint-plugin-import eslint-config-airbnb-base + - run: + name: Lint examples + command: npm run lint-examples docs-deploy: docker: - image: node:12 diff --git a/examples/.eslintrc b/examples/.eslintrc new file mode 100644 index 0000000000..5feabb97f5 --- /dev/null +++ b/examples/.eslintrc @@ -0,0 +1,16 @@ +{ + "env": { + "node": true + }, + "extends": "airbnb-base", + "parserOptions": { + "sourceType": "script" + }, + "rules": { + "strict": ["error", "global"], + "no-use-before-define": ["error", "nofunc"], + "no-console": "off", + "import/no-unresolved": "off", + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] + } +} diff --git a/examples/basic-tracer-node/index.js b/examples/basic-tracer-node/index.js index 086d1fe158..e16b95b898 100644 --- a/examples/basic-tracer-node/index.js +++ b/examples/basic-tracer-node/index.js @@ -1,11 +1,13 @@ +'use strict'; + const opentelemetry = require('@opentelemetry/core'); const { BasicTracerRegistry, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); -const { CollectorExporter } = require('@opentelemetry/exporter-collector'); +const { CollectorExporter } = require('@opentelemetry/exporter-collector'); const options = { - serviceName: 'basic-service' + serviceName: 'basic-service', }; // Initialize an exporter depending on how we were started @@ -27,15 +29,15 @@ registry.addSpanProcessor(new SimpleSpanProcessor(exporter)); // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -const tracer = opentelemetry.getTracer('example-basic-tracer-node') +const tracer = opentelemetry.getTracer('example-basic-tracer-node'); // Create a span. A span must be closed. -const span = tracer.startSpan('main'); -for (let i = 0; i < 10; i++) { - doWork(span); +const parentSpan = tracer.startSpan('main'); +for (let i = 0; i < 10; i += 1) { + doWork(parentSpan); } // Be sure to end the span. -span.end(); +parentSpan.end(); // flush and close the connection. exporter.shutdown(); @@ -44,11 +46,13 @@ function doWork(parent) { // Start another span. In this example, the main method already started a // span, so that'll be the parent span, and this will be a child span. const span = tracer.startSpan('doWork', { - parent: parent + parent, }); // simulate some random work. - for (let i = 0; i <= Math.floor(Math.random() * 40000000); i++) { } + for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) { + // empty + } // Set attributes to the span. span.setAttribute('key', 'value'); diff --git a/examples/basic-tracer-node/multi_exporter.js b/examples/basic-tracer-node/multi_exporter.js index 309daf4311..aa8478dfa2 100644 --- a/examples/basic-tracer-node/multi_exporter.js +++ b/examples/basic-tracer-node/multi_exporter.js @@ -1,39 +1,44 @@ +'use strict'; + const opentelemetry = require('@opentelemetry/core'); const { BasicTracerRegistry, BatchSpanProcessor, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); -const { CollectorExporter } = require('@opentelemetry/exporter-collector'); +const { CollectorExporter } = require('@opentelemetry/exporter-collector'); const registry = new BasicTracerRegistry(); -const zipkinExporter = new ZipkinExporter({serviceName: 'basic-service'}); +const zipkinExporter = new ZipkinExporter({ serviceName: 'basic-service' }); const jaegerExporter = new JaegerExporter({ serviceName: 'basic-service', }); -const collectorExporter = new CollectorExporter({serviceName: 'basic-service'}); +const collectorExporter = new CollectorExporter({ serviceName: 'basic-service' }); // It is recommended to use this BatchSpanProcessor for better performance // and optimization, especially in production. registry.addSpanProcessor(new BatchSpanProcessor(zipkinExporter, { - bufferSize: 10 // This is added for example, default size is 100. + // This is added for example, default size is 100. + bufferSize: 10, })); + +const tracer = opentelemetry.getTracer('default'); + tracer.addSpanProcessor(new BatchSpanProcessor(jaegerExporter), { - bufferSize: 10 + bufferSize: 10, }); registry.addSpanProcessor(new SimpleSpanProcessor(collectorExporter)); // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -const tracer = opentelemetry.getTracer('default'); // Create a span. A span must be closed. -const span = tracer.startSpan('main'); -for (let i = 0; i < 10; i++) { - doWork(span); +const parentSpan = tracer.startSpan('main'); +for (let i = 0; i < 10; i += 1) { + doWork(parentSpan); } // Be sure to end the span. -span.end(); +parentSpan.end(); // flush and close the connection. zipkinExporter.shutdown(); @@ -44,11 +49,13 @@ function doWork(parent) { // Start another span. In this example, the main method already started a // span, so that'll be the parent span, and this will be a child span. const span = tracer.startSpan('doWork', { - parent: parent + parent, }); // simulate some random work. - for (let i = 0; i <= Math.floor(Math.random() * 40000000); i++) { } + for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) { + // empty + } // Set attributes to the span. span.setAttribute('key', 'value'); diff --git a/examples/dns/client.js b/examples/dns/client.js index 317b5b9e46..1ea52d6ce0 100644 --- a/examples/dns/client.js +++ b/examples/dns/client.js @@ -1,42 +1,34 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); - -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (DNS in this case). - */ -config.setupTracerAndExporters('dns-client-service'); - +const tracer = require('./tracer')('example-dns'); +// eslint-disable-next-line import/order const dns = require('dns').promises; -const tracer = opentelemetry.getTracer('example-dns'); /** A function which makes a dns lookup and handles response. */ function makeLookup() { - // span corresponds to dns lookup. Here, we have manually created - // the span, which is created to track work that happens outside of the - // dns lookup query. - const span = tracer.startSpan('dnsLookup'); - tracer.withSpan(span, async () => { - try { - await dns.lookup('montreal.ca'); - } catch (error) { - span.setAttributes({ - 'error.name': error.name, - 'error.message': error.message - }); - }finally{ - console.log(`traceid: ${span.context().traceId}`); - span.end(); - } - }); + // span corresponds to dns lookup. Here, we have manually created + // the span, which is created to track work that happens outside of the + // dns lookup query. + const span = tracer.startSpan('dnsLookup'); + tracer.withSpan(span, async () => { + try { + await dns.lookup('montreal.ca'); + } catch (error) { + span.setAttributes({ + 'error.name': error.name, + 'error.message': error.message, + }); + } finally { + console.log(`traceid: ${span.context().traceId}`); + span.end(); + } + }); - // The process must live for at least the interval past any traces that - // must be exported, or some risk being lost if they are recorded after the - // last export. - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') - setTimeout(() => { console.log('Completed.'); }, 5000); + // The process must live for at least the interval past any traces that + // must be exported, or some risk being lost if they are recorded after the + // last export. + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); + setTimeout(() => { console.log('Completed.'); }, 5000); } makeLookup(); diff --git a/examples/dns/setup.js b/examples/dns/setup.js deleted file mode 100644 index f5ef407f2a..0000000000 --- a/examples/dns/setup.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const opentelemetry = require('@opentelemetry/core'); -const { NodeTracerRegistry } = require('@opentelemetry/node'); -const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); -const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); -const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); -const EXPORTER = process.env.EXPORTER || ''; - -function setupTracerAndExporters(service) { - const registry = new NodeTracerRegistry({ -      plugins: { -          dns: { -            enabled: true, -            path: '@opentelemetry/plugin-dns', - // Avoid dns lookup loop with http zipkin calls - ignoreHostnames: ['localhost'] -        } -      } -  }); - - let exporter; - if (EXPORTER.toLowerCase().startsWith('z')) { - exporter = new ZipkinExporter({ - serviceName: service, - }); - } else { - exporter = new JaegerExporter({ - serviceName: service, - }); - } - - registry.addSpanProcessor(new SimpleSpanProcessor(exporter)); - - // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings - opentelemetry.initGlobalTracerRegistry(registry); -} - -exports.setupTracerAndExporters = setupTracerAndExporters; diff --git a/examples/dns/tracer.js b/examples/dns/tracer.js new file mode 100644 index 0000000000..7a918ec33d --- /dev/null +++ b/examples/dns/tracer.js @@ -0,0 +1,40 @@ +'use strict'; + +const opentelemetry = require('@opentelemetry/core'); +const { NodeTracerRegistry } = require('@opentelemetry/node'); +const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); +const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); +const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); + +const EXPORTER = process.env.EXPORTER || ''; + +module.exports = (serviceName) => { + const registry = new NodeTracerRegistry({ + plugins: { + dns: { + enabled: true, + path: '@opentelemetry/plugin-dns', + // Avoid dns lookup loop with http zipkin calls + ignoreHostnames: ['localhost'], + }, + }, + }); + + let exporter; + if (EXPORTER.toLowerCase().startsWith('z')) { + exporter = new ZipkinExporter({ + serviceName, + }); + } else { + exporter = new JaegerExporter({ + serviceName, + }); + } + + registry.addSpanProcessor(new SimpleSpanProcessor(exporter)); + + // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings + opentelemetry.initGlobalTracerRegistry(registry); + + return opentelemetry.getTracer(); +}; diff --git a/examples/grpc/client.js b/examples/grpc/client.js index 4bc65bc976..5541113af7 100644 --- a/examples/grpc/client.js +++ b/examples/grpc/client.js @@ -1,20 +1,12 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); - -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (gRPC in this case). - */ -config.setupTracerAndExporters('grpc-client-service'); - +const tracer = require('./tracer')('example-grpc-client'); +// eslint-disable-next-line import/order const grpc = require('grpc'); - const messages = require('./helloworld_pb'); const services = require('./helloworld_grpc_pb'); + const PORT = 50051; -const tracer = opentelemetry.getTracer('example-grpc-client'); /** A function which makes requests and handles response. */ function main() { @@ -26,17 +18,18 @@ function main() { console.log('Client traceId ', span.context().traceId); const client = new services.GreeterClient( `localhost:${PORT}`, - grpc.credentials.createInsecure() + grpc.credentials.createInsecure(), ); const request = new messages.HelloRequest(); let user; if (process.argv.length >= 3) { + // eslint-disable-next-line prefer-destructuring user = process.argv[2]; } else { user = 'world'; } request.setName(user); - client.sayHello(request, function(err, response) { + client.sayHello(request, (err, response) => { span.end(); if (err) throw err; console.log('Greeting:', response.getMessage()); @@ -46,7 +39,7 @@ function main() { // The process must live for at least the interval past any traces that // must be exported, or some risk being lost if they are recorded after the // last export. - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); setTimeout(() => { console.log('Completed.'); }, 5000); } diff --git a/examples/grpc/helloworld_grpc_pb.js b/examples/grpc/helloworld_grpc_pb.js index e0eed2ea25..c32727f8da 100644 --- a/examples/grpc/helloworld_grpc_pb.js +++ b/examples/grpc/helloworld_grpc_pb.js @@ -1,3 +1,14 @@ +/* eslint-disable no-multi-assign */ +/* eslint-disable prefer-const */ +/* eslint-disable no-var */ +/* eslint-disable vars-on-top */ +/* eslint-disable no-unused-vars */ +/* eslint-disable func-names */ +/* eslint-disable camelcase */ +/* eslint-disable no-undef */ +/* eslint-disable no-new-func */ +/* eslint-disable strict */ + // GENERATED CODE -- DO NOT EDIT! // Original file comments: @@ -15,9 +26,11 @@ // See the License for the specific language governing permissions and // limitations under the License. // + 'use strict'; -var grpc = require('grpc'); -var helloworld_pb = require('./helloworld_pb.js'); + +const grpc = require('grpc'); +const helloworld_pb = require('./helloworld_pb.js'); function serialize_HelloReply(arg) { if (!(arg instanceof helloworld_pb.HelloReply)) { @@ -39,12 +52,12 @@ function serialize_HelloRequest(arg) { function deserialize_HelloRequest(buffer_arg) { return helloworld_pb.HelloRequest.deserializeBinary( - new Uint8Array(buffer_arg) + new Uint8Array(buffer_arg), ); } // The greeting service definition. -var GreeterService = (exports.GreeterService = { +const GreeterService = (exports.GreeterService = { // Sends a greeting sayHello: { path: '/helloworld.Greeter/SayHello', @@ -55,8 +68,8 @@ var GreeterService = (exports.GreeterService = { requestSerialize: serialize_HelloRequest, requestDeserialize: deserialize_HelloRequest, responseSerialize: serialize_HelloReply, - responseDeserialize: deserialize_HelloReply - } + responseDeserialize: deserialize_HelloReply, + }, }); exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService); diff --git a/examples/grpc/helloworld_pb.js b/examples/grpc/helloworld_pb.js index 1f69378fac..48656aef94 100644 --- a/examples/grpc/helloworld_pb.js +++ b/examples/grpc/helloworld_pb.js @@ -1,3 +1,13 @@ +/* eslint-disable prefer-const */ +/* eslint-disable no-var */ +/* eslint-disable vars-on-top */ +/* eslint-disable no-unused-vars */ +/* eslint-disable func-names */ +/* eslint-disable camelcase */ +/* eslint-disable no-undef */ +/* eslint-disable no-new-func */ +/* eslint-disable strict */ + /** * @fileoverview * @enhanceable @@ -5,9 +15,10 @@ */ // GENERATED CODE -- DO NOT EDIT! -var jspb = require('google-protobuf'); -var goog = jspb; -var global = Function('return this')(); +const jspb = require('google-protobuf'); + +const goog = jspb; +const global = Function('return this')(); goog.exportSymbol('proto.helloworld.HelloReply', null, global); goog.exportSymbol('proto.helloworld.HelloRequest', null, global); @@ -22,7 +33,7 @@ goog.exportSymbol('proto.helloworld.HelloRequest', null, global); * @extends {jspb.Message} * @constructor */ -proto.helloworld.HelloRequest = function(opt_data) { +proto.helloworld.HelloRequest = function (opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; goog.inherits(proto.helloworld.HelloRequest, jspb.Message); @@ -41,8 +52,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * for transitional soy proto support: http://goto/soy-param-migration * @return {!Object} */ - proto.helloworld.HelloRequest.prototype.toObject = function( - opt_includeInstance + proto.helloworld.HelloRequest.prototype.toObject = function ( + opt_includeInstance, ) { return proto.helloworld.HelloRequest.toObject(opt_includeInstance, this); }; @@ -55,11 +66,13 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * @param {!proto.helloworld.HelloRequest} msg The msg instance to transform. * @return {!Object} */ - proto.helloworld.HelloRequest.toObject = function(includeInstance, msg) { - var f, - obj = { - name: msg.getName() - }; + proto.helloworld.HelloRequest.toObject = function (includeInstance, msg) { + let f; + + + const obj = { + name: msg.getName(), + }; if (includeInstance) { obj.$jspbMessageInstance = msg; @@ -73,9 +86,9 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * @param {jspb.ByteSource} bytes The bytes to deserialize. * @return {!proto.helloworld.HelloRequest} */ -proto.helloworld.HelloRequest.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.helloworld.HelloRequest(); +proto.helloworld.HelloRequest.deserializeBinary = function (bytes) { + const reader = new jspb.BinaryReader(bytes); + const msg = new proto.helloworld.HelloRequest(); return proto.helloworld.HelloRequest.deserializeBinaryFromReader(msg, reader); }; @@ -86,15 +99,15 @@ proto.helloworld.HelloRequest.deserializeBinary = function(bytes) { * @param {!jspb.BinaryReader} reader The BinaryReader to use. * @return {!proto.helloworld.HelloRequest} */ -proto.helloworld.HelloRequest.deserializeBinaryFromReader = function( +proto.helloworld.HelloRequest.deserializeBinaryFromReader = function ( msg, - reader + reader, ) { while (reader.nextField()) { if (reader.isEndGroup()) { break; } - var field = reader.getFieldNumber(); + const field = reader.getFieldNumber(); switch (field) { case 1: var value = /** @type {string} */ (reader.readString()); @@ -114,9 +127,9 @@ proto.helloworld.HelloRequest.deserializeBinaryFromReader = function( * @param {!proto.helloworld.HelloRequest} message * @param {!jspb.BinaryWriter} writer */ -proto.helloworld.HelloRequest.serializeBinaryToWriter = function( +proto.helloworld.HelloRequest.serializeBinaryToWriter = function ( message, - writer + writer, ) { message.serializeBinaryToWriter(writer); }; @@ -125,8 +138,8 @@ proto.helloworld.HelloRequest.serializeBinaryToWriter = function( * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.helloworld.HelloRequest.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); +proto.helloworld.HelloRequest.prototype.serializeBinary = function () { + const writer = new jspb.BinaryWriter(); this.serializeBinaryToWriter(writer); return writer.getResultBuffer(); }; @@ -136,10 +149,10 @@ proto.helloworld.HelloRequest.prototype.serializeBinary = function() { * writing to the given BinaryWriter. * @param {!jspb.BinaryWriter} writer */ -proto.helloworld.HelloRequest.prototype.serializeBinaryToWriter = function( - writer +proto.helloworld.HelloRequest.prototype.serializeBinaryToWriter = function ( + writer, ) { - var f = undefined; + let f; f = this.getName(); if (f.length > 0) { writer.writeString(1, f); @@ -150,9 +163,9 @@ proto.helloworld.HelloRequest.prototype.serializeBinaryToWriter = function( * Creates a deep clone of this proto. No data is shared with the original. * @return {!proto.helloworld.HelloRequest} The clone. */ -proto.helloworld.HelloRequest.prototype.cloneMessage = function() { +proto.helloworld.HelloRequest.prototype.cloneMessage = function () { return /** @type {!proto.helloworld.HelloRequest} */ (jspb.Message.cloneMessage( - this + this, )); }; @@ -160,12 +173,12 @@ proto.helloworld.HelloRequest.prototype.cloneMessage = function() { * optional string name = 1; * @return {string} */ -proto.helloworld.HelloRequest.prototype.getName = function() { +proto.helloworld.HelloRequest.prototype.getName = function () { return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, '')); }; /** @param {string} value */ -proto.helloworld.HelloRequest.prototype.setName = function(value) { +proto.helloworld.HelloRequest.prototype.setName = function (value) { jspb.Message.setField(this, 1, value); }; @@ -179,7 +192,7 @@ proto.helloworld.HelloRequest.prototype.setName = function(value) { * @extends {jspb.Message} * @constructor */ -proto.helloworld.HelloReply = function(opt_data) { +proto.helloworld.HelloReply = function (opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; goog.inherits(proto.helloworld.HelloReply, jspb.Message); @@ -198,8 +211,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * for transitional soy proto support: http://goto/soy-param-migration * @return {!Object} */ - proto.helloworld.HelloReply.prototype.toObject = function( - opt_includeInstance + proto.helloworld.HelloReply.prototype.toObject = function ( + opt_includeInstance, ) { return proto.helloworld.HelloReply.toObject(opt_includeInstance, this); }; @@ -212,11 +225,13 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * @param {!proto.helloworld.HelloReply} msg The msg instance to transform. * @return {!Object} */ - proto.helloworld.HelloReply.toObject = function(includeInstance, msg) { - var f, - obj = { - message: msg.getMessage() - }; + proto.helloworld.HelloReply.toObject = function (includeInstance, msg) { + let f; + + + const obj = { + message: msg.getMessage(), + }; if (includeInstance) { obj.$jspbMessageInstance = msg; @@ -230,9 +245,9 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * @param {jspb.ByteSource} bytes The bytes to deserialize. * @return {!proto.helloworld.HelloReply} */ -proto.helloworld.HelloReply.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.helloworld.HelloReply(); +proto.helloworld.HelloReply.deserializeBinary = function (bytes) { + const reader = new jspb.BinaryReader(bytes); + const msg = new proto.helloworld.HelloReply(); return proto.helloworld.HelloReply.deserializeBinaryFromReader(msg, reader); }; @@ -243,15 +258,15 @@ proto.helloworld.HelloReply.deserializeBinary = function(bytes) { * @param {!jspb.BinaryReader} reader The BinaryReader to use. * @return {!proto.helloworld.HelloReply} */ -proto.helloworld.HelloReply.deserializeBinaryFromReader = function( +proto.helloworld.HelloReply.deserializeBinaryFromReader = function ( msg, - reader + reader, ) { while (reader.nextField()) { if (reader.isEndGroup()) { break; } - var field = reader.getFieldNumber(); + const field = reader.getFieldNumber(); switch (field) { case 1: var value = /** @type {string} */ (reader.readString()); @@ -271,9 +286,9 @@ proto.helloworld.HelloReply.deserializeBinaryFromReader = function( * @param {!proto.helloworld.HelloReply} message * @param {!jspb.BinaryWriter} writer */ -proto.helloworld.HelloReply.serializeBinaryToWriter = function( +proto.helloworld.HelloReply.serializeBinaryToWriter = function ( message, - writer + writer, ) { message.serializeBinaryToWriter(writer); }; @@ -282,8 +297,8 @@ proto.helloworld.HelloReply.serializeBinaryToWriter = function( * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.helloworld.HelloReply.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); +proto.helloworld.HelloReply.prototype.serializeBinary = function () { + const writer = new jspb.BinaryWriter(); this.serializeBinaryToWriter(writer); return writer.getResultBuffer(); }; @@ -293,10 +308,10 @@ proto.helloworld.HelloReply.prototype.serializeBinary = function() { * writing to the given BinaryWriter. * @param {!jspb.BinaryWriter} writer */ -proto.helloworld.HelloReply.prototype.serializeBinaryToWriter = function( - writer +proto.helloworld.HelloReply.prototype.serializeBinaryToWriter = function ( + writer, ) { - var f = undefined; + let f; f = this.getMessage(); if (f.length > 0) { writer.writeString(1, f); @@ -307,9 +322,9 @@ proto.helloworld.HelloReply.prototype.serializeBinaryToWriter = function( * Creates a deep clone of this proto. No data is shared with the original. * @return {!proto.helloworld.HelloReply} The clone. */ -proto.helloworld.HelloReply.prototype.cloneMessage = function() { +proto.helloworld.HelloReply.prototype.cloneMessage = function () { return /** @type {!proto.helloworld.HelloReply} */ (jspb.Message.cloneMessage( - this + this, )); }; @@ -317,12 +332,12 @@ proto.helloworld.HelloReply.prototype.cloneMessage = function() { * optional string message = 1; * @return {string} */ -proto.helloworld.HelloReply.prototype.getMessage = function() { +proto.helloworld.HelloReply.prototype.getMessage = function () { return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, '')); }; /** @param {string} value */ -proto.helloworld.HelloReply.prototype.setMessage = function(value) { +proto.helloworld.HelloReply.prototype.setMessage = function (value) { jspb.Message.setField(this, 1, value); }; diff --git a/examples/grpc/server.js b/examples/grpc/server.js index 2a411ba40b..1b9d533172 100644 --- a/examples/grpc/server.js +++ b/examples/grpc/server.js @@ -1,26 +1,19 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); - -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (gRPC in this case). - */ -const config = require('./setup'); -config.setupTracerAndExporters('grpc-server-service'); - +const tracer = require('./tracer')(('example-grpc-server')); +// eslint-disable-next-line import/order const grpc = require('grpc'); -const tracer = opentelemetry.getTracer('example-grpc-server'); const messages = require('./helloworld_pb'); const services = require('./helloworld_grpc_pb'); + const PORT = 50051; /** Starts a gRPC server that receives requests on sample server port. */ function startServer() { // Creates a server const server = new grpc.Server(); - server.addService(services.GreeterService, { sayHello: sayHello }); + server.addService(services.GreeterService, { sayHello }); server.bind(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure()); console.log(`binding server on 0.0.0.0:${PORT}`); server.start(); @@ -33,11 +26,11 @@ function sayHello(call, callback) { const span = tracer.startSpan('server.js:sayHello()', { parent: currentSpan, kind: 1, // server - attributes: { key: 'value' } + attributes: { key: 'value' }, }); span.addEvent(`invoking sayHello() to ${call.request.getName()}`); const reply = new messages.HelloReply(); - reply.setMessage('Hello ' + call.request.getName()); + reply.setMessage(`Hello ${call.request.getName()}`); callback(null, reply); span.end(); } diff --git a/examples/grpc/setup.js b/examples/grpc/tracer.js similarity index 72% rename from examples/grpc/setup.js rename to examples/grpc/tracer.js index 7a7fc3eafe..ad881334c9 100644 --- a/examples/grpc/setup.js +++ b/examples/grpc/tracer.js @@ -5,27 +5,27 @@ const { NodeTracerRegistry } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); + const EXPORTER = process.env.EXPORTER || ''; -function setupTracerAndExporters(service) { +module.exports = (serviceName) => { const registry = new NodeTracerRegistry({ plugins: { grpc: { enabled: true, - // if it can't find the module, put the absolute path since the packages are not published yet - path: '@opentelemetry/plugin-grpc' - } - } + path: '@opentelemetry/plugin-grpc', + }, + }, }); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new ZipkinExporter({ - serviceName: service, + serviceName, }); } else { exporter = new JaegerExporter({ - serviceName: service, + serviceName, }); } @@ -33,6 +33,6 @@ function setupTracerAndExporters(service) { // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -} -exports.setupTracerAndExporters = setupTracerAndExporters; + return opentelemetry.getTracer(); +}; diff --git a/examples/grpc_dynamic_codegen/capitalize_client.js b/examples/grpc_dynamic_codegen/capitalize_client.js index a994307854..0791686567 100644 --- a/examples/grpc_dynamic_codegen/capitalize_client.js +++ b/examples/grpc_dynamic_codegen/capitalize_client.js @@ -1,22 +1,15 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); - -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (gRPC in this case). - */ -config.setupTracerAndExporters('grpc-client-service'); - +// eslint-disable-next-line import/order +const tracer = require('./tracer')('example-grpc-capitalize-client'); const path = require('path'); const grpc = require('grpc'); const protoLoader = require('@grpc/proto-loader'); -const tracer = opentelemetry.getTracer('example-grpc-capitalize-client'); - const PROTO_PATH = path.join(__dirname, 'protos/defs.proto'); -const PROTO_OPTIONS = { keepCase: true, enums: String, defaults: true, oneofs: true }; +const PROTO_OPTIONS = { + keepCase: true, enums: String, defaults: true, oneofs: true, +}; const definition = protoLoader.loadSync(PROTO_PATH, PROTO_OPTIONS); const rpcProto = grpc.loadPackageDefinition(definition).rpc; @@ -28,7 +21,7 @@ function main() { const span = tracer.startSpan('tutorialsClient.capitalize'); tracer.withSpan(span, () => { - client.capitalize({ data: Buffer.from(data) }, function (err, response) { + client.capitalize({ data: Buffer.from(data) }, (err, response) => { if (err) { console.log('could not get grpc response'); return; @@ -43,7 +36,7 @@ function main() { // The process must live for at least the interval past any traces that // must be exported, or some risk being lost if they are recorded after the // last export. - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); setTimeout(() => { console.log('Completed.'); }, 5000); } diff --git a/examples/grpc_dynamic_codegen/capitalize_server.js b/examples/grpc_dynamic_codegen/capitalize_server.js index 79dcd3c0fe..3755e4ea56 100644 --- a/examples/grpc_dynamic_codegen/capitalize_server.js +++ b/examples/grpc_dynamic_codegen/capitalize_server.js @@ -1,26 +1,19 @@ 'use strict'; +// eslint-disable-next-line import/order +const tracer = require('./tracer')('example-grpc-capitalize-server'); const { SpanKind } = require('@opentelemetry/types'); -const opentelemetry = require('@opentelemetry/core'); - -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (gRPC in this case). - */ -const config = require('./setup'); -config.setupTracerAndExporters('grpc-server-service'); - const path = require('path'); const grpc = require('grpc'); const protoLoader = require('@grpc/proto-loader'); const PROTO_PATH = path.join(__dirname, 'protos/defs.proto'); -const PROTO_OPTIONS = { keepCase: true, enums: String, defaults: true, oneofs: true }; +const PROTO_OPTIONS = { + keepCase: true, enums: String, defaults: true, oneofs: true, +}; const definition = protoLoader.loadSync(PROTO_PATH, PROTO_OPTIONS); const rpcProto = grpc.loadPackageDefinition(definition).rpc; -const tracer = opentelemetry.getTracer('example-grpc-capitalize-server'); - /** Implements the Capitalize RPC method. */ function capitalize(call, callback) { const currentSpan = tracer.getCurrentSpan(); @@ -34,7 +27,9 @@ function capitalize(call, callback) { const data = call.request.data.toString('utf8'); const capitalized = data.toUpperCase(); - for (let i = 0; i < 100000000; i++) {} + for (let i = 0; i < 100000000; i += 1) { + // empty + } span.end(); callback(null, { data: Buffer.from(capitalized) }); } @@ -45,7 +40,7 @@ function capitalize(call, callback) { */ function main() { const server = new grpc.Server(); - server.addService(rpcProto.Fetch.service, { capitalize: capitalize }); + server.addService(rpcProto.Fetch.service, { capitalize }); server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); server.start(); } diff --git a/examples/grpc_dynamic_codegen/setup.js b/examples/grpc_dynamic_codegen/tracer.js similarity index 82% rename from examples/grpc_dynamic_codegen/setup.js rename to examples/grpc_dynamic_codegen/tracer.js index 4fd8b0a8b6..23d6e25f01 100644 --- a/examples/grpc_dynamic_codegen/setup.js +++ b/examples/grpc_dynamic_codegen/tracer.js @@ -5,27 +5,28 @@ const { NodeTracerRegistry } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); + const EXPORTER = process.env.EXPORTER || ''; -function setupTracerAndExporters(service) { +module.exports = (serviceName) => { const registry = new NodeTracerRegistry({ plugins: { grpc: { enabled: true, // You may use a package name or absolute path to the file. - path: '@opentelemetry/plugin-grpc' - } - } + path: '@opentelemetry/plugin-grpc', + }, + }, }); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new ZipkinExporter({ - serviceName: service, + serviceName, }); } else { exporter = new JaegerExporter({ - serviceName: service, + serviceName, }); } @@ -35,6 +36,6 @@ function setupTracerAndExporters(service) { // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -} -exports.setupTracerAndExporters = setupTracerAndExporters; + return opentelemetry.getTracer(); +}; diff --git a/examples/http/client.js b/examples/http/client.js index 8b299b98ff..5a88497f66 100644 --- a/examples/http/client.js +++ b/examples/http/client.js @@ -1,43 +1,35 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); - -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (HTTP in this case). - */ -config.setupTracerAndExporters('http-client-service'); - +const tracer = require('./tracer')('example-http-client'); +// eslint-disable-next-line import/order const http = require('http'); -const tracer = opentelemetry.getTracer('example-http-client'); /** A function which makes requests and handles response. */ function makeRequest() { - // span corresponds to outgoing requests. Here, we have manually created - // the span, which is created to track work that happens outside of the - // request lifecycle entirely. - const span = tracer.startSpan('makeRequest'); - tracer.withSpan(span, () => { - http.get({ - host: 'localhost', - port: 8080, - path: '/helloworld' - }, (response) => { - let body = []; - response.on('data', chunk => body.push(chunk)); - response.on('end', () => { - console.log(body.toString()); - span.end(); - }); - }); - }) + // span corresponds to outgoing requests. Here, we have manually created + // the span, which is created to track work that happens outside of the + // request lifecycle entirely. + const span = tracer.startSpan('makeRequest'); + tracer.withSpan(span, () => { + http.get({ + host: 'localhost', + port: 8080, + path: '/helloworld', + }, (response) => { + const body = []; + response.on('data', (chunk) => body.push(chunk)); + response.on('end', () => { + console.log(body.toString()); + span.end(); + }); + }); + }); - // The process must live for at least the interval past any traces that - // must be exported, or some risk being lost if they are recorded after the - // last export. - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') - setTimeout(() => { console.log('Completed.'); }, 5000); + // The process must live for at least the interval past any traces that + // must be exported, or some risk being lost if they are recorded after the + // last export. + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); + setTimeout(() => { console.log('Completed.'); }, 5000); } makeRequest(); diff --git a/examples/http/server.js b/examples/http/server.js index d3c0046bf9..d142badf83 100644 --- a/examples/http/server.js +++ b/examples/http/server.js @@ -1,22 +1,15 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (HTTP in this case). - */ -config.setupTracerAndExporters('http-server-service'); - +const tracer = require('./tracer')('example-http-server'); +// eslint-disable-next-line import/order const http = require('http'); -const tracer = opentelemetry.getTracer('example-http-server'); /** Starts a HTTP server that receives requests on sample server port. */ -function startServer (port) { +function startServer(port) { // Creates a server const server = http.createServer(handleRequest); // Starts the server - server.listen(port, err => { + server.listen(port, (err) => { if (err) { throw err; } @@ -25,21 +18,21 @@ function startServer (port) { } /** A function which handles requests and send response. */ -function handleRequest (request, response) { +function handleRequest(request, response) { const currentSpan = tracer.getCurrentSpan(); // display traceid in the terminal console.log(`traceid: ${currentSpan.context().traceId}`); const span = tracer.startSpan('handleRequest', { parent: currentSpan, kind: 1, // server - attributes: { key:'value' } + attributes: { key: 'value' }, }); // Annotate our span to capture metadata about the operation span.addEvent('invoking handleRequest'); try { - let body = []; - request.on('error', err => console.log(err)); - request.on('data', chunk => body.push(chunk)); + const body = []; + request.on('error', (err) => console.log(err)); + request.on('data', (chunk) => body.push(chunk)); request.on('end', () => { // deliberately sleeping to mock some action. setTimeout(() => { @@ -48,7 +41,7 @@ function handleRequest (request, response) { }, 2000); }); } catch (err) { - console.log(err); + console.error(err); span.end(); } } diff --git a/examples/redis/setup.js b/examples/http/tracer.js similarity index 83% rename from examples/redis/setup.js rename to examples/http/tracer.js index 770cdd5dfd..29a21f4f0d 100644 --- a/examples/redis/setup.js +++ b/examples/http/tracer.js @@ -5,19 +5,20 @@ const { NodeTracerRegistry } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); + const EXPORTER = process.env.EXPORTER || ''; -function setupTracerAndExporters(service) { +module.exports = (serviceName) => { const registry = new NodeTracerRegistry(); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new ZipkinExporter({ - serviceName: service, + serviceName, }); } else { exporter = new JaegerExporter({ - serviceName: service, + serviceName, }); } @@ -25,6 +26,6 @@ function setupTracerAndExporters(service) { // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -} -exports.setupTracerAndExporters = setupTracerAndExporters; + return opentelemetry.getTracer(); +}; diff --git a/examples/https/client.js b/examples/https/client.js index 40ebc94d3f..e5b1825163 100644 --- a/examples/https/client.js +++ b/examples/https/client.js @@ -1,42 +1,35 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (HTTPs in this case). - */ -config.setupTracerAndExporters('https-client-service'); - +const tracer = require('./tracer')('example-https-client'); +// eslint-disable-next-line import/order const https = require('https'); -const tracer = opentelemetry.getTracer('example-https-client'); /** A function which makes requests and handles response. */ function makeRequest() { - // span corresponds to outgoing requests. Here, we have manually created - // the span, which is created to track work that happens outside of the - // request lifecycle entirely. - const span = tracer.startSpan('makeRequest'); - tracer.withSpan(span, () => { - https.get({ - host: 'localhost', - port: 443, - path: '/helloworld' - }, (response) => { - let body = []; - response.on('data', chunk => body.push(chunk)); - response.on('end', () => { - console.log(body.toString()); - span.end(); - }); - }); + // span corresponds to outgoing requests. Here, we have manually created + // the span, which is created to track work that happens outside of the + // request lifecycle entirely. + const span = tracer.startSpan('makeRequest'); + tracer.withSpan(span, () => { + https.get({ + host: 'localhost', + port: 443, + path: '/helloworld', + }, (response) => { + const body = []; + response.on('data', (chunk) => body.push(chunk)); + response.on('end', () => { + console.log(body.toString()); + span.end(); + }); }); + }); - // The process must live for at least the interval past any traces that - // must be exported, or some risk being lost if they are recorded after the - // last export. - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') - setTimeout(() => { console.log('Completed.'); }, 5000); + // The process must live for at least the interval past any traces that + // must be exported, or some risk being lost if they are recorded after the + // last export. + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); + setTimeout(() => { console.log('Completed.'); }, 5000); } makeRequest(); diff --git a/examples/https/server.js b/examples/https/server.js index 7280c8f61f..105005fea8 100644 --- a/examples/https/server.js +++ b/examples/https/server.js @@ -1,27 +1,20 @@ 'use strict'; +// eslint-disable-next-line import/order +const tracer = require('./tracer')('example-https-server'); const fs = require('fs'); -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (HTTPs in this case). - */ -config.setupTracerAndExporters('https-server-service'); - const https = require('https'); -const tracer = opentelemetry.getTracer('example-https-server'); /** Starts a HTTPs server that receives requests on sample server port. */ -function startServer (port) { +function startServer(port) { const options = { key: fs.readFileSync('./server-key.pem'), - cert: fs.readFileSync('./server-cert.pem') + cert: fs.readFileSync('./server-cert.pem'), }; // Creates a server const server = https.createServer(options, handleRequest); // Starts the server - server.listen(port, err => { + server.listen(port, (err) => { if (err) { throw err; } @@ -30,21 +23,21 @@ function startServer (port) { } /** A function which handles requests and send response. */ -function handleRequest (request, response) { +function handleRequest(request, response) { const currentSpan = tracer.getCurrentSpan(); // display traceid in the terminal console.log(`traceid: ${currentSpan.context().traceId}`); const span = tracer.startSpan('handleRequest', { parent: currentSpan, kind: 1, // server - attributes: { key:'value' } + attributes: { key: 'value' }, }); // Annotate our span to capture metadata about the operation span.addEvent('invoking handleRequest'); try { - let body = []; - request.on('error', err => console.log(err)); - request.on('data', chunk => body.push(chunk)); + const body = []; + request.on('error', (err) => console.log(err)); + request.on('data', (chunk) => body.push(chunk)); request.on('end', () => { // deliberately sleeping to mock some action. setTimeout(() => { diff --git a/examples/https/setup.js b/examples/https/tracer.js similarity index 84% rename from examples/https/setup.js rename to examples/https/tracer.js index ef426a90b5..eaec28f376 100644 --- a/examples/https/setup.js +++ b/examples/https/tracer.js @@ -5,19 +5,21 @@ const { NodeTracerRegistry } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); + const EXPORTER = process.env.EXPORTER || ''; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; -function setupTracerAndExporters(service) { + +module.exports = (serviceName) => { let exporter; const registry = new NodeTracerRegistry(); if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new ZipkinExporter({ - serviceName: service + serviceName, }); } else { exporter = new JaegerExporter({ - serviceName: service, + serviceName, }); } @@ -25,6 +27,6 @@ function setupTracerAndExporters(service) { // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -} -exports.setupTracerAndExporters = setupTracerAndExporters; + return opentelemetry.getTracer(); +}; diff --git a/examples/mysql/client.js b/examples/mysql/client.js index e080ab994a..6e8bc0c51a 100644 --- a/examples/mysql/client.js +++ b/examples/mysql/client.js @@ -1,81 +1,73 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); - -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (HTTP and MySQL in this case). - */ -config.setupTracerAndExporters('http-client-service'); - +const tracer = require('./tracer')('example-mysql-http-client'); +// eslint-disable-next-line import/order const http = require('http'); -const tracer = opentelemetry.getTracer('example-mysql-http-client'); /** A function which makes requests and handles response. */ function makeRequest() { - // span corresponds to outgoing requests. Here, we have manually created - // the span, which is created to track work that happens outside of the - // request lifecycle entirely. - const span = tracer.startSpan('makeRequest'); + // span corresponds to outgoing requests. Here, we have manually created + // the span, which is created to track work that happens outside of the + // request lifecycle entirely. + const span = tracer.startSpan('makeRequest'); - let queries = 0 - let responses = 0; + let queries = 0; + let responses = 0; - tracer.withSpan(span, () => { - queries += 1; - http.get({ - host: 'localhost', - port: 8080, - path: '/connection/query' - }, (response) => { - let body = []; - response.on('data', chunk => body.push(chunk)); - response.on('end', () => { - responses += 1; - console.log(body.toString()); - if (responses === queries) span.end(); - }); - }); + tracer.withSpan(span, () => { + queries += 1; + http.get({ + host: 'localhost', + port: 8080, + path: '/connection/query', + }, (response) => { + const body = []; + response.on('data', (chunk) => body.push(chunk)); + response.on('end', () => { + responses += 1; + console.log(body.toString()); + if (responses === queries) span.end(); + }); }); - tracer.withSpan(span, () => { - queries += 1; - http.get({ - host: 'localhost', - port: 8080, - path: '/pool/query' - }, (response) => { - let body = []; - response.on('data', chunk => body.push(chunk)); - response.on('end', () => { - responses += 1; - console.log(body.toString()); - if (responses === queries) span.end(); - }); - }); + }); + tracer.withSpan(span, () => { + queries += 1; + http.get({ + host: 'localhost', + port: 8080, + path: '/pool/query', + }, (response) => { + const body = []; + response.on('data', (chunk) => body.push(chunk)); + response.on('end', () => { + responses += 1; + console.log(body.toString()); + if (responses === queries) span.end(); + }); }); - tracer.withSpan(span, () => { - queries += 1; - http.get({ - host: 'localhost', - port: 8080, - path: '/cluster/query' - }, (response) => { - let body = []; - response.on('data', chunk => body.push(chunk)); - response.on('end', () => { - responses += 1; - console.log(body.toString()); - if (responses === queries) span.end(); - }); - }); + }); + tracer.withSpan(span, () => { + queries += 1; + http.get({ + host: 'localhost', + port: 8080, + path: '/cluster/query', + }, (response) => { + const body = []; + response.on('data', (chunk) => body.push(chunk)); + response.on('end', () => { + responses += 1; + console.log(body.toString()); + if (responses === queries) span.end(); + }); }); + }); - // The process must live for at least the interval past any traces that - // must be exported, or some risk being lost if they are recorded after the - // last export. - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') - setTimeout(() => { console.log('Completed.'); }, 5000); + // The process must live for at least the interval past any traces that + // must be exported, or some risk being lost if they are recorded after the + // last export. + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); + setTimeout(() => { console.log('Completed.'); }, 5000); } makeRequest(); diff --git a/examples/mysql/server.js b/examples/mysql/server.js index 1ba4eb7258..748ef339d1 100644 --- a/examples/mysql/server.js +++ b/examples/mysql/server.js @@ -1,47 +1,39 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); -/** - * The trace instance needs to be initialized first, if you want to enable - * automatic tracing for built-in plugins (HTTP and MySQL in this case). - */ -config.setupTracerAndExporters('http-mysql-server-service'); - +// eslint-disable-next-line import/order +const tracer = require('./tracer')('example-mysql-http-server'); const mysql = require('mysql'); const http = require('http'); -const tracer = opentelemetry.getTracer('example-mysql-http-server'); - const pool = mysql.createPool({ - host : 'localhost', - user : 'root', - password : 'secret', - database : 'my_db', + host: 'localhost', + user: 'root', + password: 'secret', + database: 'my_db', }); const connection = mysql.createConnection({ - host : 'localhost', - user : 'root', - password : 'secret', - database : 'my_db', + host: 'localhost', + user: 'root', + password: 'secret', + database: 'my_db', }); const cluster = mysql.createPoolCluster(); cluster.add({ - host : 'localhost', - user : 'root', - password : 'secret', - database : 'my_db', -}) + host: 'localhost', + user: 'root', + password: 'secret', + database: 'my_db', +}); /** Starts a HTTP server that receives requests on sample server port. */ -function startServer (port) { +function startServer(port) { // Creates a server const server = http.createServer(handleRequest); // Starts the server - server.listen(port, err => { + server.listen(port, (err) => { if (err) { throw err; } @@ -50,23 +42,23 @@ function startServer (port) { } /** A function which handles requests and send response. */ -function handleRequest (request, response) { +function handleRequest(request, response) { const currentSpan = tracer.getCurrentSpan(); // display traceid in the terminal - const {traceId} = currentSpan.context(); + const { traceId } = currentSpan.context(); console.log(`traceid: ${traceId}`); - console.log(`Jaeger URL: http://localhost:16686/trace/${traceId}`) - console.log(`Zipkin URL: http://localhost:9411/zipkin/traces/${traceId}`) + console.log(`Jaeger URL: http://localhost:16686/trace/${traceId}`); + console.log(`Zipkin URL: http://localhost:9411/zipkin/traces/${traceId}`); try { - let body = []; - request.on('error', err => console.log(err)); - request.on('data', chunk => body.push(chunk)); + const body = []; + request.on('error', (err) => console.log(err)); + request.on('data', (chunk) => body.push(chunk)); request.on('end', () => { - if (request.url === "/connection/query") { + if (request.url === '/connection/query') { handleConnectionQuery(response); - } else if (request.url === "/pool/query") { + } else if (request.url === '/pool/query') { handlePoolQuery(response); - } else if (request.url === "/cluster/query") { + } else if (request.url === '/cluster/query') { handleClusterQuery(response); } else { handleNotFound(response); @@ -80,51 +72,47 @@ function handleRequest (request, response) { startServer(8080); function handlePoolQuery(response) { - const query = "SELECT 1 + 1 as pool_solution"; - pool.getConnection((err, conn) => { - conn.query(query, (err, results, fields) => { - tracer.getCurrentSpan().addEvent("results"); + const query = 'SELECT 1 + 1 as pool_solution'; + pool.getConnection((connErr, conn, _fields) => { + conn.query(query, (err, results) => { + tracer.getCurrentSpan().addEvent('results'); if (err) { - console.log("Error code:", err.code); + console.log('Error code:', err.code); response.end(err.message); - } - else { + } else { response.end(`${query}: ${results[0].pool_solution}`); } }); - }) + }); } function handleConnectionQuery(response) { - const query = "SELECT 1 + 1 as solution"; - connection.query(query, (err, results, fields) => { + const query = 'SELECT 1 + 1 as solution'; + connection.query(query, (err, results, _fields) => { if (err) { - console.log("Error code:", err.code); + console.log('Error code:', err.code); response.end(err.message); - } - else { + } else { response.end(`${query}: ${results[0].solution}`); } }); } function handleClusterQuery(response) { - const query = "SELECT 1 + 1 as cluster_solution"; - cluster.getConnection((err, conn) => { - conn.query(query, (err, results, fields) => { - tracer.getCurrentSpan().addEvent("results"); + const query = 'SELECT 1 + 1 as cluster_solution'; + cluster.getConnection((connErr, conn) => { + conn.query(query, (err, results, _fields) => { + tracer.getCurrentSpan().addEvent('results'); if (err) { - console.log("Error code:", err.code); + console.log('Error code:', err.code); response.end(err.message); - } - else { + } else { response.end(`${query}: ${results[0].cluster_solution}`); } }); - }) + }); } function handleNotFound(response) { - response.end("not found"); + response.end('not found'); } - diff --git a/examples/mysql/setup.js b/examples/mysql/tracer.js similarity index 75% rename from examples/mysql/setup.js rename to examples/mysql/tracer.js index e3f9ce3f41..e3e93e4d79 100644 --- a/examples/mysql/setup.js +++ b/examples/mysql/tracer.js @@ -6,29 +6,29 @@ const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); -function setupTracerAndExporters(service) { +module.exports = (serviceName) => { const registry = new NodeTracerRegistry({ plugins: { mysql: { enabled: true, - path: "@opentelemetry/plugin-mysql" + path: '@opentelemetry/plugin-mysql', }, http: { enabled: true, - path: "@opentelemetry/plugin-http" - } - } + path: '@opentelemetry/plugin-http', + }, + }, }); registry.addSpanProcessor(new SimpleSpanProcessor(new ZipkinExporter({ - serviceName: service, + serviceName, }))); registry.addSpanProcessor(new SimpleSpanProcessor(new JaegerExporter({ - serviceName: service, + serviceName, }))); // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -} -exports.setupTracerAndExporters = setupTracerAndExporters; + return opentelemetry.getTracer(); +}; diff --git a/examples/opentracing-shim/client.js b/examples/opentracing-shim/client.js index 916b1c65f0..451467e3ef 100644 --- a/examples/opentracing-shim/client.js +++ b/examples/opentracing-shim/client.js @@ -1,8 +1,8 @@ -"use strict"; +'use strict'; -const http = require("http"); -const opentracing = require("opentracing"); -const shim = require("./shim").shim("http_client_service"); +const http = require('http'); +const opentracing = require('opentracing'); +const shim = require('./shim').shim('http_client_service'); opentracing.initGlobalTracer(shim); const tracer = opentracing.globalTracer(); @@ -10,7 +10,7 @@ const tracer = opentracing.globalTracer(); makeRequest(); async function makeRequest() { - const span = tracer.startSpan("make_request"); + const span = tracer.startSpan('make_request'); const headers = {}; tracer.inject(span, opentracing.FORMAT_HTTP_HEADERS, headers); @@ -18,28 +18,28 @@ async function makeRequest() { http .get( { - host: "localhost", + host: 'localhost', port: 3000, - path: "/", - headers + path: '/', + headers, }, - resp => { - let data = ""; + (resp) => { + let data = ''; - resp.on("data", chunk => { + resp.on('data', (chunk) => { data += chunk; }); - resp.on("end", async () => { + resp.on('end', async () => { console.log(JSON.parse(data)); span.finish(); - console.log("Sleeping 5 seconds before shutdown to ensure all records are flushed."); - setTimeout(() => { console.log("Completed."); }, 5000); + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); + setTimeout(() => { console.log('Completed.'); }, 5000); }); - } + }, ) - .on("error", err => { - console.log("Error: " + err.message); + .on('error', (err) => { + console.log(`Error: ${err.message}`); }); } diff --git a/examples/opentracing-shim/package.json b/examples/opentracing-shim/package.json index 3762aca0c7..46d065f9fe 100644 --- a/examples/opentracing-shim/package.json +++ b/examples/opentracing-shim/package.json @@ -33,6 +33,7 @@ "@opentelemetry/exporter-zipkin": "^0.3.3", "@opentelemetry/node": "^0.3.3", "@opentelemetry/shim-opentracing": "^0.3.3", + "@opentelemetry/tracing": "^0.3.3", "opentracing": "^0.14.4" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", diff --git a/examples/opentracing-shim/server.js b/examples/opentracing-shim/server.js index f10ffdfc5d..66c0803ccd 100644 --- a/examples/opentracing-shim/server.js +++ b/examples/opentracing-shim/server.js @@ -1,9 +1,9 @@ -"use strict"; +'use strict'; -const http = require("http"); -const opentracing = require("opentracing"); -const utils = require("./utils"); -const shim = require("./shim").shim("http_server_service"); +const http = require('http'); +const opentracing = require('opentracing'); +const utils = require('./utils'); +const shim = require('./shim').shim('http_server_service'); opentracing.initGlobalTracer(shim); const tracer = opentracing.globalTracer(); @@ -13,7 +13,7 @@ startServer(3000); function startServer(port) { const server = http.createServer(handleRequest); - server.listen(port, err => { + server.listen(port, (err) => { if (err) throw err; console.log(`Server is listening on ${port}`); @@ -23,21 +23,21 @@ function startServer(port) { async function handleRequest(req, res) { const parentSpan = tracer.extract( opentracing.FORMAT_HTTP_HEADERS, - req.headers + req.headers, ); - const span = tracer.startSpan("handle_request", { - childOf: parentSpan + const span = tracer.startSpan('handle_request', { + childOf: parentSpan, }); - span.setTag("custom", "tag value"); - span.setTag("alpha", "1000"); + span.setTag('custom', 'tag value'); + span.setTag('alpha', '1000'); await doSomething(span); - res.writeHead(200, { "Content-Type": "application/json" }); + res.writeHead(200, { 'Content-Type': 'application/json' }); res.write( - JSON.stringify({ status: "OK", traceId: span.context().toTraceId() }) + JSON.stringify({ status: 'OK', traceId: span.context().toTraceId() }), ); res.end(); @@ -45,11 +45,11 @@ async function handleRequest(req, res) { } async function doSomething(parentSpan) { - const span = tracer.startSpan("do_something", { childOf: parentSpan }); + const span = tracer.startSpan('do_something', { childOf: parentSpan }); - span.setTag("alpha", "200"); - span.setTag("beta", "50"); - span.log({ state: "waiting" }); + span.setTag('alpha', '200'); + span.setTag('beta', '50'); + span.log({ state: 'waiting' }); // deliberately sleeping to mock some action. await utils.sleep(1000); diff --git a/examples/opentracing-shim/shim.js b/examples/opentracing-shim/shim.js index 8efb1c2a8b..4730f9e258 100644 --- a/examples/opentracing-shim/shim.js +++ b/examples/opentracing-shim/shim.js @@ -1,23 +1,23 @@ -"use strict"; +'use strict'; -const { NodeTracerRegistry } = require("@opentelemetry/node"); -const { SimpleSpanProcessor } = require("@opentelemetry/tracing"); -const { JaegerExporter } = require("@opentelemetry/exporter-jaeger"); -const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin"); -const { TracerShim } = require("@opentelemetry/shim-opentracing"); +const { NodeTracerRegistry } = require('@opentelemetry/node'); +const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); +const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); +const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); +const { TracerShim } = require('@opentelemetry/shim-opentracing'); function shim(serviceName) { const registry = new NodeTracerRegistry(); registry.addSpanProcessor(new SimpleSpanProcessor(getExporter(serviceName))); - return new TracerShim(registry.getTracer("opentracing-shim")); + return new TracerShim(registry.getTracer('opentracing-shim')); } function getExporter(serviceName) { - const type = process.env.EXPORTER.toLowerCase() || "jaeger"; + const type = process.env.EXPORTER.toLowerCase() || 'jaeger'; - if (type.startsWith("z")) { + if (type.startsWith('z')) { return new ZipkinExporter({ serviceName }); } diff --git a/examples/opentracing-shim/utils.js b/examples/opentracing-shim/utils.js index 813dc35dcd..a913e95acd 100644 --- a/examples/opentracing-shim/utils.js +++ b/examples/opentracing-shim/utils.js @@ -1,7 +1,7 @@ -"use strict"; +'use strict'; async function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } exports.sleep = sleep; diff --git a/examples/postgres/client.js b/examples/postgres/client.js index 1c31f5c1d3..1df7bdf9f6 100644 --- a/examples/postgres/client.js +++ b/examples/postgres/client.js @@ -1,35 +1,32 @@ 'use strict'; -// set up ot -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); -config.setupTracerAndExporters('postgres-client-service'); +const tracer = require('./tracer')('postgres-client-service'); +// eslint-disable-next-line import/order const http = require('http'); -const tracer = opentelemetry.getTracer(); function makeRequest() { - const span = tracer.startSpan('makeRequest'); - const randomId = Math.floor(Math.random() * 10); - tracer.withSpan(span, () => { - console.log('Client traceId ', span.context().traceId); - http.get({ - host: 'localhost', - port: 3000, - path: `/insert?id=${randomId}&text=randomstring` - }); + const span = tracer.startSpan('makeRequest'); + const randomId = Math.floor(Math.random() * 10); + tracer.withSpan(span, () => { + console.log('Client traceId ', span.context().traceId); + http.get({ + host: 'localhost', + port: 3000, + path: `/insert?id=${randomId}&text=randomstring`, + }); - http.get({ - host: 'localhost', - port: 3000, - path: `/get?id=${randomId}` - }); + http.get({ + host: 'localhost', + port: 3000, + path: `/get?id=${randomId}`, }); + }); - // The process must live for at least the interval past any traces that - // must be exported, or some risk being lost if they are recorded after the - // last export. - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') - setTimeout(() => { console.log('Completed.'); }, 5000); + // The process must live for at least the interval past any traces that + // must be exported, or some risk being lost if they are recorded after the + // last export. + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); + setTimeout(() => { console.log('Completed.'); }, 5000); } makeRequest(); diff --git a/examples/postgres/server.js b/examples/postgres/server.js index 9f67ed4a56..8486add4a3 100644 --- a/examples/postgres/server.js +++ b/examples/postgres/server.js @@ -1,18 +1,13 @@ 'use strict'; -// set up ot -const opentelemetry = require('@opentelemetry/core'); +// eslint-disable-next-line import/order +const tracer = require('./tracer')('postgres-server-service'); const { SpanKind, CanonicalCode } = require('@opentelemetry/types'); -const config = require('./setup'); -config.setupTracerAndExporters('postgres-server-service'); -const tracer = opentelemetry.getTracer(); +const express = require('express'); +const setupPg = require('./setupPsql'); -// set up pg -const setupPg = require('./setupPsql'); const pool = setupPg.startPsql(); -// set up express -const express = require('express'); const app = express(); app.get('/:cmd', (req, res) => { @@ -28,7 +23,7 @@ app.get('/:cmd', (req, res) => { return; } queryText = { - text: `INSERT INTO test (id, text) VALUES($1, $2) ON CONFLICT(id) DO UPDATE SET text=$2`, + text: 'INSERT INTO test (id, text) VALUES($1, $2) ON CONFLICT(id) DO UPDATE SET text=$2', values: [req.query.id, req.query.text], }; } @@ -45,7 +40,7 @@ app.get('/:cmd', (req, res) => { res.send(ret.rows); }); } catch (e) { - res.status(400).send({message: e.message}); + res.status(400).send({ message: e.message }); span.setStatus(CanonicalCode.UNKNOWN); } span.end(); @@ -54,7 +49,6 @@ app.get('/:cmd', (req, res) => { // start server const port = 3000; -app.listen(port, function() { +app.listen(port, () => { console.log(`Node HTTP listening on ${port}`); }); - diff --git a/examples/postgres/setupPsql.js b/examples/postgres/setupPsql.js index 988e23a467..d6722b840e 100644 --- a/examples/postgres/setupPsql.js +++ b/examples/postgres/setupPsql.js @@ -1,28 +1,31 @@ +'use strict'; + const { Pool } = require('pg'); // create new pool for psql const CONFIG = { - user: process.env.POSTGRES_USER || 'postgres', - database: process.env.POSTGRES_DB || 'postgres', - host: process.env.POSTGRES_HOST || 'localhost', - port: process.env.POSTGRES_PORT - ? parseInt(process.env.POSTGRES_PORT, 10) - : 54320, - }; - + user: process.env.POSTGRES_USER || 'postgres', + database: process.env.POSTGRES_DB || 'postgres', + host: process.env.POSTGRES_HOST || 'localhost', + port: process.env.POSTGRES_PORT + ? parseInt(process.env.POSTGRES_PORT, 10) + : 54320, +}; + function startPsql() { - let pool = new Pool(CONFIG); + const pool = new Pool(CONFIG); - pool.connect(function(err, client, release) { - if (err) throw err; - release(); - const queryText = 'CREATE TABLE IF NOT EXISTS test(id SERIAL PRIMARY KEY, text VARCHAR(40) not null)'; - client.query(queryText, (err, res) => { - if (err) throw err; - }); + pool.connect((connectErr, client, release) => { + if (connectErr) throw connectErr; + release(); + const queryText = 'CREATE TABLE IF NOT EXISTS test(id SERIAL PRIMARY KEY, text VARCHAR(40) not null)'; + client.query(queryText, (err, res) => { + if (err) throw err; + console.log(res.rows[0]); }); + }); - return pool; + return pool; } exports.startPsql = startPsql; diff --git a/examples/postgres/setup.js b/examples/postgres/tracer.js similarity index 68% rename from examples/postgres/setup.js rename to examples/postgres/tracer.js index 1c369a0f48..ab1d34ddc1 100644 --- a/examples/postgres/setup.js +++ b/examples/postgres/tracer.js @@ -5,37 +5,41 @@ const { NodeTracer } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); + const EXPORTER = process.env.EXPORTER || ''; -function setupTracerAndExporters(service) { +module.exports = (serviceName) => { const tracer = new NodeTracer({ plugins: { pg: { enabled: true, - // if it can't find the module, put the absolute path since the packages are not published yet - path: '@opentelemetry/plugin-pg' + /* + if it can't find the module, + put the absolute path since the packages are not published yet + */ + path: '@opentelemetry/plugin-pg', }, 'pg-pool': { enabled: true, - path: '@opentelemetry/plugin-pg-pool' + path: '@opentelemetry/plugin-pg-pool', }, http: { enabled: true, - path: '@opentelemetry/plugin-http' - } - } + path: '@opentelemetry/plugin-http', + }, + }, }); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new ZipkinExporter({ - serviceName: service, + serviceName, }); } else { exporter = new JaegerExporter({ - serviceName: service, + serviceName, // The default flush interval is 5 seconds. - flushInterval: 2000 + flushInterval: 2000, }); } @@ -43,6 +47,6 @@ function setupTracerAndExporters(service) { // Initialize the OpenTelemetry APIs to use the BasicTracer bindings opentelemetry.initGlobalTracer(tracer); -} -exports.setupTracerAndExporters = setupTracerAndExporters; + return opentelemetry.getTracer(); +}; diff --git a/examples/prometheus/index.js b/examples/prometheus/index.js index fea1d1c9d8..7e6a85dee9 100644 --- a/examples/prometheus/index.js +++ b/examples/prometheus/index.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; const { MeterRegistry } = require('@opentelemetry/metrics'); const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); @@ -7,37 +7,37 @@ const meter = new MeterRegistry().getMeter('example-prometheus'); const exporter = new PrometheusExporter( { - startServer: true + startServer: true, }, () => { - console.log("prometheus scrape endpoint: http://localhost:9464/metrics"); - } + console.log('prometheus scrape endpoint: http://localhost:9464/metrics'); + }, ); meter.addExporter(exporter); // Monotonic counters and gauges can only be increased. -const monotonicCounter = meter.createCounter("monotonic_counter", { +const monotonicCounter = meter.createCounter('monotonic_counter', { monotonic: true, - labelKeys: ["pid"], - description: "Example of a monotonic counter" + labelKeys: ['pid'], + description: 'Example of a monotonic counter', }); -const monotonicGauge = meter.createGauge("monotonic_gauge", { +const monotonicGauge = meter.createGauge('monotonic_gauge', { monotonic: true, - labelKeys: ["pid"], - description: "Example of a monotonic gauge" + labelKeys: ['pid'], + description: 'Example of a monotonic gauge', }); // Non-monotonic counters and gauges can be increased or decreased. -const nonMonotonicCounter = meter.createCounter("non_monotonic_counter", { +const nonMonotonicCounter = meter.createCounter('non_monotonic_counter', { monotonic: false, - labelKeys: ["pid"], - description: "Example of a non-monotonic counter" + labelKeys: ['pid'], + description: 'Example of a non-monotonic counter', }); -const nonMonotonicGauge = meter.createGauge("non_monotonic_gauge", { +const nonMonotonicGauge = meter.createGauge('non_monotonic_gauge', { monotonic: false, - labelKeys: ["pid"], - description: "Example of a non-monotonic gauge" + labelKeys: ['pid'], + description: 'Example of a non-monotonic gauge', }); let currentMonotonicGaugeValue = 0; diff --git a/examples/redis/client.js b/examples/redis/client.js index 191effe612..30fd9b65ca 100644 --- a/examples/redis/client.js +++ b/examples/redis/client.js @@ -1,30 +1,28 @@ -'use strict' +'use strict'; -const opentelemetry = require('@opentelemetry/core'); +// eslint-disable-next-line import/order +const tracer = require('./tracer')('example-redis-client'); const types = require('@opentelemetry/types'); -const config = require('./setup'); -config.setupTracerAndExporters('redis-client-service'); -const tracer = opentelemetry.getTracer('example-redis-client'); const axios = require('axios').default; function makeRequest() { - const span = tracer.startSpan('client.makeRequest()', { - parent: tracer.getCurrentSpan(), - kind: types.SpanKind.CLIENT - }); + const span = tracer.startSpan('client.makeRequest()', { + parent: tracer.getCurrentSpan(), + kind: types.SpanKind.CLIENT, + }); - tracer.withSpan(span, async () => { - try { - const res = await axios.get('http://localhost:8080/run_test'); - span.setStatus({ code: types.CanonicalCode.OK }); - console.log(res.statusText); - } catch (e) { - span.setStatus({ code: types.CanonicalCode.UNKNOWN, message: e.message }); - } - span.end(); - console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.') - setTimeout(() => { console.log('Completed.'); }, 5000); - }); + tracer.withSpan(span, async () => { + try { + const res = await axios.get('http://localhost:8080/run_test'); + span.setStatus({ code: types.CanonicalCode.OK }); + console.log(res.statusText); + } catch (e) { + span.setStatus({ code: types.CanonicalCode.UNKNOWN, message: e.message }); + } + span.end(); + console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.'); + setTimeout(() => { console.log('Completed.'); }, 5000); + }); } makeRequest(); diff --git a/examples/redis/express-tracer-handlers.js b/examples/redis/express-tracer-handlers.js index 25918318a1..1e15608ab5 100644 --- a/examples/redis/express-tracer-handlers.js +++ b/examples/redis/express-tracer-handlers.js @@ -1,36 +1,36 @@ -'use strict' +'use strict'; const types = require('@opentelemetry/types'); function getMiddlewareTracer(tracer) { - return function(req, res, next) { + return (req, res, next) => { const span = tracer.startSpan(`express.middleware.tracer(${req.method} ${req.path})`, { parent: tracer.getCurrentSpan(), kind: types.SpanKind.SERVER, }); - + // End this span before sending out the response const originalSend = res.send; - res.send = function send() { + res.send = function send(...args) { span.end(); - originalSend.apply(res, arguments); - } - + originalSend.apply(res, args); + }; + tracer.withSpan(span, next); - } + }; } function getErrorTracer(tracer) { - return function(err, _req, res, _next) { - console.log('Caught error', err.message); + return (err, _req, res, _next) => { + console.error('Caught error', err.message); const span = tracer.getCurrentSpan(); if (span) { span.setStatus({ code: types.CanonicalCode.INTERNAL, message: err.message }); } res.status(500).send(err.message); - } + }; } module.exports = { - getMiddlewareTracer, getErrorTracer -} + getMiddlewareTracer, getErrorTracer, +}; diff --git a/examples/redis/server.js b/examples/redis/server.js index 33744b2f74..f293084e2d 100644 --- a/examples/redis/server.js +++ b/examples/redis/server.js @@ -1,15 +1,13 @@ 'use strict'; -// Setup opentelemetry tracer first so that built-in plugins can hook onto their corresponding modules -const opentelemetry = require('@opentelemetry/core'); -const config = require('./setup'); -config.setupTracerAndExporters('redis-server-service'); -const tracer = opentelemetry.getTracer('example-redis-server'); +// eslint-disable-next-line import/order +const tracer = require('./tracer')('example-redis-server'); // Require in rest of modules const express = require('express'); const axios = require('axios').default; const tracerHandlers = require('./express-tracer-handlers'); +const redisPromise = require('./setup-redis').redis; // Setup express const app = express(); @@ -19,10 +17,15 @@ const PORT = 8080; * Redis Routes are set up async since we resolve the client once it is successfully connected */ async function setupRoutes() { - const redis = await require('./setup-redis').redis; + const redis = await redisPromise; app.get('/run_test', async (req, res) => { - const uuid = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + const uuid = Math.random() + .toString(36) + .substring(2, 15) + + Math.random() + .toString(36) + .substring(2, 15); await axios.get(`http://localhost:${PORT}/set?args=uuid,${uuid}`); const body = await axios.get(`http://localhost:${PORT}/get?args=uuid`); @@ -39,12 +42,12 @@ async function setupRoutes() { return; } - const cmd = req.params.cmd; + const { cmd } = req.params; const args = req.query.args.split(','); redis[cmd].call(redis, ...args, (err, result) => { if (err) { res.sendStatus(400); - } else if(result) { + } else if (result) { res.status(200).send(result); } else { throw new Error('Empty redis response'); @@ -58,5 +61,5 @@ app.use(tracerHandlers.getMiddlewareTracer(tracer)); setupRoutes().then(() => { app.use(tracerHandlers.getErrorTracer(tracer)); app.listen(PORT); - console.log(`Listening on http://localhost:${PORT}`) + console.log(`Listening on http://localhost:${PORT}`); }); diff --git a/examples/redis/setup-redis.js b/examples/redis/setup-redis.js index dba4f0ebad..59b2e189b5 100644 --- a/examples/redis/setup-redis.js +++ b/examples/redis/setup-redis.js @@ -1,13 +1,15 @@ +'use strict'; + const redis = require('redis'); const client = redis.createClient('redis://localhost:6379'); -const redisPromise = new Promise(function(resolve, reject) { +const redisPromise = new Promise(((resolve, reject) => { client.once('ready', () => { resolve(client); }); client.once('error', (error) => { reject(error); }); -}); +})); exports.redis = redisPromise; diff --git a/examples/http/setup.js b/examples/redis/tracer.js similarity index 83% rename from examples/http/setup.js rename to examples/redis/tracer.js index 770cdd5dfd..fa2680830a 100644 --- a/examples/http/setup.js +++ b/examples/redis/tracer.js @@ -5,19 +5,21 @@ const { NodeTracerRegistry } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); + const EXPORTER = process.env.EXPORTER || ''; -function setupTracerAndExporters(service) { + +module.exports = (serviceName) => { const registry = new NodeTracerRegistry(); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { exporter = new ZipkinExporter({ - serviceName: service, + serviceName, }); } else { exporter = new JaegerExporter({ - serviceName: service, + serviceName, }); } @@ -25,6 +27,6 @@ function setupTracerAndExporters(service) { // Initialize the OpenTelemetry APIs to use the BasicTracerRegistry bindings opentelemetry.initGlobalTracerRegistry(registry); -} -exports.setupTracerAndExporters = setupTracerAndExporters; + return opentelemetry.getTracer(); +}; diff --git a/examples/stackdriver-trace/index.js b/examples/stackdriver-trace/index.js index 809fc4e8f0..277ddba431 100644 --- a/examples/stackdriver-trace/index.js +++ b/examples/stackdriver-trace/index.js @@ -8,7 +8,7 @@ const { StackdriverTraceExporter } = require('@opentelemetry/exporter-stackdrive // Initialize an exporter const exporter = new StackdriverTraceExporter({ serviceName: 'basic-service', - logger: new opentelemetry.ConsoleLogger() + logger: new opentelemetry.ConsoleLogger(), }); const tracer = new BasicTracer(); @@ -22,17 +22,17 @@ opentelemetry.initGlobalTracer(tracer); // Create a span. A span must be closed. const root = opentelemetry.getTracer().startSpan('main'); const related = opentelemetry.getTracer().startSpan('related', { - links: [{ spanContext: root.context() }] + links: [{ spanContext: root.context() }], }); -for (let i = 0; i < 10; i++) { +for (let i = 0; i < 10; i += 1) { doWork(root); doWork(related); } // Be sure to end the span. root.setStatus({ - code: CanonicalCode.UNKNOWN -}) + code: CanonicalCode.UNKNOWN, +}); root.end(); related.end(); @@ -43,17 +43,19 @@ function doWork(parent) { // Start another span. In this example, the main method already started a // span, so that'll be the parent span, and this will be a child span. const span = opentelemetry.getTracer().startSpan('doWork', { - parent: parent + parent, }); // simulate some random work. const work = Math.floor(Math.random() * 40000000); - for (let i = 0; i <= work; i++) { } + for (let i = 0; i <= work; i += 1) { + // empty + } if (work % 2 === 1) { span.setStatus({ - code: CanonicalCode.UNKNOWN - }) + code: CanonicalCode.UNKNOWN, + }); } // Set attributes to the span. diff --git a/examples/tracer-web/.eslintrc b/examples/tracer-web/.eslintrc new file mode 100644 index 0000000000..e2338e5e0b --- /dev/null +++ b/examples/tracer-web/.eslintrc @@ -0,0 +1,16 @@ +{ + "env": { + "node": true + }, + "extends": "airbnb-base", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "strict": ["error", "global"], + "no-use-before-define": ["error", "nofunc"], + "no-console": "off", + "import/no-unresolved": "off", + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] + } +} diff --git a/examples/tracer-web/examples/document-load/index.js b/examples/tracer-web/examples/document-load/index.js index bd42b14797..77b7248825 100644 --- a/examples/tracer-web/examples/document-load/index.js +++ b/examples/tracer-web/examples/document-load/index.js @@ -2,43 +2,61 @@ import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing import { WebTracerRegistry } from '@opentelemetry/web'; import { DocumentLoad } from '@opentelemetry/plugin-document-load'; import { ZoneScopeManager } from '@opentelemetry/scope-zone'; -import { CollectorExporter } from '@opentelemetry/exporter-collector' +import { CollectorExporter } from '@opentelemetry/exporter-collector'; const registry = new WebTracerRegistry({ plugins: [ - new DocumentLoad() - ] + new DocumentLoad(), + ], }); registry.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); const registryWithZone = new WebTracerRegistry({ scopeManager: new ZoneScopeManager(), plugins: [ - new DocumentLoad() - ] + new DocumentLoad(), + ], }); registryWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); registryWithZone.addSpanProcessor(new SimpleSpanProcessor(new CollectorExporter())); const tracerWithZone = registryWithZone.getTracer('example-tracer-web'); +let window; console.log('Current span is window', tracerWithZone.getCurrentSpan() === window); +const getData = (url) => new Promise((resolve, reject) => { + // eslint-disable-next-line no-undef + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.send(); + req.onload = () => { + let json; + try { + json = JSON.parse(req.responseText); + } catch (e) { + reject(e); + } + resolve(json); + }; +}); + // example of keeping track of scope between async operations const prepareClickEvent = () => { const url1 = 'https://raw.githubusercontent.com/open-telemetry/opentelemetry-js/master/package.json'; const url2 = 'https://raw.githubusercontent.com/open-telemetry/opentelemetry-js/master/packages/opentelemetry-web/package.json'; + let document; const element = document.getElementById('button1'); - let mainSpan = tracerWithZone.startSpan('main-span'); + const mainSpan = tracerWithZone.startSpan('main-span'); tracerWithZone.bind(element, mainSpan); const onClick = () => { - const span1 = tracerWithZone.startSpan(`files-series-info-1`, { - parent: tracerWithZone.getCurrentSpan() + const span1 = tracerWithZone.startSpan('files-series-info-1', { + parent: tracerWithZone.getCurrentSpan(), }); - const span2 = tracerWithZone.startSpan(`files-series-info-2`, { - parent: tracerWithZone.getCurrentSpan() + const span2 = tracerWithZone.startSpan('files-series-info-2', { + parent: tracerWithZone.getCurrentSpan(), }); tracerWithZone.withSpan(span1, () => { @@ -64,21 +82,4 @@ const prepareClickEvent = () => { element.addEventListener('click', onClick); }; -const getData = (url) => { - return new Promise(async (resolve, reject) => { - const req = new XMLHttpRequest(); - req.open('GET', url, true); - req.send(); - req.onload = function () { - let json; - try { - json = JSON.parse(req.responseText); - } catch (e) { - reject(e); - } - resolve(json); - }; - }); -}; - window.addEventListener('load', prepareClickEvent); diff --git a/examples/tracer-web/examples/xml-http-request/index.js b/examples/tracer-web/examples/xml-http-request/index.js index 9e5ff9d2e3..d6698223d6 100644 --- a/examples/tracer-web/examples/xml-http-request/index.js +++ b/examples/tracer-web/examples/xml-http-request/index.js @@ -1,4 +1,3 @@ -'use strict'; import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; import { WebTracer } from '@opentelemetry/web'; @@ -14,28 +13,41 @@ const webTracerWithZone = new WebTracer({ new XMLHttpRequestPlugin({ ignoreUrls: [/localhost:8090\/sockjs-node/], propagateTraceHeaderCorsUrls: [ - 'https://httpbin.org/get' - ] - }) - ] + 'https://httpbin.org/get', + ], + }), + ], }); webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new CollectorExporter())); +const getData = (url) => new Promise((resolve, _reject) => { + // eslint-disable-next-line no-undef + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Content-Type', 'application/json'); + req.setRequestHeader('Accept', 'application/json'); + req.send(); + req.onload = () => { + resolve(); + }; +}); + // example of keeping track of scope between async operations const prepareClickEvent = () => { const url1 = 'https://httpbin.org/get'; + let document; const element = document.getElementById('button1'); const onClick = () => { - for (let i = 0, j = 5; i < j; i++) { + for (let i = 0, j = 5; i < j; i += 1) { const span1 = webTracerWithZone.startSpan(`files-series-info-${i}`, { - parent: webTracerWithZone.getCurrentSpan() + parent: webTracerWithZone.getCurrentSpan(), }); webTracerWithZone.withSpan(span1, () => { - getData(url1).then((data) => { + getData(url1).then((_data) => { webTracerWithZone.getCurrentSpan().addEvent('fetching-span1-completed'); span1.end(); }); @@ -45,17 +57,5 @@ const prepareClickEvent = () => { element.addEventListener('click', onClick); }; -const getData = (url) => { - return new Promise(async (resolve, reject) => { - const req = new XMLHttpRequest(); - req.open('GET', url, true); - req.setRequestHeader('Content-Type', 'application/json'); - req.setRequestHeader('Accept', 'application/json'); - req.send(); - req.onload = function () { - resolve(); - }; - }); -}; - +let window; window.addEventListener('load', prepareClickEvent); diff --git a/examples/tracer-web/webpack.config.js b/examples/tracer-web/webpack.config.js index a73bffb8e6..8f7fed9410 100644 --- a/examples/tracer-web/webpack.config.js +++ b/examples/tracer-web/webpack.config.js @@ -1,18 +1,19 @@ const webpack = require('webpack'); const webpackMerge = require('webpack-merge'); const path = require('path'); + const directory = path.resolve(__dirname); const common = { mode: 'development', entry: { 'document-load': 'examples/document-load/index.js', - 'xml-http-request': 'examples/xml-http-request/index.js' + 'xml-http-request': 'examples/xml-http-request/index.js', }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js', - sourceMapFilename: '[file].map' + sourceMapFilename: '[file].map', }, target: 'web', module: { @@ -21,35 +22,35 @@ const common = { test: /\.js[x]?$/, exclude: /(node_modules)/, use: { - loader: 'babel-loader' - } + loader: 'babel-loader', + }, }, { test: /\.ts$/, exclude: /(node_modules)/, use: { - loader: 'ts-loader' - } - } - ] + loader: 'ts-loader', + }, + }, + ], }, resolve: { modules: [ path.resolve(directory), - 'node_modules' + 'node_modules', ], - extensions: ['.ts', '.js', '.jsx', '.json'] - } + extensions: ['.ts', '.js', '.jsx', '.json'], + }, }; module.exports = webpackMerge(common, { devtool: 'eval-source-map', devServer: { - contentBase: path.resolve(__dirname) + contentBase: path.resolve(__dirname), }, plugins: [ new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('development') - }) - ] + 'process.env.NODE_ENV': JSON.stringify('development'), + }), + ], }); diff --git a/package.json b/package.json index f2c261d056..8f7b383bd1 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "predocs-test": "yarn docs", "docs-test": "lerna run docs-test", "docs": "lerna run docs", - "docs-deploy": "gh-pages --dist packages/opentelemetry-types/docs/out" + "docs-deploy": "gh-pages --dist packages/opentelemetry-types/docs/out", + "lint-examples": "eslint ./examples/**/*.js", + "fix-examples": "eslint ./examples/**/*.js --fix" }, "repository": "open-telemetry/opentelemetry-js", "keywords": [ @@ -40,6 +42,9 @@ "@commitlint/config-conventional": "^8.2.0", "beautify-benchmark": "^0.2.4", "benchmark": "^2.1.4", + "eslint": "^6.8.0", + "eslint-config-airbnb-base": "^14.0.0", + "eslint-plugin-import": "^2.19.1", "gh-pages": "^2.1.1", "gts": "^1.1.0", "husky": "^3.0.9", From 801df25b9bf7cf91c5cbfff2e9d3edd1b3d8cfb0 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Fri, 24 Jan 2020 10:02:12 -0500 Subject: [PATCH 19/21] fix: unref jaeger socket to prevent process running indefinitely (#715) --- packages/opentelemetry-exporter-jaeger/src/jaeger.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts index 1b1f50e6b3..4ae500f74e 100644 --- a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts +++ b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { SpanExporter, ReadableSpan } from '@opentelemetry/tracing'; import { ExportResult } from '@opentelemetry/base'; -import * as jaegerTypes from './types'; import { NoopLogger } from '@opentelemetry/core'; +import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; import * as types from '@opentelemetry/types'; +import { Socket } from 'dgram'; import { spanToThrift } from './transform'; +import * as jaegerTypes from './types'; /** * Format and sends span information to Jaeger Exporter. @@ -40,6 +41,11 @@ export class JaegerExporter implements SpanExporter { typeof config.flushTimeout === 'number' ? config.flushTimeout : 2000; this._sender = new jaegerTypes.UDPSender(config); + if (this._sender._client instanceof Socket) { + // unref socket to prevent it from keeping the process running + this._sender._client.unref(); + } + this._process = { serviceName: config.serviceName, tags: jaegerTypes.ThriftUtils.getThriftTags(tags), From d3af8c4e0b5cbed8388b87f710989d7d5848eb07 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Fri, 24 Jan 2020 11:56:17 -0500 Subject: [PATCH 20/21] chore: publish docs on all tags (#730) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c9017c895a..49f2fb39df 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -242,7 +242,7 @@ workflows: - lint_&_docs filters: tags: - only: /^v\d+\.\d+\.0$/ + only: /^v\d+\.\d+\.\d$/ branches: ignore: /.*/ - node8 From 69947da0c1f563a71906bb75b02991a749dbb07f Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Fri, 24 Jan 2020 15:06:34 -0800 Subject: [PATCH 21/21] Add benchmark README and latest numbers (#689) * add benchmark README and latest numbers * chore: update readme chore: update readme * chore: update readme chore: update readme * chore: update benchmarks * generate latest benchmark numbers Co-authored-by: Daniel Dyla Co-authored-by: Olivier Albertini --- benchmark/README.md | 67 +++++++++++++++++++++++++++++++++++++++++ benchmark/benchmark.js | 5 ++- benchmark/index.js | 4 +-- benchmark/propagator.js | 4 +-- benchmark/tracer.js | 43 ++++++++++++-------------- 5 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 benchmark/README.md diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000000..0f1e81a9fa --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,67 @@ +# Benchmarks + +## How to run + +To run your benchmark, just: +```sh +$ npm run bench +``` + +The minimum sample size is set to 10 to perform statistical analysis on benchmark, you can re-configure that in `benchmark.js`. + +> NOTE: If you're interested in writing benchmark for other APIs, please write a benchmark in the `benchmark/index.js` module. Please refer to the `benchmark/tracer.js` or `benchmark/propagator.js` for more comprehensive examples. + +## Results + +### `v0.3.3` release + +``` +Beginning NoopTracerRegistry Benchmark... + 5 tests completed. + + #startSpan x 731,516,636 ops/sec ±2.57% (20 runs sampled) + #startSpan:parent x 744,353,590 ops/sec ±3.03% (20 runs sampled) + #startSpan with attribute x 737,451,332 ops/sec ±3.75% (20 runs sampled) + #startSpan with 30 attributes x 1,658,688 ops/sec ±1.23% (20 runs sampled) + #startSpan with 100 attributes x 535,082 ops/sec ±1.55% (20 runs sampled) + +Beginning BasicTracerRegistry Benchmark... + 5 tests completed. + + #startSpan x 80,633 ops/sec ±3.57% (20 runs sampled) + #startSpan:parent x 56,228 ops/sec ±2.18% (20 runs sampled) + #startSpan with attribute x 86,710 ops/sec ±1.80% (20 runs sampled) + #startSpan with 30 attributes x 36,331 ops/sec ±1.29% (20 runs sampled) + #startSpan with 100 attributes x 3,549 ops/sec ±3.59% (20 runs sampled) + +Beginning BasicTracerRegistry with SimpleSpanProcessor Benchmark... + 5 tests completed. + + #startSpan x 74,539 ops/sec ±4.49% (20 runs sampled) + #startSpan:parent x 48,953 ops/sec ±4.98% (20 runs sampled) + #startSpan with attribute x 79,686 ops/sec ±2.54% (20 runs sampled) + #startSpan with 30 attributes x 26,491 ops/sec ±13.68% (20 runs sampled) + #startSpan with 100 attributes x 2,464 ops/sec ±19.64% (20 runs sampled) + +Beginning BasicTracerRegistry with BatchSpanProcessor Benchmark... + 5 tests completed. + + #startSpan x 74,974 ops/sec ±3.57% (20 runs sampled) + #startSpan:parent x 42,390 ops/sec ±20.68% (20 runs sampled) + #startSpan with attribute x 76,497 ops/sec ±2.93% (20 runs sampled) + #startSpan with 30 attributes x 33,042 ops/sec ±2.03% (20 runs sampled) + #startSpan with 100 attributes x 3,459 ops/sec ±4.56% (20 runs sampled) + + +Beginning B3Format Benchmark... + 2 tests completed. + + #Inject x 5,086,366 ops/sec ±3.18% (100 runs sampled) + #Extract x 4,859,557 ops/sec ±3.80% (100 runs sampled) + +Beginning HttpTraceContext Benchmark... + 2 tests completed. + + #Inject x 13,660,710 ops/sec ±1.84% (100 runs sampled) + #Extract x 1,692,010 ops/sec ±0.83% (100 runs sampled) +``` diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js index cb71c03a87..8bc04a47b5 100644 --- a/benchmark/benchmark.js +++ b/benchmark/benchmark.js @@ -4,10 +4,9 @@ const Benchmark = require('benchmark'); const benchmarks = require('beautify-benchmark'); Benchmark.options.maxTime = 0; -// @todo : Change it to between 50-100 or keep it random. -Benchmark.options.minSamples = 10; -module.exports = () => { +module.exports = (minSamples) => { + Benchmark.options.minSamples = minSamples; const suite = new Benchmark.Suite(); return suite diff --git a/benchmark/index.js b/benchmark/index.js index ebf5aff932..745b93b75a 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -3,5 +3,5 @@ const execSync = require('child_process').execSync; const exec = cmd => execSync(cmd, { stdio: [0, 1, 2] }); -exec('node benchmark/tracer'); -exec('node benchmark/propagator'); +exec('node benchmark/tracer.js'); +exec('node benchmark/propagator.js'); diff --git a/benchmark/propagator.js b/benchmark/propagator.js index f28a78a302..0c5c137c26 100644 --- a/benchmark/propagator.js +++ b/benchmark/propagator.js @@ -1,7 +1,7 @@ 'use strict'; const benchmark = require('./benchmark'); -const opentelemetry = require('@opentelemetry/core'); +const opentelemetry = require('../packages/opentelemetry-core'); const setups = [ { @@ -26,7 +26,7 @@ const setups = [ for (const setup of setups) { console.log(`Beginning ${setup.name} Benchmark...`); const propagator = setup.propagator; - const suite = benchmark() + const suite = benchmark(100) .add('#Inject', function () { propagator.inject({ traceId: 'd4cda95b652f4a1592b449d5929fda1b', diff --git a/benchmark/tracer.js b/benchmark/tracer.js index 83700b0171..a1a6c360cd 100644 --- a/benchmark/tracer.js +++ b/benchmark/tracer.js @@ -1,28 +1,34 @@ 'use strict'; const benchmark = require('./benchmark'); -const opentelemetry = require('@opentelemetry/core'); -const { BasicTracerRegistry, BatchSpanProcessor, InMemorySpanExporter, SimpleSpanProcessor } = require('@opentelemetry/tracing'); -const { NodeTracerRegistry } = require('@opentelemetry/node'); +const opentelemetry = require('../packages/opentelemetry-core'); +const { BasicTracerRegistry, BatchSpanProcessor, InMemorySpanExporter, SimpleSpanProcessor } = require('../packages/opentelemetry-tracing'); -const exporter = new InMemorySpanExporter(); const logger = new opentelemetry.NoopLogger(); const setups = [ + { + name: 'NoopTracerRegistry', + registry: opentelemetry.getTracerRegistry() + }, { name: 'BasicTracerRegistry', registry: new BasicTracerRegistry({ logger }) }, { - name: 'NodeTracerRegistry', - registry: new NodeTracerRegistry({ logger }) + name: 'BasicTracerRegistry with SimpleSpanProcessor', + registry: getRegistry(new SimpleSpanProcessor(new InMemorySpanExporter())) + }, + { + name: 'BasicTracerRegistry with BatchSpanProcessor', + registry: getRegistry(new BatchSpanProcessor(new InMemorySpanExporter())) } ]; for (const setup of setups) { console.log(`Beginning ${setup.name} Benchmark...`); const tracer = setup.registry.getTracer("benchmark"); - const suite = benchmark() + const suite = benchmark(20) .add('#startSpan', function () { const span = tracer.startSpan('op'); span.end(); @@ -51,25 +57,14 @@ for (const setup of setups) { span.setAttribute('attr-key-' + j, 'attr-value-' + j); } span.end(); - }) - .add('#startSpan with SimpleSpanProcessor', function () { - const simpleSpanProcessor = new SimpleSpanProcessor(exporter); - - registry.addSpanProcessor(simpleSpanProcessor); - const span = tracer.startSpan('op'); - span.end(); - - simpleSpanProcessor.shutdown(); - }) - .add('#startSpan with BatchSpanProcessor', function () { - const batchSpanProcessor = new BatchSpanProcessor(exporter); - - registry.addSpanProcessor(batchSpanProcessor); - const span = tracer.startSpan('op'); - span.end(); - batchSpanProcessor.shutdown(); }); // run async suite.run({ async: false }); } +function getRegistry(processor) { + const registry = new BasicTracerRegistry({ logger }); + registry.addSpanProcessor(processor); + return registry; +} +