@friggframework/core 2.0.0--canary.652.b14f3b5.0 → 2.0.0--canary.654.42e42b2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +13 -12
- package/README.md +19 -22
- package/core/CLAUDE.md +25 -12
- package/core/Worker.js +42 -34
- package/core/create-handler.js +99 -197
- package/core/index.js +8 -1
- package/core/invocation-scope.js +224 -0
- package/core/secrets-to-env.js +7 -6
- package/database/config.js +0 -7
- package/database/encryption/README.md +5 -1
- package/database/encryption/encryption-schema-registry.js +3 -0
- package/errors/fetch-error.js +48 -65
- package/handlers/app-definition-loader.js +53 -2
- package/handlers/app-handler-helpers.js +16 -4
- package/handlers/database-migration-handler.js +10 -1
- package/handlers/routers/websocket.js +26 -6
- package/handlers/workers/db-migration.js +12 -1
- package/handlers/workers/dlq-processor.js +66 -38
- package/index.js +19 -1
- package/integrations/integration-base.js +56 -38
- package/integrations/integration-router.js +21 -20
- package/integrations/use-cases/delete-integration-for-user.js +24 -13
- package/jest.config.js +2 -0
- package/logs/.eslintrc.json +26 -1
- package/logs/__fixtures__/events.js +82 -0
- package/logs/__fixtures__/matchers.js +45 -0
- package/logs/__fixtures__/secrets.js +22 -0
- package/logs/__fixtures__/vectors.js +329 -0
- package/logs/__fixtures__/with-env.js +45 -0
- package/logs/context.js +92 -0
- package/logs/debug-shims.js +49 -0
- package/logs/index.js +14 -3
- package/logs/jest-logger-setup.js +25 -0
- package/logs/levels.js +71 -0
- package/logs/logger-runtime.js +142 -0
- package/logs/logger.js +132 -42
- package/logs/record.js +199 -0
- package/logs/redact.js +325 -0
- package/logs/serialize.js +322 -0
- package/logs/sinks.js +99 -0
- package/logs/summarize-event.js +136 -0
- package/modules/module.js +32 -19
- package/modules/requester/oauth-2.js +28 -22
- package/modules/requester/requester.js +69 -6
- package/package.json +5 -6
- package/queues/queuer-util.js +1 -15
- package/telemetry/bind-telemetry-context.js +4 -0
- package/telemetry/instrument-handler.js +19 -2
- package/telemetry/no-op-telemetry.js +4 -0
- package/telemetry/otel-telemetry.js +25 -6
- package/telemetry/telemetry-context.js +19 -8
- package/telemetry/telemetry-runtime.js +17 -0
- package/telemetry/telemetry-service.js +1 -0
- package/types/errors/index.d.ts +24 -10
- package/types/logs/index.d.ts +61 -2
- package/user/repositories/user-repository-documentdb.js +18 -42
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const { LEVELS, parseLevel, resolveLevel } = require('./levels');
|
|
2
|
+
|
|
3
|
+
// On globalThis so jest.resetModules and nested core copies share one state.
|
|
4
|
+
const STATE_KEY = Symbol.for('@friggframework/core.logs');
|
|
5
|
+
|
|
6
|
+
function state() {
|
|
7
|
+
if (!globalThis[STATE_KEY]) {
|
|
8
|
+
globalThis[STATE_KEY] = {
|
|
9
|
+
config: null,
|
|
10
|
+
levelOverride: null,
|
|
11
|
+
sinks: null,
|
|
12
|
+
spanContextProvider: null,
|
|
13
|
+
warned: new Set(),
|
|
14
|
+
inLog: false,
|
|
15
|
+
trackViolations: false,
|
|
16
|
+
violations: [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return globalThis[STATE_KEY];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readEnv(name) {
|
|
23
|
+
const value = process.env[name];
|
|
24
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getConfig() {
|
|
28
|
+
const s = state();
|
|
29
|
+
if (!s.config) {
|
|
30
|
+
const resolved = s.levelOverride
|
|
31
|
+
? { level: s.levelOverride, warnings: [] }
|
|
32
|
+
: resolveLevel(process.env);
|
|
33
|
+
s.config = {
|
|
34
|
+
level: resolved.level,
|
|
35
|
+
threshold: LEVELS[resolved.level],
|
|
36
|
+
appName: readEnv('FRIGG_STACK'),
|
|
37
|
+
stage: readEnv('STAGE'),
|
|
38
|
+
pendingWarnings: resolved.warnings,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return s.config;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function takeConfigWarnings() {
|
|
45
|
+
const s = state();
|
|
46
|
+
const config = getConfig();
|
|
47
|
+
const pending = config.pendingWarnings.splice(0);
|
|
48
|
+
return pending.filter((warning) => {
|
|
49
|
+
if (s.warned.has(warning.eventName)) return false;
|
|
50
|
+
s.warned.add(warning.eventName);
|
|
51
|
+
return true;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getSinks() {
|
|
56
|
+
const s = state();
|
|
57
|
+
if (!s.sinks) {
|
|
58
|
+
s.sinks = [require('./sinks').createStdoutSink()];
|
|
59
|
+
}
|
|
60
|
+
return s.sinks;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function setSinks(sinks) {
|
|
64
|
+
state().sinks = Array.isArray(sinks) ? [...sinks] : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasFlushableSinks() {
|
|
68
|
+
return getSinks().some((sink) => typeof sink.flush === 'function');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function flushSinks({ signal } = {}) {
|
|
72
|
+
const flushes = getSinks()
|
|
73
|
+
.filter((sink) => typeof sink.flush === 'function')
|
|
74
|
+
.map((sink) => {
|
|
75
|
+
try {
|
|
76
|
+
return Promise.resolve(sink.flush({ signal }));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return Promise.reject(error);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
await Promise.allSettled(flushes);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setSpanContextProvider(provider) {
|
|
85
|
+
state().spanContextProvider = typeof provider === 'function' ? provider : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSpanContextProvider() {
|
|
89
|
+
return state().spanContextProvider;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getSpanContext() {
|
|
93
|
+
const provider = state().spanContextProvider;
|
|
94
|
+
if (!provider) return null;
|
|
95
|
+
try {
|
|
96
|
+
const ctx = provider();
|
|
97
|
+
if (ctx && typeof ctx.traceId === 'string' && typeof ctx.spanId === 'string') {
|
|
98
|
+
return ctx;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// A broken provider must not break logging.
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Off in production, so a warm container never grows this list.
|
|
107
|
+
function recordViolation(violation) {
|
|
108
|
+
const s = state();
|
|
109
|
+
if (s.trackViolations) s.violations.push(violation);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function takeViolationsForTests() {
|
|
113
|
+
return state().violations.splice(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function resetLoggerForTests({ level, sinks, trackViolations } = {}) {
|
|
117
|
+
const s = state();
|
|
118
|
+
s.config = null;
|
|
119
|
+
s.levelOverride = parseLevel(level);
|
|
120
|
+
s.sinks = Array.isArray(sinks) ? [...sinks] : [require('./sinks').createStdoutSink()];
|
|
121
|
+
s.spanContextProvider = null;
|
|
122
|
+
s.warned.clear();
|
|
123
|
+
s.violations.length = 0;
|
|
124
|
+
s.inLog = false;
|
|
125
|
+
if (typeof trackViolations === 'boolean') s.trackViolations = trackViolations;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
state,
|
|
130
|
+
getConfig,
|
|
131
|
+
takeConfigWarnings,
|
|
132
|
+
getSinks,
|
|
133
|
+
setSinks,
|
|
134
|
+
hasFlushableSinks,
|
|
135
|
+
flushSinks,
|
|
136
|
+
setSpanContextProvider,
|
|
137
|
+
getSpanContextProvider,
|
|
138
|
+
getSpanContext,
|
|
139
|
+
recordViolation,
|
|
140
|
+
takeViolationsForTests,
|
|
141
|
+
resetLoggerForTests,
|
|
142
|
+
};
|
package/logs/logger.js
CHANGED
|
@@ -1,65 +1,155 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { LEVELS, parseLevel } = require('./levels');
|
|
2
|
+
const { getLoggerScope } = require('./context');
|
|
3
|
+
const runtime = require('./logger-runtime');
|
|
4
|
+
const { buildRecord, DEFAULT_LOGGER_NAME } = require('./record');
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
/* eslint-disable no-console */
|
|
6
|
+
const LOGGER_OWN_NAME = 'frigg.logger';
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
function copyBindings(bindings) {
|
|
9
|
+
const copy = {};
|
|
10
|
+
if (!bindings || typeof bindings !== 'object') return copy;
|
|
11
|
+
for (const key of Object.keys(bindings)) {
|
|
12
|
+
try {
|
|
13
|
+
copy[key] = bindings[key];
|
|
14
|
+
} catch {
|
|
15
|
+
// A throwing getter drops only that binding.
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return copy;
|
|
19
|
+
}
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
function evaluateBindings(sources) {
|
|
22
|
+
const merged = {};
|
|
23
|
+
for (const source of sources) {
|
|
24
|
+
let value = source;
|
|
25
|
+
if (typeof source === 'function') {
|
|
26
|
+
try {
|
|
27
|
+
value = source();
|
|
28
|
+
} catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
18
31
|
}
|
|
32
|
+
Object.assign(merged, copyBindings(value));
|
|
19
33
|
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isFriggViolation(record) {
|
|
38
|
+
return (
|
|
39
|
+
record.logger.startsWith('frigg.') &&
|
|
40
|
+
LEVELS[record.level] >= LEVELS.WARN &&
|
|
41
|
+
!record.eventName
|
|
42
|
+
);
|
|
20
43
|
}
|
|
21
44
|
|
|
22
|
-
|
|
23
|
-
|
|
45
|
+
class Logger {
|
|
46
|
+
constructor(name, bindingSources = []) {
|
|
47
|
+
this.name = name;
|
|
48
|
+
this._bindingSources = bindingSources;
|
|
49
|
+
}
|
|
24
50
|
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
trace(message, fields) {
|
|
52
|
+
this._log('TRACE', message, fields);
|
|
53
|
+
}
|
|
27
54
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
55
|
+
debug(message, fields) {
|
|
56
|
+
this._log('DEBUG', message, fields);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
info(message, fields) {
|
|
60
|
+
this._log('INFO', message, fields);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
warn(message, fields) {
|
|
64
|
+
this._log('WARN', message, fields);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
error(message, fields) {
|
|
68
|
+
this._log('ERROR', message, fields);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fatal(message, fields) {
|
|
72
|
+
this._log('FATAL', message, fields);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
child(bindings) {
|
|
76
|
+
const source = typeof bindings === 'function' ? bindings : copyBindings(bindings);
|
|
77
|
+
return new Logger(this.name, [...this._bindingSources, source]);
|
|
78
|
+
}
|
|
31
79
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
80
|
+
isLevelEnabled(level) {
|
|
81
|
+
try {
|
|
82
|
+
const name = parseLevel(level);
|
|
83
|
+
return name !== null && LEVELS[name] >= runtime.getConfig().threshold;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
37
87
|
}
|
|
38
88
|
|
|
39
|
-
|
|
89
|
+
_log(level, message, fields) {
|
|
90
|
+
const state = runtime.state();
|
|
91
|
+
if (state.inLog) return;
|
|
92
|
+
try {
|
|
93
|
+
const config = runtime.getConfig();
|
|
94
|
+
emitConfigWarnings();
|
|
95
|
+
if (LEVELS[level] < config.threshold) return;
|
|
96
|
+
// Set before fields, bindings and sinks run: any of them may log.
|
|
97
|
+
state.inLog = true;
|
|
40
98
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
99
|
+
const record = buildRecord({
|
|
100
|
+
level,
|
|
101
|
+
logger: this.name,
|
|
102
|
+
message,
|
|
103
|
+
fields,
|
|
104
|
+
bindings: evaluateBindings(this._bindingSources),
|
|
105
|
+
scope: getLoggerScope(),
|
|
106
|
+
spanContext: runtime.getSpanContext(),
|
|
107
|
+
resource: { appName: config.appName, stage: config.stage },
|
|
108
|
+
});
|
|
109
|
+
if (isFriggViolation(record)) {
|
|
110
|
+
runtime.recordViolation({
|
|
111
|
+
logger: record.logger,
|
|
112
|
+
level: record.level,
|
|
113
|
+
message: record.message,
|
|
114
|
+
});
|
|
46
115
|
}
|
|
116
|
+
writeToSinks(record);
|
|
117
|
+
} catch {
|
|
118
|
+
// The logger never throws.
|
|
119
|
+
} finally {
|
|
120
|
+
state.inLog = false;
|
|
47
121
|
}
|
|
48
122
|
}
|
|
123
|
+
}
|
|
49
124
|
|
|
50
|
-
|
|
51
|
-
|
|
125
|
+
function writeToSinks(record) {
|
|
126
|
+
for (const sink of runtime.getSinks()) {
|
|
127
|
+
try {
|
|
128
|
+
sink.write(record);
|
|
129
|
+
} catch {
|
|
130
|
+
// One failing sink must not stop the others.
|
|
131
|
+
}
|
|
52
132
|
}
|
|
133
|
+
}
|
|
53
134
|
|
|
54
|
-
|
|
135
|
+
function emitConfigWarnings() {
|
|
136
|
+
const warnings = runtime.takeConfigWarnings();
|
|
137
|
+
if (!warnings.length) return;
|
|
138
|
+
const logger = getLogger(LOGGER_OWN_NAME);
|
|
139
|
+
for (const warning of warnings) {
|
|
140
|
+
logger.warn(warning.message, {
|
|
141
|
+
...warning.fields,
|
|
142
|
+
eventName: warning.eventName,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
55
146
|
|
|
56
|
-
|
|
147
|
+
const loggers = new Map();
|
|
57
148
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
149
|
+
function getLogger(name) {
|
|
150
|
+
const key = typeof name === 'string' && name ? name : DEFAULT_LOGGER_NAME;
|
|
151
|
+
if (!loggers.has(key)) loggers.set(key, new Logger(key));
|
|
152
|
+
return loggers.get(key);
|
|
63
153
|
}
|
|
64
154
|
|
|
65
|
-
module.exports = {
|
|
155
|
+
module.exports = { Logger, getLogger };
|
package/logs/record.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const { LEVELS } = require('./levels');
|
|
2
|
+
const { serializeValue, serializeError } = require('./serialize');
|
|
3
|
+
|
|
4
|
+
const MAX_RECORD_BYTES = 16 * 1024;
|
|
5
|
+
const REQUIRED_KEYS = ['timestamp', 'level', 'message', 'logger'];
|
|
6
|
+
const RESOURCE_KEYS = ['appName', 'stage'];
|
|
7
|
+
const TRACE_KEYS = ['trace_id', 'span_id', 'trace_flags'];
|
|
8
|
+
const DROPPED_KEYS_FIELD = 'droppedKeys';
|
|
9
|
+
const OWNED_KEYS = new Set([
|
|
10
|
+
...REQUIRED_KEYS,
|
|
11
|
+
...RESOURCE_KEYS,
|
|
12
|
+
...TRACE_KEYS,
|
|
13
|
+
DROPPED_KEYS_FIELD,
|
|
14
|
+
]);
|
|
15
|
+
const RESERVED_KEYS = new Set([
|
|
16
|
+
'tenantId',
|
|
17
|
+
'type',
|
|
18
|
+
'time',
|
|
19
|
+
'record',
|
|
20
|
+
'errorType',
|
|
21
|
+
'errorMessage',
|
|
22
|
+
'stackTrace',
|
|
23
|
+
'service',
|
|
24
|
+
'env',
|
|
25
|
+
'host',
|
|
26
|
+
'source',
|
|
27
|
+
'status',
|
|
28
|
+
'severity',
|
|
29
|
+
]);
|
|
30
|
+
const PAYLOAD_KEYS = new Set(['body', 'rawBody', 'payload', 'response']);
|
|
31
|
+
const DEFAULT_LOGGER_NAME = 'frigg.unknown';
|
|
32
|
+
|
|
33
|
+
function isError(value) {
|
|
34
|
+
return (
|
|
35
|
+
value instanceof Error ||
|
|
36
|
+
Object.prototype.toString.call(value) === '[object Error]'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function deepFreeze(value) {
|
|
41
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
42
|
+
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
43
|
+
Object.freeze(value);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readFields(source) {
|
|
49
|
+
const result = {};
|
|
50
|
+
if (!source || typeof source !== 'object') return result;
|
|
51
|
+
let keys;
|
|
52
|
+
try {
|
|
53
|
+
keys = Object.keys(source);
|
|
54
|
+
} catch {
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
for (const key of keys) {
|
|
58
|
+
try {
|
|
59
|
+
result[key] = source[key];
|
|
60
|
+
} catch {
|
|
61
|
+
result[key] = '[Getter threw]';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildMessage(message, callSite) {
|
|
68
|
+
if (typeof message === 'string') {
|
|
69
|
+
return serializeValue(message);
|
|
70
|
+
}
|
|
71
|
+
if (message === undefined || message === null) return '';
|
|
72
|
+
if (isError(message)) {
|
|
73
|
+
const error = serializeError(message);
|
|
74
|
+
if (callSite.error === undefined) callSite.error = message;
|
|
75
|
+
return typeof error?.message === 'string' ? error.message : '';
|
|
76
|
+
}
|
|
77
|
+
if (callSite.value === undefined) callSite.value = message;
|
|
78
|
+
return '[non-string message]';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function traceFields(spanContext) {
|
|
82
|
+
if (
|
|
83
|
+
!spanContext ||
|
|
84
|
+
typeof spanContext.traceId !== 'string' ||
|
|
85
|
+
typeof spanContext.spanId !== 'string'
|
|
86
|
+
) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
const flags = Number(spanContext.traceFlags) || 0;
|
|
90
|
+
return {
|
|
91
|
+
trace_id: spanContext.traceId,
|
|
92
|
+
span_id: spanContext.spanId,
|
|
93
|
+
trace_flags: (flags & 0xff).toString(16).padStart(2, '0'),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function byteLength(record) {
|
|
98
|
+
return Buffer.byteLength(JSON.stringify(record));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildRecord({
|
|
102
|
+
level,
|
|
103
|
+
logger,
|
|
104
|
+
message,
|
|
105
|
+
fields,
|
|
106
|
+
bindings,
|
|
107
|
+
scope,
|
|
108
|
+
spanContext,
|
|
109
|
+
resource = {},
|
|
110
|
+
now = new Date(),
|
|
111
|
+
} = {}) {
|
|
112
|
+
const dropPayloads = LEVELS[level] >= LEVELS.INFO;
|
|
113
|
+
// log.error('msg', err): own props of an Error (axios config, request) must not become fields.
|
|
114
|
+
const callSite = readFields(isError(fields) ? { error: fields } : fields);
|
|
115
|
+
const record = {
|
|
116
|
+
timestamp: now.toISOString(),
|
|
117
|
+
level,
|
|
118
|
+
message: buildMessage(message, callSite),
|
|
119
|
+
logger: typeof logger === 'string' && logger ? logger : DEFAULT_LOGGER_NAME,
|
|
120
|
+
};
|
|
121
|
+
for (const key of RESOURCE_KEYS) {
|
|
122
|
+
if (typeof resource[key] === 'string' && resource[key]) {
|
|
123
|
+
record[key] = resource[key];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
Object.assign(record, traceFields(spanContext));
|
|
127
|
+
|
|
128
|
+
const dropped = new Set();
|
|
129
|
+
const callSiteKeys = [];
|
|
130
|
+
const place = (source, isCallSite) => {
|
|
131
|
+
const accepted = {};
|
|
132
|
+
for (const [key, value] of Object.entries(source)) {
|
|
133
|
+
if (value === undefined || value === null) continue;
|
|
134
|
+
if (
|
|
135
|
+
OWNED_KEYS.has(key) ||
|
|
136
|
+
RESERVED_KEYS.has(key) ||
|
|
137
|
+
(dropPayloads && PAYLOAD_KEYS.has(key)) ||
|
|
138
|
+
Object.prototype.hasOwnProperty.call(record, key)
|
|
139
|
+
) {
|
|
140
|
+
dropped.add(key);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
accepted[key] = value;
|
|
144
|
+
}
|
|
145
|
+
// One pass over the object, so key rules (denied keys, digests,
|
|
146
|
+
// headers, OAuth shapes) apply to top-level fields too.
|
|
147
|
+
const safe = serializeValue(accepted);
|
|
148
|
+
for (const key of Object.keys(accepted)) {
|
|
149
|
+
const value =
|
|
150
|
+
key === 'error' ? serializeError(accepted[key]) : safe?.[key];
|
|
151
|
+
if (value === undefined || value === null) continue;
|
|
152
|
+
record[key] = value;
|
|
153
|
+
if (isCallSite) callSiteKeys.push(key);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
place(readFields(scope), false);
|
|
157
|
+
place(readFields(bindings), false);
|
|
158
|
+
place(callSite, true);
|
|
159
|
+
|
|
160
|
+
const measure = () => {
|
|
161
|
+
delete record[DROPPED_KEYS_FIELD];
|
|
162
|
+
if (dropped.size) record[DROPPED_KEYS_FIELD] = [...dropped];
|
|
163
|
+
return byteLength(record);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (measure() > MAX_RECORD_BYTES) {
|
|
167
|
+
const bySize = callSiteKeys
|
|
168
|
+
.filter((key) => key !== 'error')
|
|
169
|
+
.map((key) => [key, Buffer.byteLength(JSON.stringify(record[key]))])
|
|
170
|
+
.sort((a, b) => b[1] - a[1]);
|
|
171
|
+
for (const [key] of bySize) {
|
|
172
|
+
delete record[key];
|
|
173
|
+
dropped.add(key);
|
|
174
|
+
if (measure() <= MAX_RECORD_BYTES) break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const part of ['stack', 'cause']) {
|
|
178
|
+
if (
|
|
179
|
+
record.error &&
|
|
180
|
+
typeof record.error === 'object' &&
|
|
181
|
+
record.error[part] !== undefined &&
|
|
182
|
+
measure() > MAX_RECORD_BYTES
|
|
183
|
+
) {
|
|
184
|
+
const { [part]: _removed, ...rest } = record.error;
|
|
185
|
+
record.error = rest;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
measure();
|
|
189
|
+
|
|
190
|
+
return deepFreeze(record);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
buildRecord,
|
|
195
|
+
MAX_RECORD_BYTES,
|
|
196
|
+
RESERVED_KEYS,
|
|
197
|
+
PAYLOAD_KEYS,
|
|
198
|
+
DEFAULT_LOGGER_NAME,
|
|
199
|
+
};
|