@expo/event-log 0.0.1-init

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @expo/event-log
2
+
3
+ ## 0.0.1
4
+
5
+ Initial Release.
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) Phil Pluckthun,
4
+ Copyright (c) 650 Industries, Inc. (aka Expo),
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ <div align="center">
2
+ <h2>@expo/event-log</h2>
3
+ <strong>Low-overhead structured event logs for Expo CLI processes</strong>
4
+ <br />
5
+ <br />
6
+ </div>
7
+
8
+ `@expo/event-log` is a structured event logger for command-line tools. It writes JSONL logs
9
+ that can be replayed, tailed, inspected, or exported to trace formats.
10
+
11
+ In short, `@expo/event-log`,
12
+
13
+ - keeps logging cheap enough to leave enabled
14
+ - writes bounded session logs to the system temporary directory
15
+ - forwards worker and child-process events into the same session
16
+ - lets tools and agents tap into running commands
17
+ - derives typed event payloads through TypeScript declaration merging
18
+
19
+ ## Usage
20
+
21
+ Install logging once when a CLI command starts:
22
+
23
+ ```ts
24
+ import { installEventLogger } from '@expo/event-log';
25
+
26
+ installEventLogger({ command: 'expo start -p web', version: '1.0.0' });
27
+ ```
28
+
29
+ Create a logger in any package:
30
+
31
+ ```ts
32
+ import { events } from '@expo/event-log';
33
+
34
+ const log = events('metro');
35
+
36
+ log('ready', { port: 8081 });
37
+
38
+ const end = log.span('bundle', { platform: 'ios' });
39
+ end('bundle', { cached: false });
40
+ ```
41
+
42
+ Extend `EventRegistry` to type event payloads:
43
+
44
+ ```ts
45
+ declare module '@expo/event-log' {
46
+ interface EventRegistry {
47
+ 'metro:ready': { port: number };
48
+ 'metro:bundle': { platform?: string; cached?: boolean };
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## CLI
54
+
55
+ Use the CLI to find sessions, replay logs, or export traces:
56
+
57
+ ```sh
58
+ event-log ps --json
59
+ event-log tap "expo start" --filter metro:* --tail
60
+ event-log export "expo start" --format chrome-trace -o trace.json
61
+ event-log export "expo start" --format opentelemetry -o otel.json
62
+ event-log clean --json
63
+ ```
64
+
65
+ Selectors match a session by PID, command, session directory, or working directory.
66
+ `tap` and `export` replay retained history first. `--tail` appends live events.
67
+
68
+ ## API
69
+
70
+ - `events(category)` creates a typed logger
71
+ - `installEventLogger(options)` installs logging for the process
72
+ - `isEventLoggerActive()` returns the active destination, or `null`
73
+ - `list(options)` lists known sessions
74
+ - `tap(sessionDir, options)` replays and optionally follows a session
package/bin/cli.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ (async function main() {
4
+ let cli;
5
+ try {
6
+ cli = await import('../dist/expo-event-log-cli.mjs');
7
+ } catch {
8
+ cli = require('../dist/expo-event-log-cli.js');
9
+ }
10
+
11
+ try {
12
+ process.exitCode = await cli.main(process.argv.slice(2));
13
+ } catch (error) {
14
+ process.stderr.write(
15
+ `${error instanceof Error ? error.message : String(error)}\n`
16
+ );
17
+ process.exitCode = 1;
18
+ }
19
+ })();
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "expo-event-log-cli",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "main": "../dist/expo-event-log-cli.js",
6
+ "module": "../dist/expo-event-log-cli.mjs",
7
+ "types": "../dist/expo-event-log-cli.d.ts",
8
+ "source": "../src/cli.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "../dist/expo-event-log-cli.d.ts",
12
+ "import": "../dist/expo-event-log-cli.mjs",
13
+ "require": "../dist/expo-event-log-cli.js",
14
+ "source": "../src/cli.ts"
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,536 @@
1
+ var e = require("node:console");
2
+
3
+ var t = require("node:fs/promises");
4
+
5
+ var n = require("node:path");
6
+
7
+ var r = require("node:net");
8
+
9
+ var o = require("node:readline");
10
+
11
+ var i = require("node:os");
12
+
13
+ var s = require("node:fs");
14
+
15
+ const c = n.join(i.tmpdir(), "expo-event-log");
16
+
17
+ const l = {
18
+ meta: "meta.json",
19
+ liveSocket: "live.sock",
20
+ ipcSocket: "ipc.sock"
21
+ };
22
+
23
+ const a = Symbol.for("@expo/event-log/session-base-dir-override");
24
+
25
+ function cleanStaleSessionsSync() {
26
+ const e = function getSessionEntries() {
27
+ try {
28
+ const e = getSessionBaseDir();
29
+ return s.readdirSync(e, {
30
+ withFileTypes: !0
31
+ }).filter(e => e.isDirectory()).map(t => {
32
+ const r = n.join(e, t.name);
33
+ return {
34
+ dir: r,
35
+ meta: readMetaSync(r),
36
+ alive: !1
37
+ };
38
+ }).filter(e => !!e.meta && isCompatibleSessionMeta(e.meta)).map(e => ({
39
+ ...e,
40
+ alive: isPidAlive(e.meta.pid)
41
+ }));
42
+ } catch {
43
+ return [];
44
+ }
45
+ }();
46
+ const t = Date.now();
47
+ let r = 0;
48
+ for (const n of e) {
49
+ if (!n.alive && t - n.meta.startedAt > 6048e5) {
50
+ s.rmSync(n.dir, {
51
+ recursive: !0,
52
+ force: !0
53
+ });
54
+ r++;
55
+ }
56
+ }
57
+ const o = e.filter(e => s.existsSync(e.dir)).sort((e, t) => t.meta.startedAt - e.meta.startedAt);
58
+ for (const e of o.filter(e => !e.alive).slice(100)) {
59
+ s.rmSync(e.dir, {
60
+ recursive: !0,
61
+ force: !0
62
+ });
63
+ r++;
64
+ }
65
+ return r;
66
+ }
67
+
68
+ function readMetaSync(e) {
69
+ try {
70
+ const t = JSON.parse(s.readFileSync(n.join(e, l.meta), "utf8"));
71
+ return isCompatibleSessionMeta(t) ? t : null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function getSessionBaseDir() {
78
+ return globalThis[a] || c;
79
+ }
80
+
81
+ function isPidAlive(e) {
82
+ if (!e || e === process.pid) {
83
+ return !0;
84
+ }
85
+ try {
86
+ process.kill(e, 0);
87
+ return !0;
88
+ } catch {
89
+ return !1;
90
+ }
91
+ }
92
+
93
+ function isCompatibleSessionMeta(e) {
94
+ return !!e && "object" == typeof e && 1 === e.formatVersion;
95
+ }
96
+
97
+ async function listSessions(e = {}) {
98
+ cleanStaleSessionsSync();
99
+ const r = getSessionBaseDir();
100
+ const o = await t.readdir(r, {
101
+ withFileTypes: !0
102
+ }).catch(() => []);
103
+ const i = [];
104
+ for (const e of o) {
105
+ if (!e.isDirectory()) {
106
+ continue;
107
+ }
108
+ const t = n.join(r, e.name);
109
+ const o = readMetaSync(t);
110
+ if (!o) {
111
+ continue;
112
+ }
113
+ i.push({
114
+ id: e.name,
115
+ pid: o.pid,
116
+ formatVersion: o.formatVersion,
117
+ alive: isPidAlive(o.pid),
118
+ startedAt: o.startedAt,
119
+ command: o.command,
120
+ cwd: o.cwd,
121
+ version: o.version,
122
+ origin: o.origin,
123
+ sessionDir: t
124
+ });
125
+ }
126
+ return function filterSessions(e, t) {
127
+ const n = t?.trim();
128
+ if (!n) {
129
+ return e;
130
+ }
131
+ const r = e.filter(e => function matchesSessionExactly(e, t) {
132
+ return sessionSearchValues(e).some(e => e === t);
133
+ }(e, n));
134
+ if (r.length) {
135
+ return r;
136
+ }
137
+ const o = n.toLowerCase();
138
+ return e.filter(e => sessionSearchValues(e).some(e => e.toLowerCase().includes(o)));
139
+ }(i, e.selector).sort((e, t) => t.startedAt - e.startedAt);
140
+ }
141
+
142
+ async function* readLines(e) {
143
+ let t = "";
144
+ for await (const n of e.createReadStream({
145
+ encoding: "utf8"
146
+ })) {
147
+ t += n;
148
+ let e = -1;
149
+ while ((e = t.indexOf("\n")) >= 0) {
150
+ const n = t.slice(0, e);
151
+ t = t.slice(e + 1);
152
+ if (n) {
153
+ yield n;
154
+ }
155
+ }
156
+ }
157
+ if (t) {
158
+ yield t;
159
+ }
160
+ }
161
+
162
+ function parseEventLine(e, t = {}, n = compileEventFilter(t.filter), r = parseSince(t.since)) {
163
+ try {
164
+ const t = JSON.parse(e);
165
+ if (!t || "string" != typeof t._e || "number" != typeof t._t) {
166
+ return null;
167
+ }
168
+ if (null != r && t._t < r) {
169
+ return null;
170
+ }
171
+ if (!matchesEventFilter(t._e, n)) {
172
+ return null;
173
+ }
174
+ return t;
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ function parseSince(e) {
181
+ if (null == e) {
182
+ return;
183
+ }
184
+ if (e instanceof Date) {
185
+ const t = e.getTime();
186
+ if (Number.isFinite(t)) {
187
+ return t;
188
+ }
189
+ throw new Error(`Invalid since: ${e.toString()}`);
190
+ }
191
+ if ("number" == typeof e) {
192
+ if (!Number.isFinite(e) || e < 0) {
193
+ throw new Error(`Invalid since: ${e}`);
194
+ }
195
+ return normalizeUnixTimestamp(e);
196
+ }
197
+ const t = e.trim();
198
+ if (!t) {
199
+ return;
200
+ }
201
+ if (/^\d+(?:\.\d+)?$/.test(t)) {
202
+ return normalizeUnixTimestamp(Number(t));
203
+ }
204
+ const n = function parseDuration(e) {
205
+ const t = e.trim().toLowerCase();
206
+ if (!t) {
207
+ return;
208
+ }
209
+ let n = 0;
210
+ let r = 0;
211
+ const o = /(\d+(?:\.\d+)?)\s*(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)/gy;
212
+ while (r < t.length) {
213
+ o.lastIndex = r;
214
+ const e = o.exec(t);
215
+ if (!e) {
216
+ return;
217
+ }
218
+ const i = Number(e[1]);
219
+ if (!Number.isFinite(i)) {
220
+ return;
221
+ }
222
+ n += i * durationUnitToMs(e[2]);
223
+ r = o.lastIndex;
224
+ while (" " === t[r] || "," === t[r]) {
225
+ r++;
226
+ }
227
+ }
228
+ return n;
229
+ }(t);
230
+ if (null != n) {
231
+ return Date.now() - n;
232
+ }
233
+ const r = Date.parse(t);
234
+ if (Number.isFinite(r)) {
235
+ return r;
236
+ }
237
+ throw new Error(`Invalid since: ${e}`);
238
+ }
239
+
240
+ function compileEventFilter(e) {
241
+ const t = function normalizeEventFilter(e) {
242
+ const t = Array.isArray(e) ? e : e ? [ e ] : [];
243
+ return t.flatMap(e => e.split(",")).map(e => e.trim()).filter(Boolean);
244
+ }(e);
245
+ if (!t.length) {
246
+ return null;
247
+ }
248
+ return new RegExp(`^(?:${t.map(eventPatternToRegex).join("|")})$`);
249
+ }
250
+
251
+ function matchesEventFilter(e, t) {
252
+ return !t || t.test(e);
253
+ }
254
+
255
+ function formatSessionSelector(e) {
256
+ return `${e.pid} (${e.command})`;
257
+ }
258
+
259
+ function eventPatternToRegex(e) {
260
+ return (e.includes(":") || e.includes("*") ? e : `${e}:*`).split("*").map(escapeRegex).join(".*");
261
+ }
262
+
263
+ function escapeRegex(e) {
264
+ return e.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
265
+ }
266
+
267
+ function normalizeUnixTimestamp(e) {
268
+ return e < 1e10 ? 1e3 * e : e;
269
+ }
270
+
271
+ function durationUnitToMs(e) {
272
+ if ("ms" === e || "msec" === e || "msecs" === e || "millisecond" === e || "milliseconds" === e) {
273
+ return 1;
274
+ }
275
+ if ("s" === e || "sec" === e || "secs" === e || "second" === e || "seconds" === e) {
276
+ return 1e3;
277
+ }
278
+ if ("m" === e || "min" === e || "mins" === e || "minute" === e || "minutes" === e) {
279
+ return 6e4;
280
+ }
281
+ if ("h" === e || "hr" === e || "hrs" === e || "hour" === e || "hours" === e) {
282
+ return 36e5;
283
+ }
284
+ return 1;
285
+ }
286
+
287
+ function readLiveLine(e, t) {
288
+ if (!t) {
289
+ return e.next();
290
+ }
291
+ if (t.aborted) {
292
+ return Promise.resolve(null);
293
+ }
294
+ return new Promise(n => {
295
+ const onAbort = () => n(null);
296
+ t.addEventListener("abort", onAbort, {
297
+ once: !0
298
+ });
299
+ e.next().then(e => {
300
+ t.removeEventListener("abort", onAbort);
301
+ n(t.aborted ? null : e);
302
+ });
303
+ });
304
+ }
305
+
306
+ function resetAbortTimer(e, t, n) {
307
+ clearTimeout(e);
308
+ return setAbortTimer(t, n);
309
+ }
310
+
311
+ function setAbortTimer(e, t) {
312
+ const n = setTimeout(() => e.abort(), t);
313
+ n.unref?.();
314
+ return n;
315
+ }
316
+
317
+ function sessionSearchValues(e) {
318
+ return [ e.id, String(e.pid), e.sessionDir, e.cwd, e.command, e.origin?.cwd, e.origin?.argv.join(" "), e.origin?.execPath, e.origin?.env?.npmLifecycleEvent, e.origin?.env?.npmPackageName ].filter(e => !!e);
319
+ }
320
+
321
+ function tryConnect(e, t) {
322
+ return new Promise(n => {
323
+ const o = r.connect(e);
324
+ const cleanup = () => {
325
+ o.off("connect", onConnect);
326
+ o.off("error", onError);
327
+ t?.removeEventListener("abort", onAbort);
328
+ };
329
+ const onConnect = () => {
330
+ cleanup();
331
+ n(o);
332
+ };
333
+ const onError = () => {
334
+ cleanup();
335
+ o.destroy();
336
+ n(null);
337
+ };
338
+ const onAbort = () => {
339
+ cleanup();
340
+ o.destroy();
341
+ n(null);
342
+ };
343
+ o.once("connect", onConnect);
344
+ o.once("error", onError);
345
+ t?.addEventListener("abort", onAbort, {
346
+ once: !0
347
+ });
348
+ });
349
+ }
350
+
351
+ exports.DEFAULT_SEGMENTS = 3;
352
+
353
+ exports.DEFAULT_SEGMENT_SIZE = 524288;
354
+
355
+ exports.EVENT_LOG_FORMAT_VERSION = 1;
356
+
357
+ exports.EVENT_LOG_STATE_VERSION = 1;
358
+
359
+ exports.INTERNAL_IPC_ENV = "__eventLogIpc";
360
+
361
+ exports.INTERNAL_PROCESS_ORIGIN_ENV = "__eventLogProcessOrigin";
362
+
363
+ exports.LOG_EVENTS_ENV = "LOG_EVENTS";
364
+
365
+ exports.SESSION_FILES = l;
366
+
367
+ exports.cleanStaleSessionsSync = cleanStaleSessionsSync;
368
+
369
+ exports.compileEventFilter = compileEventFilter;
370
+
371
+ exports.getSessionBaseDir = getSessionBaseDir;
372
+
373
+ exports.listSessions = listSessions;
374
+
375
+ exports.matchesEventFilter = matchesEventFilter;
376
+
377
+ exports.parseEventLine = parseEventLine;
378
+
379
+ exports.parseSince = parseSince;
380
+
381
+ exports.redirectConsoleForFd = function redirectConsoleForFd(t) {
382
+ if (1 === t) {
383
+ const t = process.stderr;
384
+ Object.defineProperty(process, "stdout", {
385
+ get: () => t
386
+ });
387
+ globalThis.console = new e.Console(t, t);
388
+ } else if (2 === t) {
389
+ const t = process.stdout;
390
+ Object.defineProperty(process, "stderr", {
391
+ get: () => t
392
+ });
393
+ globalThis.console = new e.Console(t, t);
394
+ }
395
+ };
396
+
397
+ exports.redirectConsoleToStderr = function redirectConsoleToStderr() {
398
+ globalThis.console = new e.Console(process.stderr, process.stderr);
399
+ };
400
+
401
+ exports.resolveSession = async function resolveSession(e) {
402
+ const t = await listSessions({
403
+ selector: e
404
+ });
405
+ const n = 1 === t.length ? t[0] : null;
406
+ if (!n) {
407
+ throw new Error(null != e ? t.length ? `Ambiguous event-log session "${e}"; specify one of: ${t.map(formatSessionSelector).join(", ")}` : `No event-log session matching "${e}"` : t.length ? `Ambiguous event-log session; specify one of: ${t.map(formatSessionSelector).join(", ")}` : "No event-log sessions found");
408
+ }
409
+ return n;
410
+ };
411
+
412
+ exports.tap = async function* tap(e, r = {}) {
413
+ const i = !0 === r.follow;
414
+ const s = parseSince(r.since);
415
+ const c = compileEventFilter(r.filter);
416
+ const a = function createTapAbortController(e, t) {
417
+ if (!t || null == e.timeout && null == e.idleTimeout) {
418
+ return;
419
+ }
420
+ const n = new AbortController;
421
+ e.signal?.addEventListener("abort", () => n.abort(), {
422
+ once: !0
423
+ });
424
+ if (null != e.timeout) {
425
+ setAbortTimer(n, e.timeout);
426
+ }
427
+ return n;
428
+ }(r, i);
429
+ const u = a?.signal ?? r.signal;
430
+ const f = i ? await async function connectLive(e, t) {
431
+ const r = n.join(e, l.liveSocket);
432
+ const i = await async function connectWithRetry(e, t) {
433
+ for (let n = 0; n < 20 && !t?.aborted; n++) {
434
+ const n = await tryConnect(e, t);
435
+ if (n) {
436
+ return n;
437
+ }
438
+ await new Promise(e => setTimeout(e, 50));
439
+ }
440
+ return null;
441
+ }(r, t);
442
+ if (!i) {
443
+ return;
444
+ }
445
+ const s = o.createInterface({
446
+ input: i
447
+ });
448
+ const c = [];
449
+ const a = [];
450
+ let u = !1;
451
+ const push = e => {
452
+ const t = a.shift();
453
+ if (t) {
454
+ t(e);
455
+ } else if (null != e) {
456
+ c.push(e);
457
+ }
458
+ };
459
+ s.on("line", e => push(e));
460
+ s.on("close", () => {
461
+ u = !0;
462
+ push(null);
463
+ });
464
+ i.on("error", () => {
465
+ u = !0;
466
+ push(null);
467
+ });
468
+ t?.addEventListener("abort", () => i.destroy(), {
469
+ once: !0
470
+ });
471
+ return {
472
+ buffer: c,
473
+ next() {
474
+ if (c.length) {
475
+ return Promise.resolve(c.shift());
476
+ }
477
+ if (u) {
478
+ return Promise.resolve(null);
479
+ }
480
+ return new Promise(e => a.push(e));
481
+ }
482
+ };
483
+ }(e, u) : void 0;
484
+ const m = await async function openHistoryFiles(e) {
485
+ const r = readMetaSync(e)?.maxSegments ?? 3;
486
+ const o = await Promise.all(Array.from({
487
+ length: r
488
+ }, (r, o) => t.open(n.join(e, `${o}.jsonl`), "r").catch(() => null)));
489
+ return o.filter(e => null != e).reverse();
490
+ }(e);
491
+ let d;
492
+ try {
493
+ for (const e of m) {
494
+ for await (const t of readLines(e)) {
495
+ const e = parseEventLine(t, r, c, s);
496
+ if (e) {
497
+ yield e;
498
+ }
499
+ }
500
+ }
501
+ } finally {
502
+ await Promise.all(m.map(e => e.close().catch(() => {})));
503
+ }
504
+ if (!f) {
505
+ return;
506
+ }
507
+ if (null != r.idleTimeout && a) {
508
+ d = setAbortTimer(a, r.idleTimeout);
509
+ }
510
+ for (const e of f.buffer.splice(0)) {
511
+ const t = parseEventLine(e, r, c, s);
512
+ if (t) {
513
+ if (d) {
514
+ d = resetAbortTimer(d, a, r.idleTimeout);
515
+ }
516
+ yield t;
517
+ }
518
+ }
519
+ while (!u?.aborted) {
520
+ const e = await readLiveLine(f, u);
521
+ if (null == e) {
522
+ break;
523
+ }
524
+ if (d) {
525
+ d = resetAbortTimer(d, a, r.idleTimeout);
526
+ }
527
+ const t = parseEventLine(e, r, c, s);
528
+ if (t) {
529
+ yield t;
530
+ }
531
+ }
532
+ if (d) {
533
+ clearTimeout(d);
534
+ }
535
+ };
536
+ //# sourceMappingURL=tap-chunk.js.map