@reventlessdev/reventless-local 3.0.0-alpha.317 → 3.0.0-alpha.318
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 +8 -0
- package/package.json +10 -10
- package/src/LocalPlatformRegistry.res +19 -0
- package/src/LocalPlatformRegistry.res.mjs +19 -1
- package/src/Platform.res +24 -2
- package/src/Platform.res.mjs +22 -2
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +38 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +29 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +87 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +76 -0
- package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res +8 -1
- package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res.mjs +17 -4
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +49 -2
- package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +57 -21
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +85 -0
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +50 -0
- package/src/adapter/EventLog/LocalEventLogStorage.res +3 -0
- package/src/adapter/EventLog/LocalEventLogStorage.res.mjs +6 -2
- package/src/adapter/LocalBus.res +32 -5
- package/src/adapter/LocalBus.res.mjs +27 -7
- package/src/adapter/LocalEventLogRead.res +477 -0
- package/src/adapter/LocalEventLogRead.res.mjs +507 -0
- package/src/adapter/LocalEventTap.res +18 -80
- package/src/adapter/LocalEventTap.res.mjs +19 -108
- package/src/adapter/LocalFeed.res +433 -0
- package/src/adapter/LocalFeed.res.mjs +419 -0
- package/src/adapter/LocalLogFeed.res +217 -0
- package/src/adapter/LocalLogFeed.res.mjs +295 -0
- package/src/adapter/LocalPgEventLogRead.res +85 -0
- package/src/adapter/LocalPgEventLogRead.res.mjs +91 -0
- package/src/adapter/LocalPlatformObserve.res +456 -0
- package/src/adapter/LocalPlatformObserve.res.mjs +505 -0
- package/src/adapter/LocalTapLines.res +147 -0
- package/src/adapter/LocalTapLines.res.mjs +229 -0
- package/src/adapter/PlatformGraphQL_Server.res +36 -3
- package/src/adapter/PlatformGraphQL_Server.res.mjs +15 -1
- package/src/reexport/CommandTopic.res.mjs +5 -2
- package/tests/adapter/GraphQLWsFixtures.mjs +46 -0
- package/tests/adapter/LocalEventLogReadTest.res +539 -0
- package/tests/adapter/LocalEventLogReadTest.res.mjs +466 -0
- package/tests/adapter/LocalFeedTest.res +314 -0
- package/tests/adapter/LocalFeedTest.res.mjs +329 -0
- package/tests/adapter/LocalPlatformObserveTest.res +165 -0
- package/tests/adapter/LocalPlatformObserveTest.res.mjs +183 -0
- package/tests/adapter/LocalTapLinesTest.res +182 -0
- package/tests/adapter/LocalTapLinesTest.res.mjs +242 -0
- package/tests/adapter/PlatformSubscriptionsTest.res +123 -0
- package/tests/adapter/PlatformSubscriptionsTest.res.mjs +132 -0
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +80 -0
- package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +81 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +17 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +24 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res +30 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res.mjs +28 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +58 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +71 -19
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePiiTest.res.mjs +7 -7
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
4
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
8
|
+
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
9
|
+
import * as LocalFeed$ReventlessLocal from "./LocalFeed.res.mjs";
|
|
10
|
+
import * as LocalLogFeed$ReventlessLocal from "./LocalLogFeed.res.mjs";
|
|
11
|
+
import * as LocalEventTap$ReventlessLocal from "./LocalEventTap.res.mjs";
|
|
12
|
+
import * as LocalEventLogRead$ReventlessLocal from "./LocalEventLogRead.res.mjs";
|
|
13
|
+
import * as Auth_GraphqlContext$ReventlessLocal from "./Auth/Auth_GraphqlContext.res.mjs";
|
|
14
|
+
|
|
15
|
+
let eventLogTypes = [
|
|
16
|
+
"scalar AWSJSON",
|
|
17
|
+
`input Platform_EventLogFilter {
|
|
18
|
+
plugins: [String!]
|
|
19
|
+
logs: [String!]
|
|
20
|
+
eventTypes: [String!]
|
|
21
|
+
entityId: String
|
|
22
|
+
tagKey: String
|
|
23
|
+
tagValue: String
|
|
24
|
+
msgId: String
|
|
25
|
+
correlationId: String
|
|
26
|
+
causationId: String
|
|
27
|
+
user: String
|
|
28
|
+
timeFrom: String
|
|
29
|
+
timeTo: String
|
|
30
|
+
}`,
|
|
31
|
+
`type Platform_EventLogTag {
|
|
32
|
+
key: String!
|
|
33
|
+
value: String!
|
|
34
|
+
}`,
|
|
35
|
+
`type Platform_EventLogRecord {
|
|
36
|
+
plugin: String
|
|
37
|
+
log: String!
|
|
38
|
+
position: String!
|
|
39
|
+
eventType: String!
|
|
40
|
+
entityId: String
|
|
41
|
+
tags: [Platform_EventLogTag!]!
|
|
42
|
+
payload: AWSJSON!
|
|
43
|
+
meta: AWSJSON!
|
|
44
|
+
recordedAt: String!
|
|
45
|
+
}`,
|
|
46
|
+
`type Platform_EventLogEdge {
|
|
47
|
+
node: Platform_EventLogRecord!
|
|
48
|
+
cursor: String!
|
|
49
|
+
}`,
|
|
50
|
+
`type Platform_EventLogPageInfo {
|
|
51
|
+
hasNextPage: Boolean!
|
|
52
|
+
endCursor: String
|
|
53
|
+
}`,
|
|
54
|
+
`type Platform_EventLogConnection {
|
|
55
|
+
edges: [Platform_EventLogEdge!]!
|
|
56
|
+
pageInfo: Platform_EventLogPageInfo!
|
|
57
|
+
}`
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
let logLevelTypes = [
|
|
61
|
+
`enum Platform_LogLevel {
|
|
62
|
+
DEBUG
|
|
63
|
+
INFO
|
|
64
|
+
WARN
|
|
65
|
+
ERROR
|
|
66
|
+
}`,
|
|
67
|
+
`type Platform_LogLevelSetting {
|
|
68
|
+
plugin: String
|
|
69
|
+
comp: String
|
|
70
|
+
level: Platform_LogLevel!
|
|
71
|
+
}`,
|
|
72
|
+
`type Platform_LogLevels {
|
|
73
|
+
base: Platform_LogLevel
|
|
74
|
+
settings: [Platform_LogLevelSetting!]!
|
|
75
|
+
}`
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
let feedTypes = [`input Platform_FeedFilter {
|
|
79
|
+
plugins: [String!]
|
|
80
|
+
level: Platform_LogLevel
|
|
81
|
+
components: [String!]
|
|
82
|
+
eventTypes: [String!]
|
|
83
|
+
correlationId: String
|
|
84
|
+
}`];
|
|
85
|
+
|
|
86
|
+
let eventLogField = "Platform_EventLog";
|
|
87
|
+
|
|
88
|
+
let logLevelsField = "Platform_LogLevels";
|
|
89
|
+
|
|
90
|
+
let setLogLevelField = "Platform_SetLogLevel";
|
|
91
|
+
|
|
92
|
+
let queryFields = [
|
|
93
|
+
` ` + eventLogField + `(filter: Platform_EventLogFilter, first: Int, after: String): Platform_EventLogConnection!`,
|
|
94
|
+
` ` + logLevelsField + `: Platform_LogLevels!`
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
let logAppendedField = "Platform_LogAppended";
|
|
98
|
+
|
|
99
|
+
let eventAppendedField = "Platform_EventAppended";
|
|
100
|
+
|
|
101
|
+
let subscriptionFields = [
|
|
102
|
+
` ` + logAppendedField + `(filter: Platform_FeedFilter): AWSJSON!`,
|
|
103
|
+
` ` + eventAppendedField + `(filter: Platform_FeedFilter): AWSJSON!`
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
let mutationFields = [` ` + setLogLevelField + `(plugin: String, comp: String, level: Platform_LogLevel): Platform_LogLevels!`];
|
|
107
|
+
|
|
108
|
+
function arg(args, key) {
|
|
109
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(args), d => d[key]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function pageJson(page) {
|
|
113
|
+
return Object.fromEntries([
|
|
114
|
+
[
|
|
115
|
+
"edges",
|
|
116
|
+
page.edges.map(param => Object.fromEntries([
|
|
117
|
+
[
|
|
118
|
+
"node",
|
|
119
|
+
LocalEventLogRead$ReventlessLocal.recordToJson(param[0])
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
"cursor",
|
|
123
|
+
param[1]
|
|
124
|
+
]
|
|
125
|
+
]))
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
"pageInfo",
|
|
129
|
+
Object.fromEntries([
|
|
130
|
+
[
|
|
131
|
+
"hasNextPage",
|
|
132
|
+
page.hasNextPage
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
"endCursor",
|
|
136
|
+
Stdlib_Option.mapOr(page.endCursor, null, prim => prim)
|
|
137
|
+
]
|
|
138
|
+
])
|
|
139
|
+
]
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function eventLog(_root, args, _ctx) {
|
|
144
|
+
return pageJson(await LocalEventLogRead$ReventlessLocal.read(LocalEventLogRead$ReventlessLocal.filterOfJson(arg(args, "filter")), Stdlib_Option.map(Stdlib_Option.flatMap(arg(args, "first"), Stdlib_JSON.Decode.float), prim => prim | 0), Stdlib_Option.flatMap(arg(args, "after"), Stdlib_JSON.Decode.string)));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function levelOfWire(s) {
|
|
148
|
+
switch (s) {
|
|
149
|
+
case "DEBUG" :
|
|
150
|
+
return "Debug";
|
|
151
|
+
case "ERROR" :
|
|
152
|
+
return "Error";
|
|
153
|
+
case "INFO" :
|
|
154
|
+
return "Info";
|
|
155
|
+
case "WARN" :
|
|
156
|
+
return "Warn";
|
|
157
|
+
default:
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function levelsJson() {
|
|
163
|
+
return Object.fromEntries([
|
|
164
|
+
[
|
|
165
|
+
"base",
|
|
166
|
+
Stdlib_Option.mapOr(Logger$ReventlessCore.effectiveMin(), null, Logger$ReventlessCore.levelLabel)
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
"settings",
|
|
170
|
+
Logger$ReventlessCore.levelSettings().map(s => Object.fromEntries([[
|
|
171
|
+
"level",
|
|
172
|
+
Logger$ReventlessCore.levelLabel(s.level)
|
|
173
|
+
]].concat(Stdlib_Option.mapOr(s.plugin, [], p => [[
|
|
174
|
+
"plugin",
|
|
175
|
+
p
|
|
176
|
+
]])).concat(Stdlib_Option.mapOr(s.comp, [], c => [[
|
|
177
|
+
"comp",
|
|
178
|
+
c
|
|
179
|
+
]]))))
|
|
180
|
+
]
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function logLevels(_root, _args, _ctx) {
|
|
185
|
+
return levelsJson();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let log = Logger$ReventlessCore.fromEnv();
|
|
189
|
+
|
|
190
|
+
async function setLogLevel(_root, args, _ctx) {
|
|
191
|
+
let plugin = Stdlib_Option.flatMap(arg(args, "plugin"), Stdlib_JSON.Decode.string);
|
|
192
|
+
let comp = Stdlib_Option.flatMap(arg(args, "comp"), Stdlib_JSON.Decode.string);
|
|
193
|
+
let level = Stdlib_Option.flatMap(Stdlib_Option.flatMap(arg(args, "level"), Stdlib_JSON.Decode.string), levelOfWire);
|
|
194
|
+
Logger$ReventlessCore.setLevel(plugin, comp, level);
|
|
195
|
+
let scope = plugin !== undefined ? (
|
|
196
|
+
comp !== undefined ? comp + ` of ` + plugin : plugin
|
|
197
|
+
) : (
|
|
198
|
+
comp !== undefined ? comp : "every line"
|
|
199
|
+
);
|
|
200
|
+
log.info("Platform", undefined, level !== undefined ? `log level for ` + scope + ` set to ` + Logger$ReventlessCore.levelLabel(level) : `log level for ` + scope + ` cleared`);
|
|
201
|
+
return levelsJson();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function feedFilterOf(json) {
|
|
205
|
+
let d = Stdlib_Option.getOr(Stdlib_Option.flatMap(json, Stdlib_JSON.Decode.object), {});
|
|
206
|
+
let a = k => Stdlib_Option.map(Stdlib_Option.flatMap(d[k], Stdlib_JSON.Decode.array), items => Stdlib_Array.filterMap(items, Stdlib_JSON.Decode.string));
|
|
207
|
+
return {
|
|
208
|
+
plugins: a("plugins"),
|
|
209
|
+
level: Stdlib_Option.flatMap(Stdlib_Option.flatMap(d["level"], Stdlib_JSON.Decode.string), levelOfWire),
|
|
210
|
+
components: a("components"),
|
|
211
|
+
eventTypes: a("eventTypes"),
|
|
212
|
+
correlationId: Stdlib_Option.flatMap(d["correlationId"], Stdlib_JSON.Decode.string)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let makeAsyncIterable = (function (next, ret) {
|
|
217
|
+
return { next, return: ret, [Symbol.asyncIterator]() { return this } }
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
let tapSentinel = "@@RVLESS_EVT@@ ";
|
|
221
|
+
|
|
222
|
+
function lineToJson(line) {
|
|
223
|
+
let body = line.startsWith(tapSentinel) ? line.slice(tapSentinel.length, line.length) : line;
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(body);
|
|
226
|
+
} catch (exn) {
|
|
227
|
+
return line;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function feedSubscription(feed, filter) {
|
|
232
|
+
let pending = [];
|
|
233
|
+
let waiting = {
|
|
234
|
+
contents: undefined
|
|
235
|
+
};
|
|
236
|
+
let drainers = {
|
|
237
|
+
contents: []
|
|
238
|
+
};
|
|
239
|
+
let closed = {
|
|
240
|
+
contents: false
|
|
241
|
+
};
|
|
242
|
+
let sink_write = line => {
|
|
243
|
+
let value = lineToJson(line);
|
|
244
|
+
let resolve = waiting.contents;
|
|
245
|
+
if (resolve !== undefined) {
|
|
246
|
+
waiting.contents = undefined;
|
|
247
|
+
resolve({
|
|
248
|
+
done: false,
|
|
249
|
+
value: value
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
pending.push(value);
|
|
253
|
+
}
|
|
254
|
+
return pending.length < 256;
|
|
255
|
+
};
|
|
256
|
+
let sink_onceDrain = f => {
|
|
257
|
+
drainers.contents = drainers.contents.concat([f]);
|
|
258
|
+
};
|
|
259
|
+
let sink_buffered = () => Stdlib_Array.reduce(pending, 0, (sum, v) => sum + JSON.stringify(v).length | 0);
|
|
260
|
+
let sink_close = () => {
|
|
261
|
+
closed.contents = true;
|
|
262
|
+
};
|
|
263
|
+
let sink = {
|
|
264
|
+
write: sink_write,
|
|
265
|
+
onceDrain: sink_onceDrain,
|
|
266
|
+
buffered: sink_buffered,
|
|
267
|
+
close: sink_close
|
|
268
|
+
};
|
|
269
|
+
let unsubscribe = LocalFeed$ReventlessLocal.subscribe(feed, filter, sink);
|
|
270
|
+
let next = () => {
|
|
271
|
+
let value = pending.shift();
|
|
272
|
+
if (value === undefined) {
|
|
273
|
+
if (closed.contents) {
|
|
274
|
+
return Promise.resolve({
|
|
275
|
+
done: true
|
|
276
|
+
});
|
|
277
|
+
} else {
|
|
278
|
+
return new Promise((resolve, param) => {
|
|
279
|
+
waiting.contents = resolve;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (pending.length < 128) {
|
|
284
|
+
let toCall = drainers.contents;
|
|
285
|
+
drainers.contents = [];
|
|
286
|
+
toCall.forEach(f => f());
|
|
287
|
+
}
|
|
288
|
+
return Promise.resolve({
|
|
289
|
+
done: false,
|
|
290
|
+
value: value
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
let $$return = () => {
|
|
294
|
+
closed.contents = true;
|
|
295
|
+
unsubscribe();
|
|
296
|
+
Stdlib_Option.forEach(waiting.contents, resolve => resolve({
|
|
297
|
+
done: true
|
|
298
|
+
}));
|
|
299
|
+
waiting.contents = undefined;
|
|
300
|
+
return Promise.resolve({
|
|
301
|
+
done: true
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
return makeAsyncIterable(next, $$return);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function feedField(feed) {
|
|
308
|
+
return {
|
|
309
|
+
subscribe: Auth_GraphqlContext$ReventlessLocal.requireGroup(Role$Reventless.adminGroup(), async (_root, args, _ctx) => feedSubscription(feed, feedFilterOf(arg(args, "filter")))),
|
|
310
|
+
resolve: payload => payload
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function adminOnly(resolvers) {
|
|
315
|
+
return Object.fromEntries(resolvers.map(param => [
|
|
316
|
+
param[0],
|
|
317
|
+
Auth_GraphqlContext$ReventlessLocal.requireGroup(Role$Reventless.adminGroup(), param[1])
|
|
318
|
+
]));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function registerGraphQL(server) {
|
|
322
|
+
server.registerTypes(eventLogTypes.concat(logLevelTypes).concat(feedTypes));
|
|
323
|
+
server.registerQueries(queryFields, adminOnly([
|
|
324
|
+
[
|
|
325
|
+
eventLogField,
|
|
326
|
+
eventLog
|
|
327
|
+
],
|
|
328
|
+
[
|
|
329
|
+
logLevelsField,
|
|
330
|
+
logLevels
|
|
331
|
+
]
|
|
332
|
+
]));
|
|
333
|
+
server.registerMutations(mutationFields, adminOnly([[
|
|
334
|
+
setLogLevelField,
|
|
335
|
+
setLogLevel
|
|
336
|
+
]]));
|
|
337
|
+
server.registerSubscriptions(subscriptionFields, Object.fromEntries([
|
|
338
|
+
[
|
|
339
|
+
logAppendedField,
|
|
340
|
+
feedField(LocalLogFeed$ReventlessLocal.feed)
|
|
341
|
+
],
|
|
342
|
+
[
|
|
343
|
+
eventAppendedField,
|
|
344
|
+
feedField(LocalEventTap$ReventlessLocal.feed)
|
|
345
|
+
]
|
|
346
|
+
]));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let recentLogsUri = "platform/logs/recent";
|
|
350
|
+
|
|
351
|
+
let eventsUri = "platform/events";
|
|
352
|
+
|
|
353
|
+
function queryOf(uri) {
|
|
354
|
+
let params = {};
|
|
355
|
+
let qs = uri.split("?")[1];
|
|
356
|
+
if (qs !== undefined) {
|
|
357
|
+
qs.split("&").forEach(pair => {
|
|
358
|
+
let decode = s => {
|
|
359
|
+
try {
|
|
360
|
+
return decodeURIComponent(s.replaceAll("+", " "));
|
|
361
|
+
} catch (exn) {
|
|
362
|
+
return s;
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
let i = pair.indexOf("=");
|
|
366
|
+
if (i !== -1) {
|
|
367
|
+
params[decode(pair.slice(0, i))] = decode(pair.slice(i + 1 | 0, pair.length));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
return params;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function listOf(q, key) {
|
|
376
|
+
return Stdlib_Option.map(q[key], v => v.split(",").map(prim => prim.trim()).filter(s => s !== ""));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function intOf(q, key) {
|
|
380
|
+
return Stdlib_Option.flatMap(q[key], v => Stdlib_Int.fromString(v, undefined));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function resourceResult(uri, json) {
|
|
384
|
+
return {
|
|
385
|
+
contents: [{
|
|
386
|
+
uri: uri,
|
|
387
|
+
text: JSON.stringify(json)
|
|
388
|
+
}]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function recentLogs(uri) {
|
|
393
|
+
let q = queryOf(uri);
|
|
394
|
+
let filter_plugins = listOf(q, "plugins");
|
|
395
|
+
let filter_level = Stdlib_Option.flatMap(q["level"], LocalFeed$ReventlessLocal.levelOfString);
|
|
396
|
+
let filter_components = listOf(q, "components");
|
|
397
|
+
let filter_correlationId = q["correlationId"];
|
|
398
|
+
let filter = {
|
|
399
|
+
plugins: filter_plugins,
|
|
400
|
+
level: filter_level,
|
|
401
|
+
components: filter_components,
|
|
402
|
+
correlationId: filter_correlationId
|
|
403
|
+
};
|
|
404
|
+
let records = LocalLogFeed$ReventlessLocal.recentRecords(filter, Stdlib_Option.getOr(intOf(q, "limit"), 200));
|
|
405
|
+
return resourceResult(uri, Object.fromEntries([[
|
|
406
|
+
"records",
|
|
407
|
+
records.map(Logger$ReventlessCore.recordToJson)
|
|
408
|
+
]]));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function events(uri) {
|
|
412
|
+
let q = queryOf(uri);
|
|
413
|
+
let filter_plugins = listOf(q, "plugins");
|
|
414
|
+
let filter_logs = listOf(q, "logs");
|
|
415
|
+
let filter_eventTypes = listOf(q, "eventTypes");
|
|
416
|
+
let filter_entityId = q["entityId"];
|
|
417
|
+
let filter_tagKey = q["tagKey"];
|
|
418
|
+
let filter_tagValue = q["tagValue"];
|
|
419
|
+
let filter_msgId = q["msgId"];
|
|
420
|
+
let filter_correlationId = q["correlationId"];
|
|
421
|
+
let filter_causationId = q["causationId"];
|
|
422
|
+
let filter_user = q["user"];
|
|
423
|
+
let filter_timeFrom = q["timeFrom"];
|
|
424
|
+
let filter_timeTo = q["timeTo"];
|
|
425
|
+
let filter = {
|
|
426
|
+
plugins: filter_plugins,
|
|
427
|
+
logs: filter_logs,
|
|
428
|
+
eventTypes: filter_eventTypes,
|
|
429
|
+
entityId: filter_entityId,
|
|
430
|
+
tagKey: filter_tagKey,
|
|
431
|
+
tagValue: filter_tagValue,
|
|
432
|
+
msgId: filter_msgId,
|
|
433
|
+
correlationId: filter_correlationId,
|
|
434
|
+
causationId: filter_causationId,
|
|
435
|
+
user: filter_user,
|
|
436
|
+
timeFrom: filter_timeFrom,
|
|
437
|
+
timeTo: filter_timeTo
|
|
438
|
+
};
|
|
439
|
+
let page = await LocalEventLogRead$ReventlessLocal.read(filter, intOf(q, "first"), q["after"]);
|
|
440
|
+
return resourceResult(uri, pageJson(page));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function registerMcp(server) {
|
|
444
|
+
server.registerResource(recentLogsUri, {
|
|
445
|
+
uriTemplate: recentLogsUri,
|
|
446
|
+
name: recentLogsUri,
|
|
447
|
+
description: "The platform's recent log records, oldest first. Query parameters: plugins, level, components, correlationId (narrow), limit (default 200).",
|
|
448
|
+
mimeType: "application/json"
|
|
449
|
+
}, recentLogs);
|
|
450
|
+
server.registerResource(eventsUri, {
|
|
451
|
+
uriTemplate: eventsUri,
|
|
452
|
+
name: eventsUri,
|
|
453
|
+
description: "Every event of every log, newest first, one page at a time. Query parameters: plugins, logs, eventTypes, entityId, tagKey, tagValue, msgId, correlationId, causationId, user, timeFrom, timeTo, first, after (the page's endCursor).",
|
|
454
|
+
mimeType: "application/json"
|
|
455
|
+
}, events);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let Logger;
|
|
459
|
+
|
|
460
|
+
let GQL;
|
|
461
|
+
|
|
462
|
+
let subscriptionWindow = 256;
|
|
463
|
+
|
|
464
|
+
export {
|
|
465
|
+
Logger,
|
|
466
|
+
GQL,
|
|
467
|
+
eventLogTypes,
|
|
468
|
+
logLevelTypes,
|
|
469
|
+
feedTypes,
|
|
470
|
+
eventLogField,
|
|
471
|
+
logLevelsField,
|
|
472
|
+
setLogLevelField,
|
|
473
|
+
queryFields,
|
|
474
|
+
logAppendedField,
|
|
475
|
+
eventAppendedField,
|
|
476
|
+
subscriptionFields,
|
|
477
|
+
mutationFields,
|
|
478
|
+
arg,
|
|
479
|
+
pageJson,
|
|
480
|
+
eventLog,
|
|
481
|
+
levelOfWire,
|
|
482
|
+
levelsJson,
|
|
483
|
+
logLevels,
|
|
484
|
+
log,
|
|
485
|
+
setLogLevel,
|
|
486
|
+
feedFilterOf,
|
|
487
|
+
makeAsyncIterable,
|
|
488
|
+
subscriptionWindow,
|
|
489
|
+
tapSentinel,
|
|
490
|
+
lineToJson,
|
|
491
|
+
feedSubscription,
|
|
492
|
+
feedField,
|
|
493
|
+
adminOnly,
|
|
494
|
+
registerGraphQL,
|
|
495
|
+
recentLogsUri,
|
|
496
|
+
eventsUri,
|
|
497
|
+
queryOf,
|
|
498
|
+
listOf,
|
|
499
|
+
intOf,
|
|
500
|
+
resourceResult,
|
|
501
|
+
recentLogs,
|
|
502
|
+
events,
|
|
503
|
+
registerMcp,
|
|
504
|
+
}
|
|
505
|
+
/* log Not a pure module */
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// The lines the event tap carries besides events: what became of each command,
|
|
2
|
+
// and each row a projection changed. Both go to the tap's listeners only; a
|
|
3
|
+
// rejected command is also kept in the log feed's problem ring.
|
|
4
|
+
//
|
|
5
|
+
// They are sent without the tap's sentinel prefix, as the `skipped` line is, and
|
|
6
|
+
// each carries a `kind`, so a reader that picks event lines by the prefix passes
|
|
7
|
+
// over them.
|
|
8
|
+
|
|
9
|
+
module Logger = ReventlessCore.Logger
|
|
10
|
+
module Outcome = ReventlessCore.CommandTopic_Helpers
|
|
11
|
+
module ViewChange = ReventlessCore.ViewChange
|
|
12
|
+
|
|
13
|
+
let str = JSON.Encode.string
|
|
14
|
+
let now = () => Date.make()->Date.toISOString
|
|
15
|
+
|
|
16
|
+
let lineOf = (fields: array<(string, JSON.t)>): string =>
|
|
17
|
+
fields->Dict.fromArray->JSON.Encode.object->JSON.stringify
|
|
18
|
+
|
|
19
|
+
let optional = (name: string, value: option<string>): array<(string, JSON.t)> =>
|
|
20
|
+
value->Option.mapOr([], v => [(name, str(v))])
|
|
21
|
+
|
|
22
|
+
// ─── Command outcomes ────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/** The component a report names, as the log lines of its handler name it. */
|
|
25
|
+
let compOf = (r: Outcome.commandOutcomeReport) => `CommandTopic(${r.component})`
|
|
26
|
+
|
|
27
|
+
let pluginOf = (r: Outcome.commandOutcomeReport) => Logger.resolvePlugin(~comp=compOf(r), ())
|
|
28
|
+
|
|
29
|
+
/** The tap line for one outcome. */
|
|
30
|
+
let outcomeLine = (r: Outcome.commandOutcomeReport): string => {
|
|
31
|
+
let plugin = pluginOf(r)
|
|
32
|
+
let head =
|
|
33
|
+
[("kind", str("commandOutcome")), ("component", str(r.component))]
|
|
34
|
+
->Array.concat(optional("plugin", plugin))
|
|
35
|
+
->Array.concat([("msgId", str(r.reference))])
|
|
36
|
+
->Array.concat(optional("correlationId", r.correlationId))
|
|
37
|
+
let body = switch r.outcome {
|
|
38
|
+
| OutcomeAccepted({entityId, eventCount}) =>
|
|
39
|
+
[("outcome", str("accepted"))]
|
|
40
|
+
->Array.concat(optional("entityId", entityId))
|
|
41
|
+
->Array.concat([("eventCount", JSON.Encode.int(eventCount))])
|
|
42
|
+
| OutcomeRejected({errorCode, cause}) => [
|
|
43
|
+
("outcome", str("rejected")),
|
|
44
|
+
("errorCode", str(errorCode)),
|
|
45
|
+
("cause", str(Outcome.refusalCauseToGraphQL(cause))),
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
lineOf(head->Array.concat(body)->Array.concat([("ts", str(now()))]))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A rejection as a log record: an error when the platform failed, a warning
|
|
52
|
+
when the model or the caller's access refused. The detail is redacted as a
|
|
53
|
+
logged detail is. */
|
|
54
|
+
let rejectionRecord = (r: Outcome.commandOutcomeReport): option<Logger.record> =>
|
|
55
|
+
switch r.outcome {
|
|
56
|
+
| OutcomeAccepted(_) => None
|
|
57
|
+
| OutcomeRejected({errorCode, errorDetail, cause}) =>
|
|
58
|
+
let level: Logger.level = switch cause {
|
|
59
|
+
| InfrastructureFailure => Error
|
|
60
|
+
| DomainRejection | AccessRefusal => Warn
|
|
61
|
+
}
|
|
62
|
+
let detailJson = switch errorDetail {
|
|
63
|
+
| "" => []
|
|
64
|
+
| text =>
|
|
65
|
+
let parsed = try JSON.parseOrThrow(text) catch {
|
|
66
|
+
| _ => str(text)
|
|
67
|
+
}
|
|
68
|
+
[("errorDetail", parsed->Reventless.LogRedaction.redactJson->Logger.truncateDetail)]
|
|
69
|
+
}
|
|
70
|
+
let annotations = Dict.fromArray([("msgId", r.reference)])
|
|
71
|
+
r.correlationId->Option.forEach(id => annotations->Dict.set("correlationId", id))
|
|
72
|
+
Some({
|
|
73
|
+
time: now(),
|
|
74
|
+
level,
|
|
75
|
+
message: "command rejected",
|
|
76
|
+
service: ?Logger.serviceName(),
|
|
77
|
+
plugin: ?pluginOf(r),
|
|
78
|
+
comp: compOf(r),
|
|
79
|
+
annotations,
|
|
80
|
+
detail: [
|
|
81
|
+
("component", str(r.component)),
|
|
82
|
+
("errorCode", str(errorCode)),
|
|
83
|
+
("cause", str(Outcome.refusalCauseToGraphQL(cause))),
|
|
84
|
+
]
|
|
85
|
+
->Array.concat(detailJson)
|
|
86
|
+
->Dict.fromArray
|
|
87
|
+
->JSON.Encode.object,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let onOutcome = (r: Outcome.commandOutcomeReport) => {
|
|
92
|
+
rejectionRecord(r)->Option.forEach(LocalLogFeed.keepProblem)
|
|
93
|
+
if LocalEventTap.hasReaders() {
|
|
94
|
+
LocalEventTap.publish({
|
|
95
|
+
line: outcomeLine(r),
|
|
96
|
+
facets: {plugin: ?pluginOf(r), correlationId: ?r.correlationId},
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── View changes ────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/** The tap line for one row change: the summary the log line carries, with the
|
|
104
|
+
row before and after, redacted. */
|
|
105
|
+
let viewChangeLine = (c: ViewChange.t): string => {
|
|
106
|
+
let side = (name, value: option<JSON.t>) =>
|
|
107
|
+
value->Option.mapOr([], v => [
|
|
108
|
+
(name, v->Reventless.LogRedaction.redactJson->Logger.truncateDetail),
|
|
109
|
+
])
|
|
110
|
+
lineOf(
|
|
111
|
+
[("kind", str("viewChange"))]
|
|
112
|
+
->Array.concat(c->ViewChange.summaryFields)
|
|
113
|
+
->Array.concat(optional("correlationId", c.correlationId))
|
|
114
|
+
->Array.concat(side("before", c.before))
|
|
115
|
+
->Array.concat(side("after", c.after))
|
|
116
|
+
->Array.concat([("ts", str(now()))]),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let onViewChange = (c: ViewChange.t) =>
|
|
121
|
+
if LocalEventTap.hasReaders() {
|
|
122
|
+
LocalEventTap.publish({
|
|
123
|
+
line: viewChangeLine(c),
|
|
124
|
+
facets: {
|
|
125
|
+
// The plugin is looked up by the view's name, as a log line's is by the
|
|
126
|
+
// name inside its component.
|
|
127
|
+
plugin: ?Logger.resolvePlugin(~comp=`View(${c.view})`, ()),
|
|
128
|
+
correlationId: ?c.correlationId,
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── Registration ────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
let removals: ref<array<unit => unit>> = ref([])
|
|
136
|
+
|
|
137
|
+
/** Starts sending both lines, once per process. */
|
|
138
|
+
let install = () =>
|
|
139
|
+
if removals.contents == [] {
|
|
140
|
+
removals := [Outcome.registerCommandOutcome(onOutcome), ViewChange.register(onViewChange)]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Test seam: stops sending them. */
|
|
144
|
+
let resetForTests = () => {
|
|
145
|
+
removals.contents->Array.forEach(remove => remove())
|
|
146
|
+
removals := []
|
|
147
|
+
}
|