@reventlessdev/reventless-local 3.0.0-alpha.319 → 3.0.0-alpha.320
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 +11 -0
- package/package.json +11 -11
- package/src/Platform.res +13 -0
- package/src/Platform.res.mjs +14 -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 +24 -0
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogEntry.res.mjs +28 -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 +61 -0
- package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_Sqlite.res.mjs +75 -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 +129 -5
- package/src/adapter/LocalPlatformObserve.res.mjs +144 -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/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 +183 -0
- package/tests/adapter/LocalPlatformObserveTest.res.mjs +181 -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,50 @@ 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
|
+
}`,
|
|
130
|
+
`type Platform_CommandOutcomeEdge {
|
|
131
|
+
node: AWSJSON!
|
|
132
|
+
cursor: String!
|
|
133
|
+
}`,
|
|
134
|
+
`type Platform_CommandOutcomeConnection {
|
|
135
|
+
edges: [Platform_CommandOutcomeEdge!]!
|
|
136
|
+
pageInfo: Platform_EventLogPageInfo!
|
|
137
|
+
}`,
|
|
138
|
+
]
|
|
90
139
|
let logLevelsField = "Platform_LogLevels"
|
|
91
140
|
let setLogLevelField = "Platform_SetLogLevel"
|
|
141
|
+
let metricsField = "Platform_Metrics"
|
|
92
142
|
|
|
93
143
|
let queryFields = [
|
|
94
144
|
` ${eventLogField}(filter: Platform_EventLogFilter, first: Int, after: String): Platform_EventLogConnection!`,
|
|
145
|
+
` ${viewChangesField}(filter: Platform_ViewChangeFilter, first: Int, after: String): Platform_ViewChangeConnection!`,
|
|
146
|
+
` ${commandOutcomesField}(filter: Platform_CommandOutcomeFilter, first: Int, after: String): Platform_CommandOutcomeConnection!`,
|
|
95
147
|
` ${logLevelsField}: Platform_LogLevels!`,
|
|
148
|
+
` ${metricsField}(plugins: [String!]): [Platform_MetricCount!]!`,
|
|
96
149
|
]
|
|
97
150
|
|
|
98
151
|
let logAppendedField = "Platform_LogAppended"
|
|
@@ -145,6 +198,43 @@ let eventLog: GQL.resolverFn = async (_root, args, _ctx) => {
|
|
|
145
198
|
pageJson(page)
|
|
146
199
|
}
|
|
147
200
|
|
|
201
|
+
/** A page of kept lines as a connection: each node the tap's line, each cursor its place. */
|
|
202
|
+
let keptPageJson = (page: LocalViewChangeLog.page): JSON.t =>
|
|
203
|
+
Dict.fromArray([
|
|
204
|
+
(
|
|
205
|
+
"edges",
|
|
206
|
+
page.records
|
|
207
|
+
->Array.map(({item, cursor}) =>
|
|
208
|
+
Dict.fromArray([("node", item), ("cursor", JSON.Encode.string(cursor))])->JSON.Encode.object
|
|
209
|
+
)
|
|
210
|
+
->JSON.Encode.array,
|
|
211
|
+
),
|
|
212
|
+
(
|
|
213
|
+
"pageInfo",
|
|
214
|
+
Dict.fromArray([
|
|
215
|
+
("hasNextPage", JSON.Encode.bool(page.hasNextPage)),
|
|
216
|
+
("endCursor", page.endCursor->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
217
|
+
])->JSON.Encode.object,
|
|
218
|
+
),
|
|
219
|
+
])->JSON.Encode.object
|
|
220
|
+
|
|
221
|
+
let firstOf = args => arg(args, "first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
222
|
+
let afterOf = args => arg(args, "after")->Option.flatMap(JSON.Decode.string)
|
|
223
|
+
|
|
224
|
+
let viewChanges: GQL.resolverFn = async (_root, args, _ctx) =>
|
|
225
|
+
LocalViewChangeLog.read(
|
|
226
|
+
~filter=ViewChangeLogEntry.filterOfJson(arg(args, "filter")),
|
|
227
|
+
~first=?firstOf(args),
|
|
228
|
+
~after=?afterOf(args),
|
|
229
|
+
)->keptPageJson
|
|
230
|
+
|
|
231
|
+
let commandOutcomes: GQL.resolverFn = async (_root, args, _ctx) =>
|
|
232
|
+
LocalCommandOutcomeLog.read(
|
|
233
|
+
~filter=CommandOutcomeLogEntry.filterOfJson(arg(args, "filter")),
|
|
234
|
+
~first=?firstOf(args),
|
|
235
|
+
~after=?afterOf(args),
|
|
236
|
+
)->keptPageJson
|
|
237
|
+
|
|
148
238
|
let levelOfWire = (s: string): option<Logger.level> =>
|
|
149
239
|
switch s {
|
|
150
240
|
| "DEBUG" => Some(Debug)
|
|
@@ -309,6 +399,15 @@ let feedField = (feed: LocalFeed.t): GQL.resolverFn =>
|
|
|
309
399
|
resolve: payload => payload,
|
|
310
400
|
})
|
|
311
401
|
|
|
402
|
+
/** The metrics counted since the platform started, of the given plugins or of every one:
|
|
403
|
+
per metric and slice, how often, the sum of their values, and the last 60 minutes. */
|
|
404
|
+
let metrics: GQL.resolverFn = async (_root, args, _ctx) =>
|
|
405
|
+
LocalMetrics.read(
|
|
406
|
+
~plugins=?arg(args, "plugins")
|
|
407
|
+
->Option.flatMap(JSON.Decode.array)
|
|
408
|
+
->Option.map(a => a->Array.filterMap(JSON.Decode.string)),
|
|
409
|
+
)
|
|
410
|
+
|
|
312
411
|
let adminOnly = (resolvers: array<(string, GQL.resolverFn)>): dict<GQL.resolverFn> =>
|
|
313
412
|
resolvers
|
|
314
413
|
->Array.map(((name, resolver)) => (
|
|
@@ -320,11 +419,21 @@ let adminOnly = (resolvers: array<(string, GQL.resolverFn)>): dict<GQL.resolverF
|
|
|
320
419
|
/** Registers the fields on the platform API. */
|
|
321
420
|
let registerGraphQL = (server: GQL.t) => {
|
|
322
421
|
server.registerTypes(
|
|
323
|
-
~sdlTypes=eventLogTypes
|
|
422
|
+
~sdlTypes=eventLogTypes
|
|
423
|
+
->Array.concat(viewChangeTypes)
|
|
424
|
+
->Array.concat(logLevelTypes)
|
|
425
|
+
->Array.concat(metricTypes)
|
|
426
|
+
->Array.concat(feedTypes),
|
|
324
427
|
)
|
|
325
428
|
server.registerQueries(
|
|
326
429
|
~sdlFields=queryFields,
|
|
327
|
-
~resolvers=adminOnly([
|
|
430
|
+
~resolvers=adminOnly([
|
|
431
|
+
(eventLogField, eventLog),
|
|
432
|
+
(viewChangesField, viewChanges),
|
|
433
|
+
(commandOutcomesField, commandOutcomes),
|
|
434
|
+
(logLevelsField, logLevels),
|
|
435
|
+
(metricsField, metrics),
|
|
436
|
+
]),
|
|
328
437
|
)
|
|
329
438
|
server.registerMutations(
|
|
330
439
|
~sdlFields=mutationFields,
|
|
@@ -343,6 +452,7 @@ let registerGraphQL = (server: GQL.t) => {
|
|
|
343
452
|
|
|
344
453
|
let recentLogsUri = "platform/logs/recent"
|
|
345
454
|
let eventsUri = "platform/events"
|
|
455
|
+
let metricsUri = "platform/metrics"
|
|
346
456
|
|
|
347
457
|
@val external decodeURIComponent: string => string = "decodeURIComponent"
|
|
348
458
|
|
|
@@ -431,6 +541,10 @@ let events: MCP_ServerInstance.resourceHandler = async uri => {
|
|
|
431
541
|
resourceResult(~uri, pageJson(page))
|
|
432
542
|
}
|
|
433
543
|
|
|
544
|
+
/** `platform/metrics?plugins=a,b`: the metrics counted since the platform started. */
|
|
545
|
+
let metricsResource: MCP_ServerInstance.resourceHandler = async uri =>
|
|
546
|
+
resourceResult(~uri, LocalMetrics.read(~plugins=?listOf(queryOf(uri), "plugins")))
|
|
547
|
+
|
|
434
548
|
/** Registers the resources on the platform MCP server. */
|
|
435
549
|
let registerMcp = (server: MCP_ServerInstance.t) => {
|
|
436
550
|
server.registerResource(
|
|
@@ -453,4 +567,14 @@ let registerMcp = (server: MCP_ServerInstance.t) => {
|
|
|
453
567
|
},
|
|
454
568
|
~handler=events,
|
|
455
569
|
)
|
|
570
|
+
server.registerResource(
|
|
571
|
+
~name=metricsUri,
|
|
572
|
+
~definition={
|
|
573
|
+
uriTemplate: metricsUri,
|
|
574
|
+
name: metricsUri,
|
|
575
|
+
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).",
|
|
576
|
+
mimeType: "application/json",
|
|
577
|
+
},
|
|
578
|
+
~handler=metricsResource,
|
|
579
|
+
)
|
|
456
580
|
}
|
|
@@ -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,51 @@ 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
|
+
}`,
|
|
127
|
+
`type Platform_CommandOutcomeEdge {
|
|
128
|
+
node: AWSJSON!
|
|
129
|
+
cursor: String!
|
|
130
|
+
}`,
|
|
131
|
+
`type Platform_CommandOutcomeConnection {
|
|
132
|
+
edges: [Platform_CommandOutcomeEdge!]!
|
|
133
|
+
pageInfo: Platform_EventLogPageInfo!
|
|
134
|
+
}`
|
|
135
|
+
];
|
|
136
|
+
|
|
88
137
|
let logLevelsField = "Platform_LogLevels";
|
|
89
138
|
|
|
90
139
|
let setLogLevelField = "Platform_SetLogLevel";
|
|
91
140
|
|
|
141
|
+
let metricsField = "Platform_Metrics";
|
|
142
|
+
|
|
92
143
|
let queryFields = [
|
|
93
144
|
` ` + eventLogField + `(filter: Platform_EventLogFilter, first: Int, after: String): Platform_EventLogConnection!`,
|
|
94
|
-
` ` +
|
|
145
|
+
` ` + viewChangesField + `(filter: Platform_ViewChangeFilter, first: Int, after: String): Platform_ViewChangeConnection!`,
|
|
146
|
+
` ` + commandOutcomesField + `(filter: Platform_CommandOutcomeFilter, first: Int, after: String): Platform_CommandOutcomeConnection!`,
|
|
147
|
+
` ` + logLevelsField + `: Platform_LogLevels!`,
|
|
148
|
+
` ` + metricsField + `(plugins: [String!]): [Platform_MetricCount!]!`
|
|
95
149
|
];
|
|
96
150
|
|
|
97
151
|
let logAppendedField = "Platform_LogAppended";
|
|
@@ -144,6 +198,53 @@ async function eventLog(_root, args, _ctx) {
|
|
|
144
198
|
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
199
|
}
|
|
146
200
|
|
|
201
|
+
function keptPageJson(page) {
|
|
202
|
+
return Object.fromEntries([
|
|
203
|
+
[
|
|
204
|
+
"edges",
|
|
205
|
+
page.records.map(param => Object.fromEntries([
|
|
206
|
+
[
|
|
207
|
+
"node",
|
|
208
|
+
param.item
|
|
209
|
+
],
|
|
210
|
+
[
|
|
211
|
+
"cursor",
|
|
212
|
+
param.cursor
|
|
213
|
+
]
|
|
214
|
+
]))
|
|
215
|
+
],
|
|
216
|
+
[
|
|
217
|
+
"pageInfo",
|
|
218
|
+
Object.fromEntries([
|
|
219
|
+
[
|
|
220
|
+
"hasNextPage",
|
|
221
|
+
page.hasNextPage
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
"endCursor",
|
|
225
|
+
Stdlib_Option.mapOr(page.endCursor, null, prim => prim)
|
|
226
|
+
]
|
|
227
|
+
])
|
|
228
|
+
]
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function firstOf(args) {
|
|
233
|
+
return Stdlib_Option.map(Stdlib_Option.flatMap(arg(args, "first"), Stdlib_JSON.Decode.float), prim => prim | 0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function afterOf(args) {
|
|
237
|
+
return Stdlib_Option.flatMap(arg(args, "after"), Stdlib_JSON.Decode.string);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function viewChanges(_root, args, _ctx) {
|
|
241
|
+
return keptPageJson(LocalViewChangeLog$ReventlessLocal.read(ViewChangeLogEntry$ReventlessLocal.filterOfJson(arg(args, "filter")), firstOf(args), afterOf(args)));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function commandOutcomes(_root, args, _ctx) {
|
|
245
|
+
return keptPageJson(LocalCommandOutcomeLog$ReventlessLocal.read(CommandOutcomeLogEntry$ReventlessLocal.filterOfJson(arg(args, "filter")), firstOf(args), afterOf(args)));
|
|
246
|
+
}
|
|
247
|
+
|
|
147
248
|
function levelOfWire(s) {
|
|
148
249
|
switch (s) {
|
|
149
250
|
case "DEBUG" :
|
|
@@ -311,6 +412,10 @@ function feedField(feed) {
|
|
|
311
412
|
};
|
|
312
413
|
}
|
|
313
414
|
|
|
415
|
+
async function metrics(_root, args, _ctx) {
|
|
416
|
+
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)));
|
|
417
|
+
}
|
|
418
|
+
|
|
314
419
|
function adminOnly(resolvers) {
|
|
315
420
|
return Object.fromEntries(resolvers.map(param => [
|
|
316
421
|
param[0],
|
|
@@ -319,15 +424,27 @@ function adminOnly(resolvers) {
|
|
|
319
424
|
}
|
|
320
425
|
|
|
321
426
|
function registerGraphQL(server) {
|
|
322
|
-
server.registerTypes(eventLogTypes.concat(logLevelTypes).concat(feedTypes));
|
|
427
|
+
server.registerTypes(eventLogTypes.concat(viewChangeTypes).concat(logLevelTypes).concat(metricTypes).concat(feedTypes));
|
|
323
428
|
server.registerQueries(queryFields, adminOnly([
|
|
324
429
|
[
|
|
325
430
|
eventLogField,
|
|
326
431
|
eventLog
|
|
327
432
|
],
|
|
433
|
+
[
|
|
434
|
+
viewChangesField,
|
|
435
|
+
viewChanges
|
|
436
|
+
],
|
|
437
|
+
[
|
|
438
|
+
commandOutcomesField,
|
|
439
|
+
commandOutcomes
|
|
440
|
+
],
|
|
328
441
|
[
|
|
329
442
|
logLevelsField,
|
|
330
443
|
logLevels
|
|
444
|
+
],
|
|
445
|
+
[
|
|
446
|
+
metricsField,
|
|
447
|
+
metrics
|
|
331
448
|
]
|
|
332
449
|
]));
|
|
333
450
|
server.registerMutations(mutationFields, adminOnly([[
|
|
@@ -350,6 +467,8 @@ let recentLogsUri = "platform/logs/recent";
|
|
|
350
467
|
|
|
351
468
|
let eventsUri = "platform/events";
|
|
352
469
|
|
|
470
|
+
let metricsUri = "platform/metrics";
|
|
471
|
+
|
|
353
472
|
function queryOf(uri) {
|
|
354
473
|
let params = {};
|
|
355
474
|
let qs = uri.split("?")[1];
|
|
@@ -440,6 +559,10 @@ async function events(uri) {
|
|
|
440
559
|
return resourceResult(uri, pageJson(page));
|
|
441
560
|
}
|
|
442
561
|
|
|
562
|
+
async function metricsResource(uri) {
|
|
563
|
+
return resourceResult(uri, LocalMetrics$ReventlessLocal.read(listOf(queryOf(uri), "plugins")));
|
|
564
|
+
}
|
|
565
|
+
|
|
443
566
|
function registerMcp(server) {
|
|
444
567
|
server.registerResource(recentLogsUri, {
|
|
445
568
|
uriTemplate: recentLogsUri,
|
|
@@ -453,6 +576,12 @@ function registerMcp(server) {
|
|
|
453
576
|
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
577
|
mimeType: "application/json"
|
|
455
578
|
}, events);
|
|
579
|
+
server.registerResource(metricsUri, {
|
|
580
|
+
uriTemplate: metricsUri,
|
|
581
|
+
name: metricsUri,
|
|
582
|
+
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).",
|
|
583
|
+
mimeType: "application/json"
|
|
584
|
+
}, metricsResource);
|
|
456
585
|
}
|
|
457
586
|
|
|
458
587
|
let Logger;
|
|
@@ -466,10 +595,15 @@ export {
|
|
|
466
595
|
GQL,
|
|
467
596
|
eventLogTypes,
|
|
468
597
|
logLevelTypes,
|
|
598
|
+
metricTypes,
|
|
469
599
|
feedTypes,
|
|
470
600
|
eventLogField,
|
|
601
|
+
viewChangesField,
|
|
602
|
+
commandOutcomesField,
|
|
603
|
+
viewChangeTypes,
|
|
471
604
|
logLevelsField,
|
|
472
605
|
setLogLevelField,
|
|
606
|
+
metricsField,
|
|
473
607
|
queryFields,
|
|
474
608
|
logAppendedField,
|
|
475
609
|
eventAppendedField,
|
|
@@ -478,6 +612,11 @@ export {
|
|
|
478
612
|
arg,
|
|
479
613
|
pageJson,
|
|
480
614
|
eventLog,
|
|
615
|
+
keptPageJson,
|
|
616
|
+
firstOf,
|
|
617
|
+
afterOf,
|
|
618
|
+
viewChanges,
|
|
619
|
+
commandOutcomes,
|
|
481
620
|
levelOfWire,
|
|
482
621
|
levelsJson,
|
|
483
622
|
logLevels,
|
|
@@ -490,16 +629,19 @@ export {
|
|
|
490
629
|
lineToJson,
|
|
491
630
|
feedSubscription,
|
|
492
631
|
feedField,
|
|
632
|
+
metrics,
|
|
493
633
|
adminOnly,
|
|
494
634
|
registerGraphQL,
|
|
495
635
|
recentLogsUri,
|
|
496
636
|
eventsUri,
|
|
637
|
+
metricsUri,
|
|
497
638
|
queryOf,
|
|
498
639
|
listOf,
|
|
499
640
|
intOf,
|
|
500
641
|
resourceResult,
|
|
501
642
|
recentLogs,
|
|
502
643
|
events,
|
|
644
|
+
metricsResource,
|
|
503
645
|
registerMcp,
|
|
504
646
|
}
|
|
505
647
|
/* 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;
|