@agoric/telemetry 0.6.3-upgrade-17-dev-ec448b0.0 → 0.6.3-upgrade-18-dev-ef001c0.0

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 CHANGED
@@ -3,30 +3,6 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ### [0.6.3-u17.1](https://github.com/Agoric/agoric-sdk/compare/@agoric/telemetry@0.6.3-u17.0...@agoric/telemetry@0.6.3-u17.1) (2024-09-19)
7
-
8
- **Note:** Version bump only for package @agoric/telemetry
9
-
10
-
11
-
12
-
13
-
14
- ### [0.6.3-u17.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/telemetry@0.6.2...@agoric/telemetry@0.6.3-u17.0) (2024-09-17)
15
-
16
-
17
- ### Features
18
-
19
- * **cosmic-swingset:** add JS upgrade plan handler stub ([655133e](https://github.com/Agoric/agoric-sdk/commit/655133ed909b5d632dc033e992214a7b6a1b5ab1))
20
- * simple CircularBuffer with fs offsets ([8d9cb7a](https://github.com/Agoric/agoric-sdk/commit/8d9cb7abe96e8905f5aaa0927e02914ef09279c4))
21
- * use writeSync slogSender ([47a2add](https://github.com/Agoric/agoric-sdk/commit/47a2adda72a5377eda181a425130cdc5a7fd7ff5))
22
-
23
-
24
- ### Bug Fixes
25
-
26
- * ensure script main rejections exit with error ([abdab87](https://github.com/Agoric/agoric-sdk/commit/abdab879014a5c3124ebd0e9246995ac6b1ce6e5))
27
-
28
-
29
-
30
6
  ### [0.6.2](https://github.com/Agoric/agoric-sdk/compare/@agoric/telemetry@0.6.1...@agoric/telemetry@0.6.2) (2023-06-02)
31
7
 
32
8
  **Note:** Version bump only for package @agoric/telemetry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/telemetry",
3
- "version": "0.6.3-upgrade-17-dev-ec448b0.0+ec448b0",
3
+ "version": "0.6.3-upgrade-18-dev-ef001c0.0+ef001c0",
4
4
  "description": "Agoric's telemetry implementation",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/Agoric/agoric-sdk",
@@ -22,26 +22,29 @@
22
22
  "author": "Agoric",
23
23
  "license": "Apache-2.0",
24
24
  "dependencies": {
25
- "@agoric/internal": "0.4.0-upgrade-17-dev-ec448b0.0+ec448b0",
26
- "@agoric/store": "0.9.3-upgrade-17-dev-ec448b0.0+ec448b0",
27
- "@endo/errors": "^1.2.5",
28
- "@endo/init": "^1.1.4",
29
- "@endo/marshal": "^1.5.3",
30
- "@endo/stream": "^1.2.5",
25
+ "@agoric/internal": "0.3.3-upgrade-18-dev-ef001c0.0+ef001c0",
26
+ "@agoric/store": "0.9.3-upgrade-18-dev-ef001c0.0+ef001c0",
27
+ "@endo/errors": "^1.2.7",
28
+ "@endo/init": "^1.1.6",
29
+ "@endo/marshal": "^1.6.1",
30
+ "@endo/stream": "^1.2.7",
31
31
  "@opentelemetry/api": "~1.3.0",
32
+ "@opentelemetry/api-logs": "0.53.0",
33
+ "@opentelemetry/exporter-logs-otlp-http": "0.53.0",
32
34
  "@opentelemetry/exporter-prometheus": "~0.35.0",
33
35
  "@opentelemetry/exporter-trace-otlp-http": "~0.35.0",
34
36
  "@opentelemetry/resources": "~1.9.0",
37
+ "@opentelemetry/sdk-logs": "0.53.0",
35
38
  "@opentelemetry/sdk-metrics": "~1.9.0",
36
39
  "@opentelemetry/sdk-trace-base": "~1.9.0",
37
- "@opentelemetry/semantic-conventions": "~1.9.0",
40
+ "@opentelemetry/semantic-conventions": "~1.27.0",
38
41
  "anylogger": "^0.21.0",
39
42
  "better-sqlite3": "^9.1.1",
40
43
  "tmp": "^0.2.1"
41
44
  },
42
45
  "devDependencies": {
43
- "@endo/lockdown": "^1.0.10",
44
- "@endo/ses-ava": "^1.2.5",
46
+ "@endo/lockdown": "^1.0.12",
47
+ "@endo/ses-ava": "^1.2.7",
45
48
  "ava": "^5.3.0",
46
49
  "c8": "^9.1.0",
47
50
  "tmp": "^0.2.1"
@@ -65,5 +68,5 @@
65
68
  "typeCoverage": {
66
69
  "atLeast": 87.03
67
70
  },
68
- "gitHead": "ec448b081ac21cbe217f210e06f0b8f7989e73d6"
71
+ "gitHead": "ef001c015ac66ff65840bee1f7fd3847e999154d"
69
72
  }
@@ -0,0 +1,42 @@
1
+ /* eslint-env node */
2
+
3
+ import { makeFsStreamWriter } from '@agoric/internal/src/node/fs-stream.js';
4
+ import { makeContextualSlogProcessor } from './context-aware-slog.js';
5
+ import { serializeSlogObj } from './serialize-slog-obj.js';
6
+
7
+ /**
8
+ * @param {import('./index.js').MakeSlogSenderOptions} options
9
+ */
10
+ export const makeSlogSender = async options => {
11
+ const { CHAIN_ID, CONTEXTUAL_SLOGFILE } = options.env || {};
12
+ if (!CONTEXTUAL_SLOGFILE)
13
+ return console.warn(
14
+ 'Ignoring invocation of slogger "context-aware-slog-file" without the presence of "CONTEXTUAL_SLOGFILE"',
15
+ );
16
+
17
+ const stream = await makeFsStreamWriter(CONTEXTUAL_SLOGFILE);
18
+
19
+ if (!stream)
20
+ return console.error(
21
+ `Couldn't create a write stream on file "${CONTEXTUAL_SLOGFILE}"`,
22
+ );
23
+
24
+ const contextualSlogProcessor = makeContextualSlogProcessor({
25
+ 'chain-id': CHAIN_ID,
26
+ });
27
+
28
+ /**
29
+ * @param {import('./context-aware-slog.js').Slog} slog
30
+ */
31
+ const slogSender = slog => {
32
+ const contextualizedSlog = contextualSlogProcessor(slog);
33
+
34
+ // eslint-disable-next-line prefer-template
35
+ stream.write(serializeSlogObj(contextualizedSlog) + '\n').catch(() => {});
36
+ };
37
+
38
+ return Object.assign(slogSender, {
39
+ forceFlush: () => stream.flush(),
40
+ shutdown: () => stream.close(),
41
+ });
42
+ };
@@ -0,0 +1,383 @@
1
+ /* eslint-env node */
2
+
3
+ /**
4
+ * @typedef {Partial<{
5
+ * 'block.height': Slog['blockHeight'];
6
+ * 'block.time': Slog['blockTime'];
7
+ * 'crank.deliveryNum': Slog['deliveryNum'];
8
+ * 'crank.num': Slog['crankNum'];
9
+ * 'crank.type': Slog['crankType'];
10
+ * 'crank.vatID': Slog['vatID'];
11
+ * init: boolean;
12
+ * replay: boolean;
13
+ * 'run.id': string;
14
+ * 'run.num': string | null;
15
+ * 'run.trigger.blockHeight': Slog['blockHeight'];
16
+ * 'run.trigger.msgIdx': number;
17
+ * 'run.trigger.sender': Slog['sender'];
18
+ * 'run.trigger.source': Slog['source'];
19
+ * 'run.trigger.bundleHash': Slog['endoZipBase64Sha512'];
20
+ * 'run.trigger.time': Slog['blockTime'];
21
+ * 'run.trigger.txHash': string;
22
+ * 'run.trigger.type': string;
23
+ * }>
24
+ * } Context
25
+ *
26
+ * @typedef {{
27
+ * 'crank.syscallNum'?: Slog['syscallNum'];
28
+ * 'process.uptime': Slog['monotime'];
29
+ * } & Context} LogAttributes
30
+ *
31
+ * @typedef {{
32
+ * blockHeight?: number;
33
+ * blockTime?: number;
34
+ * crankNum?: bigint;
35
+ * crankType?: string;
36
+ * deliveryNum?: bigint;
37
+ * inboundNum?: string;
38
+ * monotime: number;
39
+ * remainingBeans?: bigint;
40
+ * replay?: boolean;
41
+ * runNum?: number;
42
+ * sender?: string;
43
+ * source?: string;
44
+ * endoZipBase64Sha512?: string;
45
+ * syscallNum?: number;
46
+ * time: number;
47
+ * type: string;
48
+ * vatID?: string;
49
+ * }} Slog
50
+ */
51
+
52
+ const SLOG_TYPES = {
53
+ CLIST: 'clist',
54
+ CONSOLE: 'console',
55
+ COSMIC_SWINGSET: {
56
+ AFTER_COMMIT_STATS: 'cosmic-swingset-after-commit-stats',
57
+ BEGIN_BLOCK: 'cosmic-swingset-begin-block',
58
+ BOOTSTRAP_BLOCK: {
59
+ FINISH: 'cosmic-swingset-bootstrap-block-finish',
60
+ START: 'cosmic-swingset-bootstrap-block-start',
61
+ },
62
+ COMMIT: {
63
+ FINISH: 'cosmic-swingset-commit-finish',
64
+ START: 'cosmic-swingset-commit-start',
65
+ },
66
+ END_BLOCK: {
67
+ FINISH: 'cosmic-swingset-end-block-finish',
68
+ START: 'cosmic-swingset-end-block-start',
69
+ },
70
+ // eslint-disable-next-line no-restricted-syntax
71
+ RUN: {
72
+ FINISH: 'cosmic-swingset-run-finish',
73
+ START: 'cosmic-swingset-run-start',
74
+ },
75
+ },
76
+ COSMIC_SWINGSET_TRIGGERS: {
77
+ BRIDGE_INBOUND: 'cosmic-swingset-bridge-inbound',
78
+ DELIVER_INBOUND: 'cosmic-swingset-deliver-inbound',
79
+ TIMER_POLL: 'cosmic-swingset-timer-poll',
80
+ INSTALL_BUNDLE: 'cosmic-swingset-install-bundle',
81
+ },
82
+ CRANK: {
83
+ FINISH: 'crank-finish',
84
+ START: 'crank-start',
85
+ },
86
+ DELIVER: 'deliver',
87
+ DELIVER_RESULT: 'deliver-result',
88
+ KERNEL: {
89
+ INIT: {
90
+ FINISH: 'kernel-init-finish',
91
+ START: 'kernel-init-start',
92
+ },
93
+ },
94
+ REPLAY: {
95
+ FINISH: 'finish-replay',
96
+ START: 'start-replay',
97
+ },
98
+ SYSCALL: 'syscall',
99
+ SYSCALL_RESULT: 'syscall-result',
100
+ };
101
+
102
+ /**
103
+ * @template {Record<string, any>} [T={}]
104
+ * @param {T} [staticContext]
105
+ * @param {Partial<{ persistContext: (context: Context) => void; restoreContext: () => Context | null; }>} [persistenceUtils]
106
+ */
107
+ export const makeContextualSlogProcessor = (
108
+ staticContext,
109
+ persistenceUtils = {},
110
+ ) => {
111
+ /** @type Array<Context | null> */
112
+ let [
113
+ blockContext,
114
+ crankContext,
115
+ initContext,
116
+ lastPersistedTriggerContext,
117
+ replayContext,
118
+ triggerContext,
119
+ ] = [null, null, null, null, null, null];
120
+
121
+ /**
122
+ * @param {Context} context
123
+ */
124
+ const persistContext = context => {
125
+ lastPersistedTriggerContext = context;
126
+ return persistenceUtils?.persistContext?.(context);
127
+ };
128
+
129
+ const restoreContext = () => {
130
+ if (!lastPersistedTriggerContext)
131
+ lastPersistedTriggerContext =
132
+ persistenceUtils?.restoreContext?.() || null;
133
+ return lastPersistedTriggerContext;
134
+ };
135
+
136
+ /**
137
+ * @param {Slog} slog
138
+ * @returns {{ attributes: T & LogAttributes, body: Partial<Slog>; timestamp: Slog['time'] }}
139
+ */
140
+ const slogProcessor = ({ monotime, time: timestamp, ...body }) => {
141
+ const finalBody = { ...body };
142
+
143
+ /** @type {{'crank.syscallNum'?: Slog['syscallNum']}} */
144
+ const eventLogAttributes = {};
145
+
146
+ /**
147
+ * Add any before report operations here
148
+ * like setting context data
149
+ */
150
+ switch (body.type) {
151
+ case SLOG_TYPES.KERNEL.INIT.START: {
152
+ initContext = { init: true };
153
+ break;
154
+ }
155
+ case SLOG_TYPES.COSMIC_SWINGSET.BEGIN_BLOCK: {
156
+ blockContext = {
157
+ 'block.height': finalBody.blockHeight,
158
+ 'block.time': finalBody.blockTime,
159
+ };
160
+ break;
161
+ }
162
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.START: {
163
+ blockContext = {
164
+ 'block.height': finalBody.blockHeight || 0,
165
+ 'block.time': finalBody.blockTime,
166
+ };
167
+ break;
168
+ }
169
+ case SLOG_TYPES.COSMIC_SWINGSET.END_BLOCK.START:
170
+ case SLOG_TYPES.COSMIC_SWINGSET.END_BLOCK.FINISH:
171
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.FINISH:
172
+ case SLOG_TYPES.COSMIC_SWINGSET.COMMIT.START:
173
+ case SLOG_TYPES.COSMIC_SWINGSET.COMMIT.FINISH:
174
+ case SLOG_TYPES.COSMIC_SWINGSET.AFTER_COMMIT_STATS: {
175
+ assert(!!blockContext && !triggerContext);
176
+ break;
177
+ }
178
+ case SLOG_TYPES.COSMIC_SWINGSET_TRIGGERS.BRIDGE_INBOUND:
179
+ case SLOG_TYPES.COSMIC_SWINGSET_TRIGGERS.DELIVER_INBOUND: {
180
+ const [blockHeight, txHash, msgIdx] = (
181
+ finalBody.inboundNum || ''
182
+ ).split('-');
183
+ const [, triggerType] =
184
+ /cosmic-swingset-([^-]+)-inbound/.exec(body.type) || [];
185
+
186
+ triggerContext = {
187
+ 'run.num': undefined,
188
+ 'run.id': `${triggerType}-${finalBody.inboundNum}`,
189
+ 'run.trigger.type': triggerType,
190
+ 'run.trigger.source': finalBody.source,
191
+ 'run.trigger.sender': finalBody.sender,
192
+ 'run.trigger.blockHeight': Number(blockHeight),
193
+ 'run.trigger.txHash': txHash,
194
+ 'run.trigger.msgIdx': Number(msgIdx),
195
+ };
196
+ break;
197
+ }
198
+ case SLOG_TYPES.COSMIC_SWINGSET_TRIGGERS.INSTALL_BUNDLE: {
199
+ const [blockHeight, txHash, msgIdx] = (
200
+ finalBody.inboundNum || ''
201
+ ).split('-');
202
+
203
+ const triggerType = 'install-bundle';
204
+
205
+ triggerContext = {
206
+ 'run.num': undefined,
207
+ 'run.id': `${triggerType}-${finalBody.inboundNum}`,
208
+ 'run.trigger.type': triggerType,
209
+ 'run.trigger.bundleHash': finalBody.endoZipBase64Sha512,
210
+ 'run.trigger.blockHeight': Number(blockHeight),
211
+ 'run.trigger.txHash': txHash,
212
+ 'run.trigger.msgIdx': Number(msgIdx),
213
+ };
214
+
215
+ break;
216
+ }
217
+ case SLOG_TYPES.COSMIC_SWINGSET_TRIGGERS.TIMER_POLL: {
218
+ const triggerType = 'timer-poll';
219
+
220
+ triggerContext = {
221
+ 'run.num': undefined,
222
+ 'run.id': `${triggerType}-${finalBody.inboundNum}`,
223
+ 'run.trigger.type': triggerType,
224
+ 'run.trigger.time': finalBody.blockTime,
225
+ 'run.trigger.blockHeight': finalBody.blockHeight,
226
+ };
227
+
228
+ break;
229
+ }
230
+ // eslint-disable-next-line no-restricted-syntax
231
+ case SLOG_TYPES.COSMIC_SWINGSET.RUN.START: {
232
+ if (!finalBody.runNum) {
233
+ assert(!triggerContext);
234
+ triggerContext = restoreContext(); // Restore persisted context if any
235
+ } else if (!triggerContext) {
236
+ assert(!!blockContext);
237
+ // TODO: add explicit slog events of both timer poll and install bundle
238
+ // https://github.com/Agoric/agoric-sdk/issues/10332
239
+ triggerContext = {
240
+ 'run.num': undefined,
241
+ 'run.id': `unknown-${finalBody.blockHeight}-${finalBody.runNum}`,
242
+ 'run.trigger.type': 'unknown',
243
+ 'run.trigger.blockHeight': finalBody.blockHeight,
244
+ };
245
+ }
246
+
247
+ if (!triggerContext) triggerContext = {};
248
+ triggerContext['run.num'] = `${finalBody.runNum}`;
249
+
250
+ break;
251
+ }
252
+ case SLOG_TYPES.CRANK.START: {
253
+ crankContext = {
254
+ 'crank.num': finalBody.crankNum,
255
+ 'crank.type': finalBody.crankType,
256
+ };
257
+ break;
258
+ }
259
+ case SLOG_TYPES.CLIST: {
260
+ assert(!!crankContext);
261
+ crankContext['crank.vatID'] = finalBody.vatID;
262
+ break;
263
+ }
264
+ case SLOG_TYPES.REPLAY.START:
265
+ case SLOG_TYPES.REPLAY.FINISH: {
266
+ replayContext = { replay: true, 'crank.vatID': finalBody.vatID };
267
+ break;
268
+ }
269
+ case SLOG_TYPES.DELIVER: {
270
+ if (replayContext) {
271
+ assert(finalBody.replay);
272
+ replayContext = {
273
+ ...replayContext,
274
+ 'crank.vatID': finalBody.vatID,
275
+ 'crank.deliveryNum': finalBody.deliveryNum,
276
+ };
277
+ } else {
278
+ assert(!!crankContext && !finalBody.replay);
279
+ crankContext = {
280
+ ...crankContext,
281
+ 'crank.vatID': finalBody.vatID,
282
+ 'crank.deliveryNum': finalBody.deliveryNum,
283
+ };
284
+ }
285
+
286
+ delete finalBody.deliveryNum;
287
+ delete finalBody.replay;
288
+
289
+ break;
290
+ }
291
+ case SLOG_TYPES.DELIVER_RESULT: {
292
+ delete finalBody.deliveryNum;
293
+ delete finalBody.replay;
294
+
295
+ break;
296
+ }
297
+ case SLOG_TYPES.SYSCALL:
298
+ case SLOG_TYPES.SYSCALL_RESULT: {
299
+ eventLogAttributes['crank.syscallNum'] = finalBody.syscallNum;
300
+
301
+ delete finalBody.deliveryNum;
302
+ delete finalBody.replay;
303
+ delete finalBody.syscallNum;
304
+
305
+ break;
306
+ }
307
+ case SLOG_TYPES.CONSOLE: {
308
+ delete finalBody.crankNum;
309
+ delete finalBody.deliveryNum;
310
+
311
+ break;
312
+ }
313
+ default:
314
+ // All other log types are logged as is (using existing contexts) without
315
+ // any change to the slogs or any contributions to the contexts. This also
316
+ // means that any unexpected slog type will pass through. To fix that, add
317
+ // all remaining cases of expected slog types above with a simple break
318
+ // statement and log a warning here
319
+ break;
320
+ }
321
+
322
+ const logAttributes = {
323
+ ...staticContext,
324
+ 'process.uptime': monotime,
325
+ ...initContext, // Optional prelude
326
+ ...blockContext, // Block is the first level of execution nesting
327
+ ...triggerContext, // run and trigger info is nested next
328
+ ...crankContext, // Finally cranks are the last level of nesting
329
+ ...replayContext, // Replay is a substitute for crank context during vat page in
330
+ ...eventLogAttributes,
331
+ };
332
+
333
+ /**
334
+ * Add any after report operations here
335
+ * like resetting context data
336
+ */
337
+ switch (body.type) {
338
+ case SLOG_TYPES.KERNEL.INIT.FINISH: {
339
+ initContext = null;
340
+ break;
341
+ }
342
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.START: {
343
+ triggerContext = {
344
+ 'run.num': undefined,
345
+ 'run.id': `bootstrap-${finalBody.blockTime}`,
346
+ 'run.trigger.type': 'bootstrap',
347
+ 'run.trigger.time': finalBody.blockTime,
348
+ };
349
+ break;
350
+ }
351
+ case SLOG_TYPES.COSMIC_SWINGSET.AFTER_COMMIT_STATS:
352
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.FINISH: {
353
+ blockContext = null;
354
+ break;
355
+ }
356
+ // eslint-disable-next-line no-restricted-syntax
357
+ case SLOG_TYPES.COSMIC_SWINGSET.RUN.FINISH: {
358
+ assert(!!triggerContext);
359
+ persistContext(finalBody.remainingBeans ? {} : triggerContext);
360
+ triggerContext = null;
361
+ break;
362
+ }
363
+ case SLOG_TYPES.CRANK.FINISH: {
364
+ crankContext = null;
365
+ break;
366
+ }
367
+ case SLOG_TYPES.REPLAY.FINISH: {
368
+ replayContext = null;
369
+ break;
370
+ }
371
+ default:
372
+ break;
373
+ }
374
+
375
+ return {
376
+ attributes: /** @type {T & LogAttributes} */ (logAttributes),
377
+ body: finalBody,
378
+ timestamp,
379
+ };
380
+ };
381
+
382
+ return slogProcessor;
383
+ };
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /* global Buffer */
2
+ /* eslint-env node */
3
3
  /// <reference types="ses" />
4
4
 
5
5
  import fs from 'node:fs';
@@ -1,5 +1,5 @@
1
1
  #! /usr/bin/env node
2
- /* global process */
2
+ /* eslint-env node */
3
3
  // frcat - print out the contents of a flight recorder
4
4
  // NOTE: this only works on inactive recorder files where the writer has terminated
5
5
 
@@ -1,5 +1,5 @@
1
1
  #! /usr/bin/env node
2
- /* global setTimeout */
2
+ /* eslint-env node */
3
3
  import '@endo/init';
4
4
 
5
5
  import fs from 'fs';
@@ -11,7 +11,8 @@ import { makeSlogSender } from './make-slog-sender.js';
11
11
 
12
12
  const LINE_COUNT_TO_FLUSH = 10000;
13
13
  const ELAPSED_MS_TO_FLUSH = 3000;
14
- const MAX_LINE_COUNT_PER_PERIOD = 1000;
14
+ const MAX_LINE_COUNT_PER_PERIOD = 10000;
15
+ const MAX_BLOCKS_PER_PERIOD = 10;
15
16
  const PROCESSING_PERIOD = 1000;
16
17
 
17
18
  async function run() {
@@ -29,7 +30,7 @@ async function run() {
29
30
  return;
30
31
  }
31
32
 
32
- const [slogFile] = args;
33
+ const slogFile = args[0] === '-' ? undefined : args[0];
33
34
  const slogSender = await makeSlogSender({
34
35
  serviceName,
35
36
  stateDir: '.',
@@ -56,14 +57,21 @@ async function run() {
56
57
  const lines = readline.createInterface({ input: slogF });
57
58
  const slogFileName = slogFile || '*stdin*';
58
59
 
59
- const progressFileName = `${slogFileName}.ingest-progress`;
60
- if (!fs.existsSync(progressFileName)) {
61
- const progress = { virtualTimeOffset: 0, lastSlogTime: 0 };
62
- fs.writeFileSync(progressFileName, JSON.stringify(progress));
60
+ const progressFileName = slogFile && `${slogFileName}.ingest-progress`;
61
+ const progress = { virtualTimeOffset: 0, lastSlogTime: 0 };
62
+ if (progressFileName) {
63
+ if (!fs.existsSync(progressFileName)) {
64
+ fs.writeFileSync(progressFileName, JSON.stringify(progress));
65
+ } else {
66
+ Object.assign(
67
+ progress,
68
+ JSON.parse(fs.readFileSync(progressFileName).toString()),
69
+ );
70
+ }
63
71
  }
64
- const progress = JSON.parse(fs.readFileSync(progressFileName).toString());
65
72
 
66
73
  let linesProcessedThisPeriod = 0;
74
+ let blocksInThisPeriod = 0;
67
75
  let startOfLastPeriod = 0;
68
76
 
69
77
  let lastTime = Date.now();
@@ -75,10 +83,12 @@ async function run() {
75
83
  return;
76
84
  }
77
85
  await slogSender.forceFlush?.();
78
- fs.writeFileSync(progressFileName, JSON.stringify(progress));
86
+ if (progressFileName) {
87
+ fs.writeFileSync(progressFileName, JSON.stringify(progress));
88
+ }
79
89
  };
80
90
 
81
- console.log(`parsing`, slogFileName);
91
+ console.warn(`parsing`, slogFileName);
82
92
 
83
93
  let update = false;
84
94
  const maybeUpdateStats = async now => {
@@ -106,9 +116,14 @@ async function run() {
106
116
  continue;
107
117
  }
108
118
 
119
+ const isAfterCommit = obj.type === 'cosmic-swingset-after-commit-stats';
120
+
109
121
  // Maybe wait for the next period to process a bunch of lines.
110
122
  let maybeWait;
111
- if (linesProcessedThisPeriod >= MAX_LINE_COUNT_PER_PERIOD) {
123
+ if (
124
+ linesProcessedThisPeriod >= MAX_LINE_COUNT_PER_PERIOD ||
125
+ blocksInThisPeriod >= MAX_BLOCKS_PER_PERIOD
126
+ ) {
112
127
  const delayMS = PROCESSING_PERIOD - (now - startOfLastPeriod);
113
128
  maybeWait = new Promise(resolve => setTimeout(resolve, delayMS));
114
129
  }
@@ -118,8 +133,8 @@ async function run() {
118
133
  if (now - startOfLastPeriod >= PROCESSING_PERIOD) {
119
134
  startOfLastPeriod = now;
120
135
  linesProcessedThisPeriod = 0;
136
+ blocksInThisPeriod = 0;
121
137
  }
122
- linesProcessedThisPeriod += 1;
123
138
 
124
139
  if (progress.virtualTimeOffset) {
125
140
  const virtualTime = obj.time + progress.virtualTimeOffset;
@@ -133,10 +148,17 @@ async function run() {
133
148
  // Use the original.
134
149
  slogSender(obj);
135
150
  }
151
+
152
+ linesProcessedThisPeriod += 1;
153
+ if (isAfterCommit) {
154
+ blocksInThisPeriod += 1;
155
+ lastTime = Date.now();
156
+ await stats(true);
157
+ }
136
158
  }
137
159
 
138
160
  await stats(true);
139
- console.log(
161
+ console.warn(
140
162
  `done parsing`,
141
163
  slogFileName,
142
164
  `(${lineCount} lines, ${byteCount} bytes)`,
@@ -0,0 +1,131 @@
1
+ /* eslint-env node */
2
+ import { logs, SeverityNumber } from '@opentelemetry/api-logs';
3
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
4
+ import { Resource } from '@opentelemetry/resources';
5
+ import {
6
+ LoggerProvider,
7
+ SimpleLogRecordProcessor,
8
+ } from '@opentelemetry/sdk-logs';
9
+ import { readFileSync, writeFileSync } from 'fs';
10
+ import { makeContextualSlogProcessor } from './context-aware-slog.js';
11
+ import { getResourceAttributes } from './index.js';
12
+ import { serializeSlogObj } from './serialize-slog-obj.js';
13
+
14
+ const DEFAULT_CONTEXT_FILE = 'slog-context.json';
15
+ const FILE_ENCODING = 'utf8';
16
+
17
+ /**
18
+ * @param {string} filePath
19
+ */
20
+ export const getContextFilePersistenceUtils = filePath => {
21
+ console.warn(`Using file ${filePath} for slogger context`);
22
+
23
+ return {
24
+ /**
25
+ * @param {import('./context-aware-slog.js').Context} context
26
+ */
27
+ persistContext: context => {
28
+ try {
29
+ writeFileSync(filePath, serializeSlogObj(context), FILE_ENCODING);
30
+ } catch (err) {
31
+ console.error('Error writing context to file: ', err);
32
+ }
33
+ },
34
+
35
+ /**
36
+ * @returns {import('./context-aware-slog.js').Context | null}
37
+ */
38
+ restoreContext: () => {
39
+ try {
40
+ return JSON.parse(readFileSync(filePath, FILE_ENCODING));
41
+ } catch (parseErr) {
42
+ console.error('Error reading context from file: ', parseErr);
43
+ return null;
44
+ }
45
+ },
46
+ };
47
+ };
48
+
49
+ /**
50
+ * @param {import('./index.js').MakeSlogSenderOptions} options
51
+ */
52
+ export const makeSlogSender = async options => {
53
+ const { CHAIN_ID, OTEL_EXPORTER_OTLP_ENDPOINT } = options.env || {};
54
+ if (!(OTEL_EXPORTER_OTLP_ENDPOINT && options.stateDir))
55
+ return console.error(
56
+ 'Ignoring invocation of slogger "context-aware-slog" without the presence of "OTEL_EXPORTER_OTLP_ENDPOINT" and "stateDir"',
57
+ );
58
+
59
+ const loggerProvider = new LoggerProvider({
60
+ resource: new Resource(getResourceAttributes(options)),
61
+ });
62
+
63
+ const otelLogExporter = new OTLPLogExporter({ keepAlive: true });
64
+ const logRecordProcessor = new SimpleLogRecordProcessor(otelLogExporter);
65
+
66
+ loggerProvider.addLogRecordProcessor(logRecordProcessor);
67
+
68
+ logs.setGlobalLoggerProvider(loggerProvider);
69
+ const logger = logs.getLogger('default');
70
+
71
+ const persistenceUtils = getContextFilePersistenceUtils(
72
+ process.env.SLOG_CONTEXT_FILE_PATH ||
73
+ `${options.stateDir}/${DEFAULT_CONTEXT_FILE}`,
74
+ );
75
+
76
+ const contextualSlogProcessor = makeContextualSlogProcessor(
77
+ { 'chain-id': CHAIN_ID },
78
+ persistenceUtils,
79
+ );
80
+
81
+ /**
82
+ * @param {import('./context-aware-slog.js').Slog} slog
83
+ */
84
+ const slogSender = slog => {
85
+ const { timestamp, ...logRecord } = contextualSlogProcessor(slog);
86
+
87
+ const [secondsStr, fractionStr] = String(timestamp).split('.');
88
+ const seconds = parseInt(secondsStr, 10);
89
+ const nanoSeconds = parseInt(
90
+ (fractionStr || String(0)).padEnd(9, String(0)).slice(0, 9),
91
+ 10,
92
+ );
93
+
94
+ logger.emit({
95
+ ...JSON.parse(serializeSlogObj(logRecord)),
96
+ severityNumber: SeverityNumber.INFO,
97
+ timestamp: [seconds, nanoSeconds],
98
+ });
99
+ };
100
+
101
+ const shutdown = async () => {
102
+ await Promise.resolve();
103
+ const errors = [];
104
+
105
+ try {
106
+ await logRecordProcessor.shutdown();
107
+ } catch (err) {
108
+ errors.push(err);
109
+ }
110
+
111
+ try {
112
+ await otelLogExporter.forceFlush();
113
+ } catch (err) {
114
+ errors.push(err);
115
+ }
116
+
117
+ switch (errors.length) {
118
+ case 0:
119
+ return;
120
+ case 1:
121
+ throw errors[0];
122
+ default:
123
+ throw AggregateError(errors);
124
+ }
125
+ };
126
+
127
+ return Object.assign(slogSender, {
128
+ forceFlush: () => otelLogExporter.forceFlush(),
129
+ shutdown,
130
+ });
131
+ };
package/src/slog-file.js CHANGED
@@ -11,7 +11,7 @@ export const makeSlogSender = async ({ env: { SLOGFILE } = {} } = {}) => {
11
11
 
12
12
  const slogSender = (slogObj, jsonObj = serializeSlogObj(slogObj)) => {
13
13
  // eslint-disable-next-line prefer-template
14
- void stream.write(jsonObj + '\n');
14
+ stream.write(jsonObj + '\n').catch(() => {});
15
15
  };
16
16
 
17
17
  return Object.assign(slogSender, {
@@ -1,4 +1,4 @@
1
- /* global process */
1
+ /* eslint-env node */
2
2
  import '@endo/init';
3
3
 
4
4
  import anylogger from 'anylogger';
@@ -915,7 +915,7 @@ export const makeSlogToOtelKit = (tracer, overrideAttrs = {}) => {
915
915
  break;
916
916
  }
917
917
  case 'cosmic-swingset-upgrade-finish': {
918
- spans.pop(['slogAttrs.blockHeight', slogAttrs.blockHeight]);
918
+ spans.pop(['upgrade', slogAttrs.blockHeight]);
919
919
  dbTransactionManager.end();
920
920
  break;
921
921
  }
@@ -971,6 +971,16 @@ export const makeSlogToOtelKit = (tracer, overrideAttrs = {}) => {
971
971
  spans.pop('bridge-inbound');
972
972
  break;
973
973
  }
974
+ case 'cosmic-swingset-timer-poll': {
975
+ spans.push(['timer-poll', slogAttrs.blockTime]);
976
+ spans.pop('timer-poll');
977
+ break;
978
+ }
979
+ case 'cosmic-swingset-install-bundle': {
980
+ spans.push(['install-bundle', slogAttrs.endoZipBase64Sha512]);
981
+ spans.pop('install-bundle');
982
+ break;
983
+ }
974
984
  case 'cosmic-swingset-end-block-start': {
975
985
  // Add `end-block` as an event onto the encompassing `block` span
976
986
  spans.top()?.addEvent('end-block-action', cleanAttrs(slogAttrs), now);