@agoric/telemetry 0.6.3-dev-7ae1f27.0 → 0.6.3-dev-acbd3ae.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/telemetry",
3
- "version": "0.6.3-dev-7ae1f27.0+7ae1f27",
3
+ "version": "0.6.3-dev-acbd3ae.0+acbd3ae",
4
4
  "description": "Agoric's telemetry implementation",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/Agoric/agoric-sdk",
@@ -22,16 +22,19 @@
22
22
  "author": "Agoric",
23
23
  "license": "Apache-2.0",
24
24
  "dependencies": {
25
- "@agoric/internal": "0.3.3-dev-7ae1f27.0+7ae1f27",
26
- "@agoric/store": "0.9.3-dev-7ae1f27.0+7ae1f27",
25
+ "@agoric/internal": "0.3.3-dev-acbd3ae.0+acbd3ae",
26
+ "@agoric/store": "0.9.3-dev-acbd3ae.0+acbd3ae",
27
27
  "@endo/errors": "^1.2.7",
28
28
  "@endo/init": "^1.1.6",
29
29
  "@endo/marshal": "^1.6.1",
30
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
40
  "@opentelemetry/semantic-conventions": "~1.27.0",
@@ -65,5 +68,5 @@
65
68
  "typeCoverage": {
66
69
  "atLeast": 87.03
67
70
  },
68
- "gitHead": "7ae1f278fa8cbeb0cfc777b7cebf507b1f07c958"
71
+ "gitHead": "acbd3ae486c3749b66bf068dcca83585a34f9110"
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,345 @@
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.time': Slog['blockTime'];
20
+ * 'run.trigger.txHash': string;
21
+ * 'run.trigger.type': string;
22
+ * }>
23
+ * } Context
24
+ *
25
+ * @typedef {{
26
+ * 'crank.syscallNum'?: Slog['syscallNum'];
27
+ * 'process.uptime': Slog['monotime'];
28
+ * } & Context} LogAttributes
29
+ *
30
+ * @typedef {{
31
+ * blockHeight?: number;
32
+ * blockTime?: number;
33
+ * crankNum?: bigint;
34
+ * crankType?: string;
35
+ * deliveryNum?: bigint;
36
+ * inboundNum?: string;
37
+ * monotime: number;
38
+ * remainingBeans?: bigint;
39
+ * replay?: boolean;
40
+ * runNum?: number;
41
+ * sender?: string;
42
+ * source?: string;
43
+ * syscallNum?: number;
44
+ * time: number;
45
+ * type: string;
46
+ * vatID?: string;
47
+ * }} Slog
48
+ */
49
+
50
+ const SLOG_TYPES = {
51
+ CLIST: 'clist',
52
+ CONSOLE: 'console',
53
+ COSMIC_SWINGSET: {
54
+ AFTER_COMMIT_STATS: 'cosmic-swingset-after-commit-stats',
55
+ BEGIN_BLOCK: 'cosmic-swingset-begin-block',
56
+ BOOTSTRAP_BLOCK: {
57
+ FINISH: 'cosmic-swingset-bootstrap-block-finish',
58
+ START: 'cosmic-swingset-bootstrap-block-start',
59
+ },
60
+ BRIDGE_INBOUND: 'cosmic-swingset-bridge-inbound',
61
+ COMMIT: {
62
+ FINISH: 'cosmic-swingset-commit-finish',
63
+ START: 'cosmic-swingset-commit-start',
64
+ },
65
+ DELIVER_INBOUND: 'cosmic-swingset-deliver-inbound',
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
+ CRANK: {
77
+ FINISH: 'crank-finish',
78
+ START: 'crank-start',
79
+ },
80
+ DELIVER: 'deliver',
81
+ DELIVER_RESULT: 'deliver-result',
82
+ KERNEL: {
83
+ INIT: {
84
+ FINISH: 'kernel-init-finish',
85
+ START: 'kernel-init-start',
86
+ },
87
+ },
88
+ REPLAY: {
89
+ FINISH: 'finish-replay',
90
+ START: 'start-replay',
91
+ },
92
+ SYSCALL: 'syscall',
93
+ SYSCALL_RESULT: 'syscall-result',
94
+ };
95
+
96
+ /**
97
+ * @template {Record<string, any>} [T={}]
98
+ * @param {T} [staticContext]
99
+ * @param {Partial<{ persistContext: (context: Context) => void; restoreContext: () => Context | null; }>} [persistenceUtils]
100
+ */
101
+ export const makeContextualSlogProcessor = (
102
+ staticContext,
103
+ persistenceUtils = {},
104
+ ) => {
105
+ /** @type Array<Context | null> */
106
+ let [
107
+ blockContext,
108
+ crankContext,
109
+ initContext,
110
+ lastPersistedTriggerContext,
111
+ replayContext,
112
+ triggerContext,
113
+ ] = [null, null, null, null, null, null];
114
+
115
+ /**
116
+ * @param {Context} context
117
+ */
118
+ const persistContext = context => {
119
+ lastPersistedTriggerContext = context;
120
+ return persistenceUtils?.persistContext?.(context);
121
+ };
122
+
123
+ const restoreContext = () => {
124
+ if (!lastPersistedTriggerContext)
125
+ lastPersistedTriggerContext =
126
+ persistenceUtils?.restoreContext?.() || null;
127
+ return lastPersistedTriggerContext;
128
+ };
129
+
130
+ /**
131
+ * @param {Slog} slog
132
+ * @returns {{ attributes: T & LogAttributes, body: Partial<Slog>; timestamp: Slog['time'] }}
133
+ */
134
+ const slogProcessor = ({ monotime, time: timestamp, ...body }) => {
135
+ const finalBody = { ...body };
136
+
137
+ /** @type {{'crank.syscallNum'?: Slog['syscallNum']}} */
138
+ const eventLogAttributes = {};
139
+
140
+ /**
141
+ * Add any before report operations here
142
+ * like setting context data
143
+ */
144
+ switch (body.type) {
145
+ case SLOG_TYPES.KERNEL.INIT.START: {
146
+ initContext = { init: true };
147
+ break;
148
+ }
149
+ case SLOG_TYPES.COSMIC_SWINGSET.BEGIN_BLOCK: {
150
+ blockContext = {
151
+ 'block.height': finalBody.blockHeight,
152
+ 'block.time': finalBody.blockTime,
153
+ };
154
+ break;
155
+ }
156
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.START: {
157
+ blockContext = {
158
+ 'block.height': finalBody.blockHeight || 0,
159
+ 'block.time': finalBody.blockTime,
160
+ };
161
+ break;
162
+ }
163
+ case SLOG_TYPES.COSMIC_SWINGSET.END_BLOCK.START:
164
+ case SLOG_TYPES.COSMIC_SWINGSET.END_BLOCK.FINISH:
165
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.FINISH:
166
+ case SLOG_TYPES.COSMIC_SWINGSET.COMMIT.START:
167
+ case SLOG_TYPES.COSMIC_SWINGSET.COMMIT.FINISH:
168
+ case SLOG_TYPES.COSMIC_SWINGSET.AFTER_COMMIT_STATS: {
169
+ assert(!!blockContext && !triggerContext);
170
+ break;
171
+ }
172
+ case SLOG_TYPES.COSMIC_SWINGSET.BRIDGE_INBOUND:
173
+ case SLOG_TYPES.COSMIC_SWINGSET.DELIVER_INBOUND: {
174
+ const [blockHeight, txHash, msgIdx] = (
175
+ finalBody.inboundNum || ''
176
+ ).split('-');
177
+ const [, triggerType] =
178
+ /cosmic-swingset-([^-]+)-inbound/.exec(body.type) || [];
179
+
180
+ triggerContext = {
181
+ 'run.num': undefined,
182
+ 'run.id': `${triggerType}-${finalBody.inboundNum}`,
183
+ 'run.trigger.type': triggerType,
184
+ 'run.trigger.source': finalBody.source,
185
+ 'run.trigger.sender': finalBody.sender,
186
+ 'run.trigger.blockHeight': Number(blockHeight),
187
+ 'run.trigger.txHash': txHash,
188
+ 'run.trigger.msgIdx': Number(msgIdx),
189
+ };
190
+ break;
191
+ }
192
+ // eslint-disable-next-line no-restricted-syntax
193
+ case SLOG_TYPES.COSMIC_SWINGSET.RUN.START: {
194
+ if (!finalBody.runNum) {
195
+ assert(!triggerContext);
196
+ triggerContext = restoreContext(); // Restore persisted context if any
197
+ } else if (!triggerContext) {
198
+ assert(!!blockContext);
199
+ // TODO: add explicit slog events of both timer poll and install bundle
200
+ // https://github.com/Agoric/agoric-sdk/issues/10332
201
+ triggerContext = {
202
+ 'run.num': undefined,
203
+ 'run.id': `unknown-${finalBody.blockHeight}-${finalBody.runNum}`,
204
+ 'run.trigger.type': 'unknown',
205
+ 'run.trigger.blockHeight': finalBody.blockHeight,
206
+ };
207
+ }
208
+
209
+ if (!triggerContext) triggerContext = {};
210
+ triggerContext['run.num'] = `${finalBody.runNum}`;
211
+
212
+ break;
213
+ }
214
+ case SLOG_TYPES.CRANK.START: {
215
+ crankContext = {
216
+ 'crank.num': finalBody.crankNum,
217
+ 'crank.type': finalBody.crankType,
218
+ };
219
+ break;
220
+ }
221
+ case SLOG_TYPES.CLIST: {
222
+ assert(!!crankContext);
223
+ crankContext['crank.vatID'] = finalBody.vatID;
224
+ break;
225
+ }
226
+ case SLOG_TYPES.REPLAY.START:
227
+ case SLOG_TYPES.REPLAY.FINISH: {
228
+ replayContext = { replay: true, 'crank.vatID': finalBody.vatID };
229
+ break;
230
+ }
231
+ case SLOG_TYPES.DELIVER: {
232
+ if (replayContext) {
233
+ assert(finalBody.replay);
234
+ replayContext = {
235
+ ...replayContext,
236
+ 'crank.vatID': finalBody.vatID,
237
+ 'crank.deliveryNum': finalBody.deliveryNum,
238
+ };
239
+ } else {
240
+ assert(!!crankContext && !finalBody.replay);
241
+ crankContext = {
242
+ ...crankContext,
243
+ 'crank.vatID': finalBody.vatID,
244
+ 'crank.deliveryNum': finalBody.deliveryNum,
245
+ };
246
+ }
247
+
248
+ delete finalBody.deliveryNum;
249
+ delete finalBody.replay;
250
+
251
+ break;
252
+ }
253
+ case SLOG_TYPES.DELIVER_RESULT: {
254
+ delete finalBody.deliveryNum;
255
+ delete finalBody.replay;
256
+
257
+ break;
258
+ }
259
+ case SLOG_TYPES.SYSCALL:
260
+ case SLOG_TYPES.SYSCALL_RESULT: {
261
+ eventLogAttributes['crank.syscallNum'] = finalBody.syscallNum;
262
+
263
+ delete finalBody.deliveryNum;
264
+ delete finalBody.replay;
265
+ delete finalBody.syscallNum;
266
+
267
+ break;
268
+ }
269
+ case SLOG_TYPES.CONSOLE: {
270
+ delete finalBody.crankNum;
271
+ delete finalBody.deliveryNum;
272
+
273
+ break;
274
+ }
275
+ default:
276
+ // All other log types are logged as is (using existing contexts) without
277
+ // any change to the slogs or any contributions to the contexts. This also
278
+ // means that any unexpected slog type will pass through. To fix that, add
279
+ // all remaining cases of expected slog types above with a simple break
280
+ // statement and log a warning here
281
+ break;
282
+ }
283
+
284
+ const logAttributes = {
285
+ ...staticContext,
286
+ 'process.uptime': monotime,
287
+ ...initContext, // Optional prelude
288
+ ...blockContext, // Block is the first level of execution nesting
289
+ ...triggerContext, // run and trigger info is nested next
290
+ ...crankContext, // Finally cranks are the last level of nesting
291
+ ...replayContext, // Replay is a substitute for crank context during vat page in
292
+ ...eventLogAttributes,
293
+ };
294
+
295
+ /**
296
+ * Add any after report operations here
297
+ * like resetting context data
298
+ */
299
+ switch (body.type) {
300
+ case SLOG_TYPES.KERNEL.INIT.FINISH: {
301
+ initContext = null;
302
+ break;
303
+ }
304
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.START: {
305
+ triggerContext = {
306
+ 'run.num': undefined,
307
+ 'run.id': `bootstrap-${finalBody.blockTime}`,
308
+ 'run.trigger.type': 'bootstrap',
309
+ 'run.trigger.time': finalBody.blockTime,
310
+ };
311
+ break;
312
+ }
313
+ case SLOG_TYPES.COSMIC_SWINGSET.AFTER_COMMIT_STATS:
314
+ case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.FINISH: {
315
+ blockContext = null;
316
+ break;
317
+ }
318
+ // eslint-disable-next-line no-restricted-syntax
319
+ case SLOG_TYPES.COSMIC_SWINGSET.RUN.FINISH: {
320
+ assert(!!triggerContext);
321
+ persistContext(finalBody.remainingBeans ? {} : triggerContext);
322
+ triggerContext = null;
323
+ break;
324
+ }
325
+ case SLOG_TYPES.CRANK.FINISH: {
326
+ crankContext = null;
327
+ break;
328
+ }
329
+ case SLOG_TYPES.REPLAY.FINISH: {
330
+ replayContext = null;
331
+ break;
332
+ }
333
+ default:
334
+ break;
335
+ }
336
+
337
+ return {
338
+ attributes: /** @type {T & LogAttributes} */ (logAttributes),
339
+ body: finalBody,
340
+ timestamp,
341
+ };
342
+ };
343
+
344
+ return slogProcessor;
345
+ };
@@ -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
+ };