@reventlessdev/reventless-local 3.0.0-alpha.319 → 3.0.0-alpha.321
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 +19 -0
- package/package.json +11 -11
- package/src/Platform.res +16 -0
- package/src/Platform.res.mjs +17 -0
- package/src/adapter/Api/LocalEvents_Server.res +34 -3
- package/src/adapter/Api/LocalEvents_Server.res.mjs +31 -3
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogEntry.res +33 -0
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogEntry.res.mjs +38 -0
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_InMemory.res +26 -0
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_InMemory.res.mjs +35 -0
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_Sqlite.res +64 -0
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_Sqlite.res.mjs +78 -0
- package/src/adapter/CommandOutcomeLog/LocalCommandOutcomeLog.res +88 -0
- package/src/adapter/CommandOutcomeLog/LocalCommandOutcomeLog.res.mjs +112 -0
- package/src/adapter/DomainGraphQL_Server.res +2 -1
- package/src/adapter/DomainGraphQL_Server.res.mjs +3 -1
- package/src/adapter/LocalFeed.res +16 -2
- package/src/adapter/LocalFeed.res.mjs +19 -2
- package/src/adapter/LocalIdleExit.res +71 -0
- package/src/adapter/LocalIdleExit.res.mjs +96 -0
- package/src/adapter/LocalMetrics.res +29 -0
- package/src/adapter/LocalMetrics.res.mjs +61 -0
- package/src/adapter/LocalPlatformObserve.res +130 -5
- package/src/adapter/LocalPlatformObserve.res.mjs +145 -2
- package/src/adapter/LocalRequestLog.res +62 -0
- package/src/adapter/LocalRequestLog.res.mjs +80 -0
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +11 -3
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +10 -3
- package/src/adapter/ViewChangeLog/LocalViewChangeLog.res +95 -0
- package/src/adapter/ViewChangeLog/LocalViewChangeLog.res.mjs +116 -0
- package/src/adapter/ViewChangeLog/ViewChangeLogEntry.res +33 -0
- package/src/adapter/ViewChangeLog/ViewChangeLogEntry.res.mjs +30 -0
- package/src/adapter/ViewChangeLog/ViewChangeLogStorage_InMemory.res +28 -0
- package/src/adapter/ViewChangeLog/ViewChangeLogStorage_InMemory.res.mjs +40 -0
- package/src/adapter/ViewChangeLog/ViewChangeLogStorage_Sqlite.res +76 -0
- package/src/adapter/ViewChangeLog/ViewChangeLogStorage_Sqlite.res.mjs +88 -0
- package/src/reexport/CommandTopic.res.mjs +6 -0
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +3 -2
- package/tests/adapter/GraphQL_SchemaInspectorTest.res.mjs +1 -1
- package/tests/adapter/LocalEvents_ServerTest.res +11 -0
- package/tests/adapter/LocalEvents_ServerTest.res.mjs +7 -1
- package/tests/adapter/LocalIdleExitTest.res +57 -0
- package/tests/adapter/LocalIdleExitTest.res.mjs +62 -0
- package/tests/adapter/LocalPlatformObserveTest.res +191 -0
- package/tests/adapter/LocalPlatformObserveTest.res.mjs +187 -0
- package/tests/adapter/LocalRequestLogTest.res +46 -0
- package/tests/adapter/LocalRequestLogTest.res.mjs +61 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// What the platform API and the platform MCP server offer a tool that watches
|
|
2
|
-
// this platform: every event of every log newest first (`Platform_EventLog`),
|
|
2
|
+
// this platform: every event of every log newest first (`Platform_EventLog`), every row
|
|
3
|
+
// change and command outcome kept (`Platform_ViewChanges`, `Platform_CommandOutcomes`),
|
|
3
4
|
// the log level while running (`Platform_LogLevels`, `Platform_SetLogLevel`),
|
|
4
5
|
// the log feed and the event tap as subscriptions (`Platform_LogAppended`,
|
|
5
|
-
// `Platform_EventAppended`),
|
|
6
|
-
// `platform/events
|
|
6
|
+
// `Platform_EventAppended`), the metrics counted since start (`Platform_Metrics`),
|
|
7
|
+
// and the MCP resources `platform/logs/recent`, `platform/events` and
|
|
8
|
+
// `platform/metrics`.
|
|
7
9
|
//
|
|
8
10
|
// Every field here is admin-checked, also when the platform serves one API for
|
|
9
11
|
// both, where nothing else would check it.
|
|
@@ -76,6 +78,19 @@ let logLevelTypes = [
|
|
|
76
78
|
}`,
|
|
77
79
|
]
|
|
78
80
|
|
|
81
|
+
let metricTypes = [
|
|
82
|
+
`type Platform_MetricCount {
|
|
83
|
+
metric: String!
|
|
84
|
+
plugin: String!
|
|
85
|
+
slice: String!
|
|
86
|
+
unit: String!
|
|
87
|
+
count: Int!
|
|
88
|
+
sum: Float!
|
|
89
|
+
lastAt: String!
|
|
90
|
+
perMinute: [Int!]!
|
|
91
|
+
}`,
|
|
92
|
+
]
|
|
93
|
+
|
|
79
94
|
let feedTypes = [
|
|
80
95
|
`input Platform_FeedFilter {
|
|
81
96
|
plugins: [String!]
|
|
@@ -87,12 +102,51 @@ let feedTypes = [
|
|
|
87
102
|
]
|
|
88
103
|
|
|
89
104
|
let eventLogField = "Platform_EventLog"
|
|
105
|
+
let viewChangesField = "Platform_ViewChanges"
|
|
106
|
+
let commandOutcomesField = "Platform_CommandOutcomes"
|
|
107
|
+
|
|
108
|
+
// Every row change kept since the store began, newest first; each node is the tap's
|
|
109
|
+
// `viewChange` line as JSON.
|
|
110
|
+
let viewChangeTypes = [
|
|
111
|
+
`input Platform_ViewChangeFilter {
|
|
112
|
+
correlationId: String
|
|
113
|
+
msgId: String
|
|
114
|
+
view: String
|
|
115
|
+
key: String
|
|
116
|
+
}`,
|
|
117
|
+
`type Platform_ViewChangeEdge {
|
|
118
|
+
node: AWSJSON!
|
|
119
|
+
cursor: String!
|
|
120
|
+
}`,
|
|
121
|
+
`type Platform_ViewChangeConnection {
|
|
122
|
+
edges: [Platform_ViewChangeEdge!]!
|
|
123
|
+
pageInfo: Platform_EventLogPageInfo!
|
|
124
|
+
}`,
|
|
125
|
+
// Every command outcome kept, newest first; each node is the tap's `commandOutcome` line.
|
|
126
|
+
`input Platform_CommandOutcomeFilter {
|
|
127
|
+
correlationId: String
|
|
128
|
+
msgId: String
|
|
129
|
+
startsFlow: Boolean
|
|
130
|
+
}`,
|
|
131
|
+
`type Platform_CommandOutcomeEdge {
|
|
132
|
+
node: AWSJSON!
|
|
133
|
+
cursor: String!
|
|
134
|
+
}`,
|
|
135
|
+
`type Platform_CommandOutcomeConnection {
|
|
136
|
+
edges: [Platform_CommandOutcomeEdge!]!
|
|
137
|
+
pageInfo: Platform_EventLogPageInfo!
|
|
138
|
+
}`,
|
|
139
|
+
]
|
|
90
140
|
let logLevelsField = "Platform_LogLevels"
|
|
91
141
|
let setLogLevelField = "Platform_SetLogLevel"
|
|
142
|
+
let metricsField = "Platform_Metrics"
|
|
92
143
|
|
|
93
144
|
let queryFields = [
|
|
94
145
|
` ${eventLogField}(filter: Platform_EventLogFilter, first: Int, after: String): Platform_EventLogConnection!`,
|
|
146
|
+
` ${viewChangesField}(filter: Platform_ViewChangeFilter, first: Int, after: String): Platform_ViewChangeConnection!`,
|
|
147
|
+
` ${commandOutcomesField}(filter: Platform_CommandOutcomeFilter, first: Int, after: String): Platform_CommandOutcomeConnection!`,
|
|
95
148
|
` ${logLevelsField}: Platform_LogLevels!`,
|
|
149
|
+
` ${metricsField}(plugins: [String!]): [Platform_MetricCount!]!`,
|
|
96
150
|
]
|
|
97
151
|
|
|
98
152
|
let logAppendedField = "Platform_LogAppended"
|
|
@@ -145,6 +199,43 @@ let eventLog: GQL.resolverFn = async (_root, args, _ctx) => {
|
|
|
145
199
|
pageJson(page)
|
|
146
200
|
}
|
|
147
201
|
|
|
202
|
+
/** A page of kept lines as a connection: each node the tap's line, each cursor its place. */
|
|
203
|
+
let keptPageJson = (page: LocalViewChangeLog.page): JSON.t =>
|
|
204
|
+
Dict.fromArray([
|
|
205
|
+
(
|
|
206
|
+
"edges",
|
|
207
|
+
page.records
|
|
208
|
+
->Array.map(({item, cursor}) =>
|
|
209
|
+
Dict.fromArray([("node", item), ("cursor", JSON.Encode.string(cursor))])->JSON.Encode.object
|
|
210
|
+
)
|
|
211
|
+
->JSON.Encode.array,
|
|
212
|
+
),
|
|
213
|
+
(
|
|
214
|
+
"pageInfo",
|
|
215
|
+
Dict.fromArray([
|
|
216
|
+
("hasNextPage", JSON.Encode.bool(page.hasNextPage)),
|
|
217
|
+
("endCursor", page.endCursor->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
218
|
+
])->JSON.Encode.object,
|
|
219
|
+
),
|
|
220
|
+
])->JSON.Encode.object
|
|
221
|
+
|
|
222
|
+
let firstOf = args => arg(args, "first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
223
|
+
let afterOf = args => arg(args, "after")->Option.flatMap(JSON.Decode.string)
|
|
224
|
+
|
|
225
|
+
let viewChanges: GQL.resolverFn = async (_root, args, _ctx) =>
|
|
226
|
+
LocalViewChangeLog.read(
|
|
227
|
+
~filter=ViewChangeLogEntry.filterOfJson(arg(args, "filter")),
|
|
228
|
+
~first=?firstOf(args),
|
|
229
|
+
~after=?afterOf(args),
|
|
230
|
+
)->keptPageJson
|
|
231
|
+
|
|
232
|
+
let commandOutcomes: GQL.resolverFn = async (_root, args, _ctx) =>
|
|
233
|
+
LocalCommandOutcomeLog.read(
|
|
234
|
+
~filter=CommandOutcomeLogEntry.filterOfJson(arg(args, "filter")),
|
|
235
|
+
~first=?firstOf(args),
|
|
236
|
+
~after=?afterOf(args),
|
|
237
|
+
)->keptPageJson
|
|
238
|
+
|
|
148
239
|
let levelOfWire = (s: string): option<Logger.level> =>
|
|
149
240
|
switch s {
|
|
150
241
|
| "DEBUG" => Some(Debug)
|
|
@@ -309,6 +400,15 @@ let feedField = (feed: LocalFeed.t): GQL.resolverFn =>
|
|
|
309
400
|
resolve: payload => payload,
|
|
310
401
|
})
|
|
311
402
|
|
|
403
|
+
/** The metrics counted since the platform started, of the given plugins or of every one:
|
|
404
|
+
per metric and slice, how often, the sum of their values, and the last 60 minutes. */
|
|
405
|
+
let metrics: GQL.resolverFn = async (_root, args, _ctx) =>
|
|
406
|
+
LocalMetrics.read(
|
|
407
|
+
~plugins=?arg(args, "plugins")
|
|
408
|
+
->Option.flatMap(JSON.Decode.array)
|
|
409
|
+
->Option.map(a => a->Array.filterMap(JSON.Decode.string)),
|
|
410
|
+
)
|
|
411
|
+
|
|
312
412
|
let adminOnly = (resolvers: array<(string, GQL.resolverFn)>): dict<GQL.resolverFn> =>
|
|
313
413
|
resolvers
|
|
314
414
|
->Array.map(((name, resolver)) => (
|
|
@@ -320,11 +420,21 @@ let adminOnly = (resolvers: array<(string, GQL.resolverFn)>): dict<GQL.resolverF
|
|
|
320
420
|
/** Registers the fields on the platform API. */
|
|
321
421
|
let registerGraphQL = (server: GQL.t) => {
|
|
322
422
|
server.registerTypes(
|
|
323
|
-
~sdlTypes=eventLogTypes
|
|
423
|
+
~sdlTypes=eventLogTypes
|
|
424
|
+
->Array.concat(viewChangeTypes)
|
|
425
|
+
->Array.concat(logLevelTypes)
|
|
426
|
+
->Array.concat(metricTypes)
|
|
427
|
+
->Array.concat(feedTypes),
|
|
324
428
|
)
|
|
325
429
|
server.registerQueries(
|
|
326
430
|
~sdlFields=queryFields,
|
|
327
|
-
~resolvers=adminOnly([
|
|
431
|
+
~resolvers=adminOnly([
|
|
432
|
+
(eventLogField, eventLog),
|
|
433
|
+
(viewChangesField, viewChanges),
|
|
434
|
+
(commandOutcomesField, commandOutcomes),
|
|
435
|
+
(logLevelsField, logLevels),
|
|
436
|
+
(metricsField, metrics),
|
|
437
|
+
]),
|
|
328
438
|
)
|
|
329
439
|
server.registerMutations(
|
|
330
440
|
~sdlFields=mutationFields,
|
|
@@ -343,6 +453,7 @@ let registerGraphQL = (server: GQL.t) => {
|
|
|
343
453
|
|
|
344
454
|
let recentLogsUri = "platform/logs/recent"
|
|
345
455
|
let eventsUri = "platform/events"
|
|
456
|
+
let metricsUri = "platform/metrics"
|
|
346
457
|
|
|
347
458
|
@val external decodeURIComponent: string => string = "decodeURIComponent"
|
|
348
459
|
|
|
@@ -431,6 +542,10 @@ let events: MCP_ServerInstance.resourceHandler = async uri => {
|
|
|
431
542
|
resourceResult(~uri, pageJson(page))
|
|
432
543
|
}
|
|
433
544
|
|
|
545
|
+
/** `platform/metrics?plugins=a,b`: the metrics counted since the platform started. */
|
|
546
|
+
let metricsResource: MCP_ServerInstance.resourceHandler = async uri =>
|
|
547
|
+
resourceResult(~uri, LocalMetrics.read(~plugins=?listOf(queryOf(uri), "plugins")))
|
|
548
|
+
|
|
434
549
|
/** Registers the resources on the platform MCP server. */
|
|
435
550
|
let registerMcp = (server: MCP_ServerInstance.t) => {
|
|
436
551
|
server.registerResource(
|
|
@@ -453,4 +568,14 @@ let registerMcp = (server: MCP_ServerInstance.t) => {
|
|
|
453
568
|
},
|
|
454
569
|
~handler=events,
|
|
455
570
|
)
|
|
571
|
+
server.registerResource(
|
|
572
|
+
~name=metricsUri,
|
|
573
|
+
~definition={
|
|
574
|
+
uriTemplate: metricsUri,
|
|
575
|
+
name: metricsUri,
|
|
576
|
+
description: "The metrics counted since the platform started, per metric, plugin and slice: count, sum of values, last time, and counts of the last 60 minutes. Query parameter: plugins (narrow).",
|
|
577
|
+
mimeType: "application/json",
|
|
578
|
+
},
|
|
579
|
+
~handler=metricsResource,
|
|
580
|
+
)
|
|
456
581
|
}
|
|
@@ -8,9 +8,14 @@ import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.
|
|
|
8
8
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
9
9
|
import * as LocalFeed$ReventlessLocal from "./LocalFeed.res.mjs";
|
|
10
10
|
import * as LocalLogFeed$ReventlessLocal from "./LocalLogFeed.res.mjs";
|
|
11
|
+
import * as LocalMetrics$ReventlessLocal from "./LocalMetrics.res.mjs";
|
|
11
12
|
import * as LocalEventTap$ReventlessLocal from "./LocalEventTap.res.mjs";
|
|
12
13
|
import * as LocalEventLogRead$ReventlessLocal from "./LocalEventLogRead.res.mjs";
|
|
14
|
+
import * as LocalViewChangeLog$ReventlessLocal from "./ViewChangeLog/LocalViewChangeLog.res.mjs";
|
|
15
|
+
import * as ViewChangeLogEntry$ReventlessLocal from "./ViewChangeLog/ViewChangeLogEntry.res.mjs";
|
|
13
16
|
import * as Auth_GraphqlContext$ReventlessLocal from "./Auth/Auth_GraphqlContext.res.mjs";
|
|
17
|
+
import * as CommandOutcomeLogEntry$ReventlessLocal from "./CommandOutcomeLog/CommandOutcomeLogEntry.res.mjs";
|
|
18
|
+
import * as LocalCommandOutcomeLog$ReventlessLocal from "./CommandOutcomeLog/LocalCommandOutcomeLog.res.mjs";
|
|
14
19
|
|
|
15
20
|
let eventLogTypes = [
|
|
16
21
|
"scalar AWSJSON",
|
|
@@ -75,6 +80,17 @@ let logLevelTypes = [
|
|
|
75
80
|
}`
|
|
76
81
|
];
|
|
77
82
|
|
|
83
|
+
let metricTypes = [`type Platform_MetricCount {
|
|
84
|
+
metric: String!
|
|
85
|
+
plugin: String!
|
|
86
|
+
slice: String!
|
|
87
|
+
unit: String!
|
|
88
|
+
count: Int!
|
|
89
|
+
sum: Float!
|
|
90
|
+
lastAt: String!
|
|
91
|
+
perMinute: [Int!]!
|
|
92
|
+
}`];
|
|
93
|
+
|
|
78
94
|
let feedTypes = [`input Platform_FeedFilter {
|
|
79
95
|
plugins: [String!]
|
|
80
96
|
level: Platform_LogLevel
|
|
@@ -85,13 +101,52 @@ let feedTypes = [`input Platform_FeedFilter {
|
|
|
85
101
|
|
|
86
102
|
let eventLogField = "Platform_EventLog";
|
|
87
103
|
|
|
104
|
+
let viewChangesField = "Platform_ViewChanges";
|
|
105
|
+
|
|
106
|
+
let commandOutcomesField = "Platform_CommandOutcomes";
|
|
107
|
+
|
|
108
|
+
let viewChangeTypes = [
|
|
109
|
+
`input Platform_ViewChangeFilter {
|
|
110
|
+
correlationId: String
|
|
111
|
+
msgId: String
|
|
112
|
+
view: String
|
|
113
|
+
key: String
|
|
114
|
+
}`,
|
|
115
|
+
`type Platform_ViewChangeEdge {
|
|
116
|
+
node: AWSJSON!
|
|
117
|
+
cursor: String!
|
|
118
|
+
}`,
|
|
119
|
+
`type Platform_ViewChangeConnection {
|
|
120
|
+
edges: [Platform_ViewChangeEdge!]!
|
|
121
|
+
pageInfo: Platform_EventLogPageInfo!
|
|
122
|
+
}`,
|
|
123
|
+
`input Platform_CommandOutcomeFilter {
|
|
124
|
+
correlationId: String
|
|
125
|
+
msgId: String
|
|
126
|
+
startsFlow: Boolean
|
|
127
|
+
}`,
|
|
128
|
+
`type Platform_CommandOutcomeEdge {
|
|
129
|
+
node: AWSJSON!
|
|
130
|
+
cursor: String!
|
|
131
|
+
}`,
|
|
132
|
+
`type Platform_CommandOutcomeConnection {
|
|
133
|
+
edges: [Platform_CommandOutcomeEdge!]!
|
|
134
|
+
pageInfo: Platform_EventLogPageInfo!
|
|
135
|
+
}`
|
|
136
|
+
];
|
|
137
|
+
|
|
88
138
|
let logLevelsField = "Platform_LogLevels";
|
|
89
139
|
|
|
90
140
|
let setLogLevelField = "Platform_SetLogLevel";
|
|
91
141
|
|
|
142
|
+
let metricsField = "Platform_Metrics";
|
|
143
|
+
|
|
92
144
|
let queryFields = [
|
|
93
145
|
` ` + eventLogField + `(filter: Platform_EventLogFilter, first: Int, after: String): Platform_EventLogConnection!`,
|
|
94
|
-
` ` +
|
|
146
|
+
` ` + viewChangesField + `(filter: Platform_ViewChangeFilter, first: Int, after: String): Platform_ViewChangeConnection!`,
|
|
147
|
+
` ` + commandOutcomesField + `(filter: Platform_CommandOutcomeFilter, first: Int, after: String): Platform_CommandOutcomeConnection!`,
|
|
148
|
+
` ` + logLevelsField + `: Platform_LogLevels!`,
|
|
149
|
+
` ` + metricsField + `(plugins: [String!]): [Platform_MetricCount!]!`
|
|
95
150
|
];
|
|
96
151
|
|
|
97
152
|
let logAppendedField = "Platform_LogAppended";
|
|
@@ -144,6 +199,53 @@ async function eventLog(_root, args, _ctx) {
|
|
|
144
199
|
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
200
|
}
|
|
146
201
|
|
|
202
|
+
function keptPageJson(page) {
|
|
203
|
+
return Object.fromEntries([
|
|
204
|
+
[
|
|
205
|
+
"edges",
|
|
206
|
+
page.records.map(param => Object.fromEntries([
|
|
207
|
+
[
|
|
208
|
+
"node",
|
|
209
|
+
param.item
|
|
210
|
+
],
|
|
211
|
+
[
|
|
212
|
+
"cursor",
|
|
213
|
+
param.cursor
|
|
214
|
+
]
|
|
215
|
+
]))
|
|
216
|
+
],
|
|
217
|
+
[
|
|
218
|
+
"pageInfo",
|
|
219
|
+
Object.fromEntries([
|
|
220
|
+
[
|
|
221
|
+
"hasNextPage",
|
|
222
|
+
page.hasNextPage
|
|
223
|
+
],
|
|
224
|
+
[
|
|
225
|
+
"endCursor",
|
|
226
|
+
Stdlib_Option.mapOr(page.endCursor, null, prim => prim)
|
|
227
|
+
]
|
|
228
|
+
])
|
|
229
|
+
]
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function firstOf(args) {
|
|
234
|
+
return Stdlib_Option.map(Stdlib_Option.flatMap(arg(args, "first"), Stdlib_JSON.Decode.float), prim => prim | 0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function afterOf(args) {
|
|
238
|
+
return Stdlib_Option.flatMap(arg(args, "after"), Stdlib_JSON.Decode.string);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function viewChanges(_root, args, _ctx) {
|
|
242
|
+
return keptPageJson(LocalViewChangeLog$ReventlessLocal.read(ViewChangeLogEntry$ReventlessLocal.filterOfJson(arg(args, "filter")), firstOf(args), afterOf(args)));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function commandOutcomes(_root, args, _ctx) {
|
|
246
|
+
return keptPageJson(LocalCommandOutcomeLog$ReventlessLocal.read(CommandOutcomeLogEntry$ReventlessLocal.filterOfJson(arg(args, "filter")), firstOf(args), afterOf(args)));
|
|
247
|
+
}
|
|
248
|
+
|
|
147
249
|
function levelOfWire(s) {
|
|
148
250
|
switch (s) {
|
|
149
251
|
case "DEBUG" :
|
|
@@ -311,6 +413,10 @@ function feedField(feed) {
|
|
|
311
413
|
};
|
|
312
414
|
}
|
|
313
415
|
|
|
416
|
+
async function metrics(_root, args, _ctx) {
|
|
417
|
+
return LocalMetrics$ReventlessLocal.read(Stdlib_Option.map(Stdlib_Option.flatMap(arg(args, "plugins"), Stdlib_JSON.Decode.array), a => Stdlib_Array.filterMap(a, Stdlib_JSON.Decode.string)));
|
|
418
|
+
}
|
|
419
|
+
|
|
314
420
|
function adminOnly(resolvers) {
|
|
315
421
|
return Object.fromEntries(resolvers.map(param => [
|
|
316
422
|
param[0],
|
|
@@ -319,15 +425,27 @@ function adminOnly(resolvers) {
|
|
|
319
425
|
}
|
|
320
426
|
|
|
321
427
|
function registerGraphQL(server) {
|
|
322
|
-
server.registerTypes(eventLogTypes.concat(logLevelTypes).concat(feedTypes));
|
|
428
|
+
server.registerTypes(eventLogTypes.concat(viewChangeTypes).concat(logLevelTypes).concat(metricTypes).concat(feedTypes));
|
|
323
429
|
server.registerQueries(queryFields, adminOnly([
|
|
324
430
|
[
|
|
325
431
|
eventLogField,
|
|
326
432
|
eventLog
|
|
327
433
|
],
|
|
434
|
+
[
|
|
435
|
+
viewChangesField,
|
|
436
|
+
viewChanges
|
|
437
|
+
],
|
|
438
|
+
[
|
|
439
|
+
commandOutcomesField,
|
|
440
|
+
commandOutcomes
|
|
441
|
+
],
|
|
328
442
|
[
|
|
329
443
|
logLevelsField,
|
|
330
444
|
logLevels
|
|
445
|
+
],
|
|
446
|
+
[
|
|
447
|
+
metricsField,
|
|
448
|
+
metrics
|
|
331
449
|
]
|
|
332
450
|
]));
|
|
333
451
|
server.registerMutations(mutationFields, adminOnly([[
|
|
@@ -350,6 +468,8 @@ let recentLogsUri = "platform/logs/recent";
|
|
|
350
468
|
|
|
351
469
|
let eventsUri = "platform/events";
|
|
352
470
|
|
|
471
|
+
let metricsUri = "platform/metrics";
|
|
472
|
+
|
|
353
473
|
function queryOf(uri) {
|
|
354
474
|
let params = {};
|
|
355
475
|
let qs = uri.split("?")[1];
|
|
@@ -440,6 +560,10 @@ async function events(uri) {
|
|
|
440
560
|
return resourceResult(uri, pageJson(page));
|
|
441
561
|
}
|
|
442
562
|
|
|
563
|
+
async function metricsResource(uri) {
|
|
564
|
+
return resourceResult(uri, LocalMetrics$ReventlessLocal.read(listOf(queryOf(uri), "plugins")));
|
|
565
|
+
}
|
|
566
|
+
|
|
443
567
|
function registerMcp(server) {
|
|
444
568
|
server.registerResource(recentLogsUri, {
|
|
445
569
|
uriTemplate: recentLogsUri,
|
|
@@ -453,6 +577,12 @@ function registerMcp(server) {
|
|
|
453
577
|
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
578
|
mimeType: "application/json"
|
|
455
579
|
}, events);
|
|
580
|
+
server.registerResource(metricsUri, {
|
|
581
|
+
uriTemplate: metricsUri,
|
|
582
|
+
name: metricsUri,
|
|
583
|
+
description: "The metrics counted since the platform started, per metric, plugin and slice: count, sum of values, last time, and counts of the last 60 minutes. Query parameter: plugins (narrow).",
|
|
584
|
+
mimeType: "application/json"
|
|
585
|
+
}, metricsResource);
|
|
456
586
|
}
|
|
457
587
|
|
|
458
588
|
let Logger;
|
|
@@ -466,10 +596,15 @@ export {
|
|
|
466
596
|
GQL,
|
|
467
597
|
eventLogTypes,
|
|
468
598
|
logLevelTypes,
|
|
599
|
+
metricTypes,
|
|
469
600
|
feedTypes,
|
|
470
601
|
eventLogField,
|
|
602
|
+
viewChangesField,
|
|
603
|
+
commandOutcomesField,
|
|
604
|
+
viewChangeTypes,
|
|
471
605
|
logLevelsField,
|
|
472
606
|
setLogLevelField,
|
|
607
|
+
metricsField,
|
|
473
608
|
queryFields,
|
|
474
609
|
logAppendedField,
|
|
475
610
|
eventAppendedField,
|
|
@@ -478,6 +613,11 @@ export {
|
|
|
478
613
|
arg,
|
|
479
614
|
pageJson,
|
|
480
615
|
eventLog,
|
|
616
|
+
keptPageJson,
|
|
617
|
+
firstOf,
|
|
618
|
+
afterOf,
|
|
619
|
+
viewChanges,
|
|
620
|
+
commandOutcomes,
|
|
481
621
|
levelOfWire,
|
|
482
622
|
levelsJson,
|
|
483
623
|
logLevels,
|
|
@@ -490,16 +630,19 @@ export {
|
|
|
490
630
|
lineToJson,
|
|
491
631
|
feedSubscription,
|
|
492
632
|
feedField,
|
|
633
|
+
metrics,
|
|
493
634
|
adminOnly,
|
|
494
635
|
registerGraphQL,
|
|
495
636
|
recentLogsUri,
|
|
496
637
|
eventsUri,
|
|
638
|
+
metricsUri,
|
|
497
639
|
queryOf,
|
|
498
640
|
listOf,
|
|
499
641
|
intOf,
|
|
500
642
|
resourceResult,
|
|
501
643
|
recentLogs,
|
|
502
644
|
events,
|
|
645
|
+
metricsResource,
|
|
503
646
|
registerMcp,
|
|
504
647
|
}
|
|
505
648
|
/* log Not a pure module */
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// One log line per GraphQL operation the app's API answers: a query or a subscription at debug, a
|
|
2
|
+
// mutation at info, one that answered with errors at warn, so it shows among the problems.
|
|
3
|
+
|
|
4
|
+
module YG = GraphqlYoga
|
|
5
|
+
module L = ReventlessCore.Logger
|
|
6
|
+
|
|
7
|
+
type line = {level: L.level, message: string, data: JSON.t}
|
|
8
|
+
|
|
9
|
+
let lineOf = (r: YG.requestReport): line => {
|
|
10
|
+
let name = switch (r.operationName, r.fields) {
|
|
11
|
+
| (Some(n), _) => n
|
|
12
|
+
| (None, []) => "(anonymous)"
|
|
13
|
+
| (None, fields) => fields->Array.join(", ")
|
|
14
|
+
}
|
|
15
|
+
let who = r.user->Option.mapOr("", u => ` by ${u}`)
|
|
16
|
+
let took = r.kind == "subscription" ? "" : ` in ${r.ms->Float.toString} ms`
|
|
17
|
+
let message = switch r.errors->Array.get(0) {
|
|
18
|
+
| None => `${r.kind} ${name}${who}${took}`
|
|
19
|
+
| Some(first) =>
|
|
20
|
+
let more = r.errors->Array.length - 1
|
|
21
|
+
`${r.kind} ${name}${who} failed${took}: ${first}${more > 0
|
|
22
|
+
? ` (and ${more->Int.toString} more)`
|
|
23
|
+
: ""}`
|
|
24
|
+
}
|
|
25
|
+
let level: L.level = if r.errors != [] {
|
|
26
|
+
Warn
|
|
27
|
+
} else if r.kind == "mutation" {
|
|
28
|
+
Info
|
|
29
|
+
} else {
|
|
30
|
+
Debug
|
|
31
|
+
}
|
|
32
|
+
let data = JSON.Encode.object(
|
|
33
|
+
Dict.fromArray(
|
|
34
|
+
[
|
|
35
|
+
("operation", JSON.Encode.string(r.kind)),
|
|
36
|
+
("fields", JSON.Encode.array(r.fields->Array.map(JSON.Encode.string))),
|
|
37
|
+
("ms", JSON.Encode.float(r.ms)),
|
|
38
|
+
]
|
|
39
|
+
->Array.concat(
|
|
40
|
+
r.operationName->Option.mapOr([], n => [("operationName", JSON.Encode.string(n))]),
|
|
41
|
+
)
|
|
42
|
+
->Array.concat(r.user->Option.mapOr([], u => [("user", JSON.Encode.string(u))]))
|
|
43
|
+
->Array.concat(
|
|
44
|
+
r.errors == []
|
|
45
|
+
? []
|
|
46
|
+
: [("errors", JSON.Encode.array(r.errors->Array.map(JSON.Encode.string)))],
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
{level, message, data}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The plugin that writes the line under the API's component, its fields as the line's detail
|
|
54
|
+
rather than in its words. */
|
|
55
|
+
let plugin = (): YG.plugin =>
|
|
56
|
+
YG.requestLog(r => {
|
|
57
|
+
let {level, message, data} = lineOf(r)
|
|
58
|
+
let comp = "GraphQL:Domain"
|
|
59
|
+
if L.shouldLog(level, ~comp, ()) {
|
|
60
|
+
L.emit(~level, ~comp, ~detail=data, message)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as GraphqlYoga from "@reventlessdev/rescript-graphql-yoga/src/GraphqlYoga.res.mjs";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
6
|
+
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
7
|
+
|
|
8
|
+
function lineOf(r) {
|
|
9
|
+
let match = r.operationName;
|
|
10
|
+
let match$1 = r.fields;
|
|
11
|
+
let name = match !== undefined ? match : (
|
|
12
|
+
match$1.length !== 0 ? match$1.join(", ") : "(anonymous)"
|
|
13
|
+
);
|
|
14
|
+
let who = Stdlib_Option.mapOr(r.user, "", u => ` by ` + u);
|
|
15
|
+
let took = r.kind === "subscription" ? "" : ` in ` + r.ms.toString() + ` ms`;
|
|
16
|
+
let first = r.errors[0];
|
|
17
|
+
let message;
|
|
18
|
+
if (first !== undefined) {
|
|
19
|
+
let more = r.errors.length - 1 | 0;
|
|
20
|
+
message = r.kind + ` ` + name + who + ` failed` + took + `: ` + first + (
|
|
21
|
+
more > 0 ? ` (and ` + more.toString() + ` more)` : ""
|
|
22
|
+
);
|
|
23
|
+
} else {
|
|
24
|
+
message = r.kind + ` ` + name + who + took;
|
|
25
|
+
}
|
|
26
|
+
let level = Primitive_object.notequal(r.errors, []) ? "Warn" : (
|
|
27
|
+
r.kind === "mutation" ? "Info" : "Debug"
|
|
28
|
+
);
|
|
29
|
+
let data = Object.fromEntries([
|
|
30
|
+
[
|
|
31
|
+
"operation",
|
|
32
|
+
r.kind
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
"fields",
|
|
36
|
+
r.fields.map(prim => prim)
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
"ms",
|
|
40
|
+
r.ms
|
|
41
|
+
]
|
|
42
|
+
].concat(Stdlib_Option.mapOr(r.operationName, [], n => [[
|
|
43
|
+
"operationName",
|
|
44
|
+
n
|
|
45
|
+
]])).concat(Stdlib_Option.mapOr(r.user, [], u => [[
|
|
46
|
+
"user",
|
|
47
|
+
u
|
|
48
|
+
]])).concat(Primitive_object.equal(r.errors, []) ? [] : [[
|
|
49
|
+
"errors",
|
|
50
|
+
r.errors.map(prim => prim)
|
|
51
|
+
]]));
|
|
52
|
+
return {
|
|
53
|
+
level: level,
|
|
54
|
+
message: message,
|
|
55
|
+
data: data
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function plugin() {
|
|
60
|
+
return GraphqlYoga.requestLog(r => {
|
|
61
|
+
let match = lineOf(r);
|
|
62
|
+
let level = match.level;
|
|
63
|
+
let comp = "GraphQL:Domain";
|
|
64
|
+
if (Logger$ReventlessCore.shouldLog(level, undefined, comp, undefined)) {
|
|
65
|
+
return Logger$ReventlessCore.emit(level, comp, undefined, match.data, undefined, match.message);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let YG;
|
|
71
|
+
|
|
72
|
+
let L;
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
YG,
|
|
76
|
+
L,
|
|
77
|
+
lineOf,
|
|
78
|
+
plugin,
|
|
79
|
+
}
|
|
80
|
+
/* GraphqlYoga Not a pure module */
|
|
@@ -1221,9 +1221,17 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
1221
1221
|
fieldResolvers->Dict.set(resolvedField, resolver)
|
|
1222
1222
|
})
|
|
1223
1223
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1224
|
+
// A row's last change is read by an elevated caller only; anyone else reads null.
|
|
1225
|
+
let changeResolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (
|
|
1226
|
+
root,
|
|
1227
|
+
_args,
|
|
1228
|
+
ctx,
|
|
1229
|
+
) =>
|
|
1230
|
+
extractIdentity(ctx)->Reventless.OwnerScope.resolve->Reventless.OwnerScope.isExempt
|
|
1231
|
+
? root->sourceField(ReventlessCore.RowChange.key)->Option.getOr(JSON.Encode.null)
|
|
1232
|
+
: JSON.Encode.null
|
|
1233
|
+
fieldResolvers->Dict.set(ReventlessCore.RowChange.key, changeResolver)
|
|
1234
|
+
server.registerFieldResolvers(~typeName=returnTypeName, ~resolvers=fieldResolvers)
|
|
1227
1235
|
|
|
1228
1236
|
// -- Register all fields --------------------------------------------------
|
|
1229
1237
|
let allSdl =
|
|
@@ -16,6 +16,7 @@ import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/compon
|
|
|
16
16
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
17
17
|
import * as Api_Ids$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Ids.res.mjs";
|
|
18
18
|
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
19
|
+
import * as RowChange$ReventlessCore from "@reventlessdev/reventless-core/src/RowChange.res.mjs";
|
|
19
20
|
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
20
21
|
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
21
22
|
import * as SortKey_Filter$ReventlessLocal from "./SortKey_Filter.res.mjs";
|
|
@@ -749,9 +750,15 @@ function Make(Bus) {
|
|
|
749
750
|
};
|
|
750
751
|
fieldResolvers[match.resolvedField] = resolver;
|
|
751
752
|
});
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
753
|
+
let changeResolver = async (root, _args, ctx) => {
|
|
754
|
+
if (OwnerScope$Reventless.isExempt(OwnerScope$Reventless.resolve(extractIdentity(ctx), undefined))) {
|
|
755
|
+
return Stdlib_Option.getOr(sourceField(root, RowChange$ReventlessCore.key), null);
|
|
756
|
+
} else {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
fieldResolvers[RowChange$ReventlessCore.key] = changeResolver;
|
|
761
|
+
server.registerFieldResolvers(returnTypeName, fieldResolvers);
|
|
755
762
|
let allSdl = [byIdSdl].concat(byIdsSdl).concat(refsSdl).concat(match[0]).concat(itemsSdl).concat(indexSdlFields);
|
|
756
763
|
let resolvers = {};
|
|
757
764
|
resolvers[singleQueryName] = byIdResolver;
|