arcway 0.1.4 → 0.1.6
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/server/context.js +54 -2
- package/server/logger/index.js +10 -0
- package/server/router/api-router.js +28 -15
package/package.json
CHANGED
package/server/context.js
CHANGED
|
@@ -1,15 +1,67 @@
|
|
|
1
|
+
function trackDb(db) {
|
|
2
|
+
if (!db || typeof db !== 'function' && typeof db !== 'object') return { db, stats: { queries: 0, durationMs: 0 } };
|
|
3
|
+
const stats = { queries: 0, durationMs: 0 };
|
|
4
|
+
const handler = {
|
|
5
|
+
get(target, prop, receiver) {
|
|
6
|
+
const value = Reflect.get(target, prop, receiver);
|
|
7
|
+
if (typeof value !== 'function') return value;
|
|
8
|
+
if (prop === 'raw' || prop === 'select' || prop === 'insert' || prop === 'update' ||
|
|
9
|
+
prop === 'delete' || prop === 'del' || prop === 'from' || prop === 'into' ||
|
|
10
|
+
prop === 'table' || prop === 'with' || prop === 'withRecursive') {
|
|
11
|
+
return (...args) => {
|
|
12
|
+
const builder = value.apply(target, args);
|
|
13
|
+
return wrapBuilder(builder, stats);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return value.bind ? value.bind(target) : value;
|
|
17
|
+
},
|
|
18
|
+
apply(target, thisArg, args) {
|
|
19
|
+
const builder = Reflect.apply(target, thisArg, args);
|
|
20
|
+
if (builder && typeof builder.then === 'function' && typeof builder.select === 'function') {
|
|
21
|
+
return wrapBuilder(builder, stats);
|
|
22
|
+
}
|
|
23
|
+
return builder;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const proxy = new Proxy(db, handler);
|
|
27
|
+
return { db: proxy, stats };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function wrapBuilder(builder, stats) {
|
|
31
|
+
const origThen = builder.then.bind(builder);
|
|
32
|
+
builder.then = (resolve, reject) => {
|
|
33
|
+
const start = performance.now();
|
|
34
|
+
return origThen(
|
|
35
|
+
(result) => {
|
|
36
|
+
stats.queries++;
|
|
37
|
+
stats.durationMs += performance.now() - start;
|
|
38
|
+
return resolve?.(result);
|
|
39
|
+
},
|
|
40
|
+
(err) => {
|
|
41
|
+
stats.queries++;
|
|
42
|
+
stats.durationMs += performance.now() - start;
|
|
43
|
+
if (reject) return reject(err);
|
|
44
|
+
throw err;
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
return builder;
|
|
49
|
+
}
|
|
50
|
+
|
|
1
51
|
function buildContext(appContext, extras) {
|
|
52
|
+
const tracked = trackDb(appContext.db);
|
|
2
53
|
const ctx = {
|
|
3
|
-
db:
|
|
54
|
+
db: tracked.db,
|
|
4
55
|
log: appContext.log,
|
|
5
56
|
events: appContext.events,
|
|
6
57
|
cache: appContext.cache,
|
|
7
58
|
queue: appContext.queue,
|
|
8
59
|
files: appContext.files,
|
|
9
60
|
mail: appContext.mail,
|
|
61
|
+
_dbStats: tracked.stats,
|
|
10
62
|
...extras,
|
|
11
63
|
};
|
|
12
64
|
return ctx;
|
|
13
65
|
}
|
|
14
66
|
|
|
15
|
-
export { buildContext };
|
|
67
|
+
export { buildContext, trackDb };
|
package/server/logger/index.js
CHANGED
|
@@ -112,6 +112,15 @@ class Logger {
|
|
|
112
112
|
return this.query({ level: 'error', limit, since });
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
addContext(fields) {
|
|
116
|
+
if (!this._canonicalContext) this._canonicalContext = {};
|
|
117
|
+
Object.assign(this._canonicalContext, fields);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getCanonicalContext() {
|
|
121
|
+
return this._canonicalContext ?? null;
|
|
122
|
+
}
|
|
123
|
+
|
|
115
124
|
extend(fields) {
|
|
116
125
|
const child = Object.create(Logger.prototype);
|
|
117
126
|
const hasFields = Object.keys(fields).length > 0;
|
|
@@ -123,6 +132,7 @@ class Logger {
|
|
|
123
132
|
child._root = this._root ?? this;
|
|
124
133
|
child._base = null;
|
|
125
134
|
child._ring = null;
|
|
135
|
+
child._canonicalContext = null;
|
|
126
136
|
return child;
|
|
127
137
|
}
|
|
128
138
|
}
|
|
@@ -146,6 +146,28 @@ class ApiRouter {
|
|
|
146
146
|
);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
_emitCanonicalLog(ctx, { method, path, route, status, startTime, middlewareNames, error, session }) {
|
|
150
|
+
const durationMs = Date.now() - startTime;
|
|
151
|
+
const dbStats = ctx._dbStats ?? { queries: 0, durationMs: 0 };
|
|
152
|
+
const extra = ctx.log.getCanonicalContext?.() ?? {};
|
|
153
|
+
const line = {
|
|
154
|
+
method,
|
|
155
|
+
path,
|
|
156
|
+
route,
|
|
157
|
+
status,
|
|
158
|
+
durationMs,
|
|
159
|
+
dbQueries: dbStats.queries,
|
|
160
|
+
dbDurationMs: Math.round(dbStats.durationMs),
|
|
161
|
+
middleware: middlewareNames,
|
|
162
|
+
...extra,
|
|
163
|
+
};
|
|
164
|
+
if (session?.userId ?? session?.id) {
|
|
165
|
+
line.userId = session.userId ?? session.id;
|
|
166
|
+
}
|
|
167
|
+
if (error) line.error = error;
|
|
168
|
+
ctx.log.info('request', line);
|
|
169
|
+
}
|
|
170
|
+
|
|
149
171
|
async init() {
|
|
150
172
|
if (this._config.enabled === false) {
|
|
151
173
|
this._log?.info('API: disabled');
|
|
@@ -214,14 +236,9 @@ class ApiRouter {
|
|
|
214
236
|
} catch (err) {
|
|
215
237
|
const errorMsg = toErrorMessage(err);
|
|
216
238
|
ctx.log.error(`Handler error in ${route.method} ${route.pattern}`, { error: errorMsg });
|
|
217
|
-
|
|
218
|
-
method,
|
|
219
|
-
|
|
220
|
-
route: route.pattern,
|
|
221
|
-
status: 500,
|
|
222
|
-
durationMs: Date.now() - startTime,
|
|
223
|
-
middleware: middlewareNames,
|
|
224
|
-
error: errorMsg,
|
|
239
|
+
this._emitCanonicalLog(ctx, {
|
|
240
|
+
method, path: pathname, route: route.pattern, status: 500,
|
|
241
|
+
startTime, middlewareNames, error: errorMsg, session: reqInfo.session,
|
|
225
242
|
});
|
|
226
243
|
sendJson(res, 500, {
|
|
227
244
|
error: { code: ErrorCodes.HANDLER_ERROR, message: 'An internal error occurred' },
|
|
@@ -248,13 +265,9 @@ class ApiRouter {
|
|
|
248
265
|
// ── Serialize + send ──
|
|
249
266
|
const statusCode = response.status ?? (response.error ? 400 : 200);
|
|
250
267
|
await serializeResponse(res, response, responseHeaders, statusCode);
|
|
251
|
-
|
|
252
|
-
method,
|
|
253
|
-
|
|
254
|
-
route: route.pattern,
|
|
255
|
-
status: statusCode,
|
|
256
|
-
durationMs: Date.now() - startTime,
|
|
257
|
-
middleware: middlewareNames,
|
|
268
|
+
this._emitCanonicalLog(ctx, {
|
|
269
|
+
method, path: pathname, route: route.pattern, status: statusCode,
|
|
270
|
+
startTime, middlewareNames, session: response.session !== void 0 ? response.session : reqInfo.session,
|
|
258
271
|
});
|
|
259
272
|
|
|
260
273
|
return true;
|