h1v3 0.6.4 → 0.7.1
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 +1 -1
- package/src/configuration.js +14 -13
- package/src/event-store/configuration.js +5 -5
- package/src/event-store/projections.js +18 -6
- package/src/index.js +3 -1
- package/src/observability/index.js +123 -0
package/package.json
CHANGED
package/src/configuration.js
CHANGED
|
@@ -3,7 +3,7 @@ import { all, assertNewDataDoesNotHave, assertNewDataHasString, any, assertNewDa
|
|
|
3
3
|
|
|
4
4
|
export const RAW_STORE_META = Symbol("Raw database metadata");
|
|
5
5
|
|
|
6
|
-
export function configureConfigStore(paths, _onValueWritten,
|
|
6
|
+
export function configureConfigStore(paths, _onValueWritten, _observe) {
|
|
7
7
|
|
|
8
8
|
if (!paths?.config?.path) throw new Error("Missing: paths.config.path");
|
|
9
9
|
return {
|
|
@@ -17,10 +17,12 @@ export function configureConfigStore(paths, _onValueWritten, _logger) {
|
|
|
17
17
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const googleLogLevels = ["DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "CRITICAL", "ALERT", "EMERGENCY"];
|
|
21
|
+
|
|
20
22
|
function decideSeverity(newData) {
|
|
21
23
|
|
|
22
|
-
const text = newData.severity_text?.
|
|
23
|
-
return
|
|
24
|
+
const text = newData.severity_text?.toUpperCase();
|
|
25
|
+
return googleLogLevels.includes(text) ? text : "info";
|
|
24
26
|
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -30,7 +32,7 @@ function decideMessage(newData) {
|
|
|
30
32
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
function
|
|
35
|
+
function handleO11yEventWritten(observe, e) {
|
|
34
36
|
|
|
35
37
|
const { after } = e.data;
|
|
36
38
|
const newData = after.val();
|
|
@@ -39,7 +41,7 @@ function handleLogValueWritten(logger, e) {
|
|
|
39
41
|
const now = new Date();
|
|
40
42
|
const time_server_ms = now.valueOf();
|
|
41
43
|
const timestamp = now.toISOString();
|
|
42
|
-
|
|
44
|
+
observe({
|
|
43
45
|
"logging.googleapis.com/trace": `projects/${process.env.GCLOUD_PROJECT}/traces/${newData.trace_id}`,
|
|
44
46
|
"logging.googleapis.com/spanId": newData.span_id,
|
|
45
47
|
"message": decideMessage(newData),
|
|
@@ -49,31 +51,30 @@ function handleLogValueWritten(logger, e) {
|
|
|
49
51
|
time_server_ms
|
|
50
52
|
},
|
|
51
53
|
timestamp
|
|
52
|
-
};
|
|
54
|
+
});
|
|
53
55
|
after.ref.update({ time_server_ms });
|
|
54
|
-
logger.write(payload);
|
|
55
56
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
export function configureLoggingStore({ paths, onValueWritten,
|
|
61
|
+
export function configureLoggingStore({ paths, onValueWritten, observe, region, instance }) {
|
|
61
62
|
|
|
62
63
|
if (!paths) throw new Error("Missing paths");
|
|
63
64
|
if (!onValueWritten) throw new Error("Missing onValueWritten");
|
|
64
|
-
if (!
|
|
65
|
+
if (!observe) throw new Error("Missing observe");
|
|
65
66
|
if (!region) throw new Error("Missing region");
|
|
66
67
|
if (!instance) throw new Error("Missing instance");
|
|
67
68
|
|
|
68
|
-
const ref = paths?.
|
|
69
|
-
if (!ref) throw new Error("Missing: paths.
|
|
69
|
+
const ref = paths?.o11y?.path;
|
|
70
|
+
if (!ref) throw new Error("Missing: paths.o11y.path");
|
|
70
71
|
const triggerPath = refPathToTriggerPath(ref);
|
|
71
72
|
return Object.assign(
|
|
72
73
|
onValueWritten({
|
|
73
74
|
ref: triggerPath,
|
|
74
75
|
region,
|
|
75
76
|
instance
|
|
76
|
-
},
|
|
77
|
+
}, handleO11yEventWritten.bind(this, observe)),
|
|
77
78
|
{
|
|
78
79
|
[RAW_STORE_META]: {
|
|
79
80
|
raw: true,
|
|
@@ -82,7 +83,7 @@ export function configureLoggingStore({ paths, onValueWritten, logger, region, i
|
|
|
82
83
|
write: all(
|
|
83
84
|
// required
|
|
84
85
|
assertNewDataHasString("body", 500, 1),
|
|
85
|
-
assertNewDataHasOneOf("severity_text",
|
|
86
|
+
assertNewDataHasOneOf("severity_text", ...googleLogLevels),
|
|
86
87
|
// trace context
|
|
87
88
|
all(
|
|
88
89
|
assertNewDataHasLowercaseHexCharacters("trace_id", 32),
|
|
@@ -4,10 +4,10 @@ import { refPathToTriggerPath } from "../paths.js";
|
|
|
4
4
|
|
|
5
5
|
export const EVENT_STORE_META = Symbol("Event store configuration metadata");
|
|
6
6
|
|
|
7
|
-
export function configureEventStore({ ref, projections, ...rest }, { onValueWritten,
|
|
7
|
+
export function configureEventStore({ ref, projections, ...rest }, { onValueWritten, observe, region, instance }) {
|
|
8
8
|
|
|
9
9
|
if (!onValueWritten) throw new Error("Missing onValueWritten");
|
|
10
|
-
if (!
|
|
10
|
+
if (!observe) throw new Error("Missing observe");
|
|
11
11
|
if (!region) throw new Error("Missing region");
|
|
12
12
|
if (!instance) throw new Error("Missing instance");
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@ export function configureEventStore({ ref, projections, ...rest }, { onValueWrit
|
|
|
18
18
|
const writeRefPath = `${eventStorePath}/${EVENTS}/{eid}`;
|
|
19
19
|
|
|
20
20
|
// handle an incoming write event
|
|
21
|
-
const handler = e => updateProjections(e.data.after, projections,
|
|
21
|
+
const handler = e => updateProjections(e.data.after, projections, observe, rest.debug);
|
|
22
22
|
|
|
23
23
|
const metadata = {
|
|
24
24
|
...rest,
|
|
@@ -35,10 +35,10 @@ export function configureEventStore({ ref, projections, ...rest }, { onValueWrit
|
|
|
35
35
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
configureEventStore.inject = ({ onValueWritten,
|
|
38
|
+
configureEventStore.inject = ({ onValueWritten, observe, paths, region, instance }) =>
|
|
39
39
|
|
|
40
40
|
(eventStoreFactory, ...args) =>
|
|
41
41
|
|
|
42
|
-
configureEventStore(eventStoreFactory(paths, ...args), { onValueWritten,
|
|
42
|
+
configureEventStore(eventStoreFactory(paths, ...args), { onValueWritten, observe, region, instance });
|
|
43
43
|
|
|
44
44
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PROJECTIONS } from "../../dist/browser/client/event-store.js";
|
|
2
2
|
import { inspect } from "node:util";
|
|
3
3
|
|
|
4
|
-
export async function updateProjections(incomingEventSnap, projectionTransformationPerEvent,
|
|
4
|
+
export async function updateProjections(incomingEventSnap, projectionTransformationPerEvent, observe, debug = false) {
|
|
5
5
|
|
|
6
6
|
// order the events
|
|
7
7
|
const eventsSnap = await incomingEventSnap.ref.parent.get();
|
|
@@ -35,7 +35,11 @@ export async function updateProjections(incomingEventSnap, projectionTransformat
|
|
|
35
35
|
|
|
36
36
|
return (agg, e) => {
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
observe({
|
|
39
|
+
severity: "WARNING",
|
|
40
|
+
message: "Missing transform for event",
|
|
41
|
+
logData: { projection, event: e }
|
|
42
|
+
});
|
|
39
43
|
return agg;
|
|
40
44
|
|
|
41
45
|
};
|
|
@@ -46,9 +50,13 @@ export async function updateProjections(incomingEventSnap, projectionTransformat
|
|
|
46
50
|
|
|
47
51
|
const projectionRef = incomingEventSnap.ref.parent.parent.child(PROJECTIONS).child(projection);
|
|
48
52
|
const incomingEvent = incomingEventSnap.val();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
observe({
|
|
54
|
+
severity: "INFO",
|
|
55
|
+
message: "Writing projection after event",
|
|
56
|
+
logData: {
|
|
57
|
+
path: projectionRef,
|
|
58
|
+
incomingEvent
|
|
59
|
+
}
|
|
52
60
|
});
|
|
53
61
|
try {
|
|
54
62
|
|
|
@@ -58,7 +66,11 @@ export async function updateProjections(incomingEventSnap, projectionTransformat
|
|
|
58
66
|
|
|
59
67
|
const data = inspect(view).replace(/\n */g, " ");
|
|
60
68
|
const path = projectionRef.toString();
|
|
61
|
-
|
|
69
|
+
observe({
|
|
70
|
+
severity: "ERROR",
|
|
71
|
+
message: `Error writing projection: ${err?.message}`,
|
|
72
|
+
logData: { projection, path, data }
|
|
73
|
+
});
|
|
62
74
|
|
|
63
75
|
}
|
|
64
76
|
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export async function provider({ trace, projectId, serviceName, log }) {
|
|
2
|
+
|
|
3
|
+
if (!serviceName) throw new Error("Missing serviceName");
|
|
4
|
+
if (!trace) throw new Error("Missing trace");
|
|
5
|
+
if (!projectId) throw new Error("Missing projectId");
|
|
6
|
+
if (!log) throw new Error("Missing log");
|
|
7
|
+
|
|
8
|
+
return async ({
|
|
9
|
+
message, severity, // required
|
|
10
|
+
traceId, spanId, timestamp, // advised
|
|
11
|
+
logData, // optional log line
|
|
12
|
+
spanStart, spanEnd, spanAttributes // optional trace attributes
|
|
13
|
+
}) => {
|
|
14
|
+
|
|
15
|
+
if (!message) throw new Error("Missing message");
|
|
16
|
+
if (!severity) throw new Error("Missing severity");
|
|
17
|
+
|
|
18
|
+
traceId = traceId || console.warn("No trace id provided - one will be generated") || generateLowerHex(16);
|
|
19
|
+
spanId = spanId || console.warn("No span id provided - one will be generated") || generateLowerHex(8);
|
|
20
|
+
timestamp = timestamp || console.warn("No timestamp provided - one will be generated") || new Date().toISOString();
|
|
21
|
+
if (!spanStart) {
|
|
22
|
+
|
|
23
|
+
spanStart = spanEnd = new Date(timestamp);
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const tracePath = `projects/${projectId}/traces/${traceId}`;
|
|
28
|
+
const projectTraceSpanName = `${tracePath}/spans/${spanId}`;
|
|
29
|
+
|
|
30
|
+
const startTime = asISOString(spanStart);
|
|
31
|
+
const endTime = asISOString(spanEnd);
|
|
32
|
+
|
|
33
|
+
await createSpan({ trace }, { serviceName, projectTraceSpanName, message, spanId, startTime, endTime, spanAttributes });
|
|
34
|
+
await writeLogLine({ log, projectId }, { tracePath, spanId, severity, timestamp, message, logData });
|
|
35
|
+
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function generateLowerHex(byteLength) {
|
|
41
|
+
|
|
42
|
+
const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
|
|
43
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function toTraceAttributes(obj) {
|
|
48
|
+
|
|
49
|
+
const attributeMap = {};
|
|
50
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
51
|
+
if (value === null || value === undefined) continue;
|
|
52
|
+
|
|
53
|
+
switch (typeof value) {
|
|
54
|
+
case 'string':
|
|
55
|
+
attributeMap[key] = { stringValue: { value } };
|
|
56
|
+
break;
|
|
57
|
+
case 'number':
|
|
58
|
+
// Cloud Trace expects integers as strings
|
|
59
|
+
attributeMap[key] = { intValue: String(Math.trunc(value)) };
|
|
60
|
+
break;
|
|
61
|
+
case 'boolean':
|
|
62
|
+
attributeMap[key] = { boolValue: value };
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
// fallback to JSON string
|
|
66
|
+
attributeMap[key] = { stringValue: { value: JSON.stringify(value) } };
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { attributeMap };
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function writeLogLine({ log, projectId }, { message, tracePath, spanId, severity, timestamp, logData }) {
|
|
75
|
+
|
|
76
|
+
const logMeta = {
|
|
77
|
+
resource: { type: "global", labels: { "project_id": projectId } },
|
|
78
|
+
trace: tracePath,
|
|
79
|
+
spanId,
|
|
80
|
+
severity,
|
|
81
|
+
traceSampled: "true",
|
|
82
|
+
timestamp
|
|
83
|
+
};
|
|
84
|
+
console.log("Log meta", logMeta);
|
|
85
|
+
|
|
86
|
+
const entry = log.entry(
|
|
87
|
+
logMeta,
|
|
88
|
+
{
|
|
89
|
+
message,
|
|
90
|
+
...logData
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
await log.write(entry);
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function createSpan({ trace }, { serviceName, projectTraceSpanName, spanId, startTime, endTime, spanAttributes, message }) {
|
|
98
|
+
|
|
99
|
+
const attributes = toTraceAttributes({
|
|
100
|
+
"service.name": serviceName,
|
|
101
|
+
message,
|
|
102
|
+
...spanAttributes
|
|
103
|
+
});
|
|
104
|
+
await trace.projects.traces.spans.createSpan({
|
|
105
|
+
name: projectTraceSpanName,
|
|
106
|
+
requestBody: {
|
|
107
|
+
attributes,
|
|
108
|
+
name: projectTraceSpanName,
|
|
109
|
+
spanId,
|
|
110
|
+
displayName: { value: serviceName },
|
|
111
|
+
startTime,
|
|
112
|
+
endTime
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function asISOString(x) {
|
|
119
|
+
|
|
120
|
+
if (typeof x === "string") return x;
|
|
121
|
+
if (typeof x?.toISOString !== "function") throw new Error("Missing toISOString function");
|
|
122
|
+
return x.toISOString();
|
|
123
|
+
}
|