hearald 0.0.3 → 0.0.5
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/.prettierignore +9 -0
- package/CHANGELOG.md +48 -0
- package/dist/analytics.html +4 -4
- package/dist/index.css +2 -2
- package/dist/index.js +2 -2
- package/package.json +5 -1
- package/src/index.js +27 -5
- package/src/public/css/analytics.css +2 -2
- package/src/public/js/App.js +2 -2
- package/src/server/analytics.js +6 -10
- package/src/server/configuration.js +33 -0
- package/src/server/logger.js +7 -9
- package/src/server/server.js +59 -9
- package/src/server/serverEvents.js +10 -7
- package/src/shared/utils.js +3 -5
- package/src/server/serverTrackError.js +0 -47
package/.prettierignore
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.0.5] - 2025-10-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Export `errorMiddlware`, `trackServerEvent`, `trackServerError` as part of default `hearald` in `src/index.js'
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Bugfix to restore intra-day timestamp granularity of events.
|
|
19
|
+
- Percent formatted numbers now use proper formatter
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- All server-side configuration parameters are defined in `src/server/configuration.js`
|
|
24
|
+
|
|
25
|
+
## [0.0.4] - 2025-09-21
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Use package.json `exports`
|
|
30
|
+
|
|
31
|
+
## [0.0.3] - 2025-09-19
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Initial release of Hearald
|
|
36
|
+
|
|
37
|
+
[0.0.5]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.4...v0.0.5
|
|
38
|
+
[0.0.4]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.3...v0.0.4
|
|
39
|
+
[0.0.3]: https://github.com/olivierlacan/keep-a-changelog/releases/tag/v0.0.3
|
|
40
|
+
|
|
41
|
+
<!--
|
|
42
|
+
`Added` for new features.
|
|
43
|
+
`Changed` for changes in existing functionality.
|
|
44
|
+
`Deprecated` for soon-to-be removed features.
|
|
45
|
+
`Removed` for now removed features.
|
|
46
|
+
`Fixed` for any bug fixes.
|
|
47
|
+
`Security` in case of vulnerabilities.
|
|
48
|
+
-->
|
package/dist/analytics.html
CHANGED
|
@@ -134,7 +134,7 @@ th {
|
|
|
134
134
|
@media screen and (max-width: 640px) {
|
|
135
135
|
body {
|
|
136
136
|
font-size: 12px;
|
|
137
|
-
font-feature-settings:
|
|
137
|
+
font-feature-settings: "lnum";
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -264,7 +264,7 @@ th {
|
|
|
264
264
|
word-break: keep-all;
|
|
265
265
|
}
|
|
266
266
|
.bar-chart__bar__label:after {
|
|
267
|
-
content:
|
|
267
|
+
content: "";
|
|
268
268
|
width: 12px;
|
|
269
269
|
height: 12px;
|
|
270
270
|
background: var(--color-label);
|
|
@@ -915,8 +915,8 @@ th {
|
|
|
915
915
|
type="${f.type}"
|
|
916
916
|
value=${deepGet(stats.metrics, f.key)}
|
|
917
917
|
span=${f.span || null}
|
|
918
|
-
|
|
919
|
-
|
|
918
|
+
percent=${f.type === "percent"}
|
|
919
|
+
histogram=${f.type === "histogram"}
|
|
920
920
|
/>
|
|
921
921
|
`;
|
|
922
922
|
})}
|
package/dist/index.css
CHANGED
|
@@ -122,7 +122,7 @@ th {
|
|
|
122
122
|
@media screen and (max-width: 640px) {
|
|
123
123
|
body {
|
|
124
124
|
font-size: 12px;
|
|
125
|
-
font-feature-settings:
|
|
125
|
+
font-feature-settings: "lnum";
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -252,7 +252,7 @@ th {
|
|
|
252
252
|
word-break: keep-all;
|
|
253
253
|
}
|
|
254
254
|
.bar-chart__bar__label:after {
|
|
255
|
-
content:
|
|
255
|
+
content: "";
|
|
256
256
|
width: 12px;
|
|
257
257
|
height: 12px;
|
|
258
258
|
background: var(--color-label);
|
package/dist/index.js
CHANGED
|
@@ -517,8 +517,8 @@
|
|
|
517
517
|
type="${f.type}"
|
|
518
518
|
value=${deepGet(stats.metrics, f.key)}
|
|
519
519
|
span=${f.span || null}
|
|
520
|
-
|
|
521
|
-
|
|
520
|
+
percent=${f.type === "percent"}
|
|
521
|
+
histogram=${f.type === "histogram"}
|
|
522
522
|
/>
|
|
523
523
|
`;
|
|
524
524
|
})}
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hearald",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A single server drop-in file system based metrics and analytics library.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js",
|
|
8
|
+
"./client": "./src/client.js"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"scripts": {
|
|
8
12
|
"build": "rollup --config"
|
package/src/index.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import hearaldConfiguration from "./server/configuration.js";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
eventEndpointMiddleware,
|
|
5
|
+
trackServerEvent,
|
|
6
|
+
trackServerError,
|
|
7
|
+
errorTrackingMiddleware,
|
|
8
|
+
shutDown,
|
|
9
|
+
} from "./server/server.js";
|
|
3
10
|
import { createAnalyticsRouter } from "./server/analytics.js";
|
|
4
11
|
|
|
5
12
|
export default function hearald({
|
|
13
|
+
/* singleton configuration options */
|
|
6
14
|
logLevel,
|
|
15
|
+
nowFn /* () => nowAsDateString */,
|
|
16
|
+
getUserId /* (req) => userId */,
|
|
17
|
+
|
|
7
18
|
eventEndpoint: { url = "/e", parseBody } = {},
|
|
8
19
|
analytics: { enabled: analyticsEnabled = true, statDefinitions } = {},
|
|
9
20
|
} = {}) {
|
|
10
21
|
if (logLevel) {
|
|
11
|
-
|
|
22
|
+
hearaldConfiguration.setLogLevel(logLevel);
|
|
23
|
+
}
|
|
24
|
+
if (nowFn) {
|
|
25
|
+
hearaldConfiguration.setNowFn(nowFn);
|
|
26
|
+
}
|
|
27
|
+
if (getUserId) {
|
|
28
|
+
hearaldConfiguration.setGetUserId(getUserId);
|
|
12
29
|
}
|
|
13
30
|
|
|
14
31
|
const eventMiddleware = eventEndpointMiddleware({ url, parseBody });
|
|
@@ -17,9 +34,14 @@ export default function hearald({
|
|
|
17
34
|
? createAnalyticsRouter({ statDefinitions })
|
|
18
35
|
: null;
|
|
19
36
|
|
|
37
|
+
const errorMiddlware = errorTrackingMiddleware({});
|
|
38
|
+
|
|
20
39
|
return {
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
errorMiddlware /* app.use(errorMiddlware) */,
|
|
41
|
+
eventMiddleware /* app.use(eventMiddleware) */,
|
|
42
|
+
analyticsMiddleware /* app.use('/analytics', analyticsMiddleware) */,
|
|
43
|
+
trackServerEvent /* ({ event, time = null, userId, params = {} }) => Promise } */,
|
|
44
|
+
trackServerError /* ({ error, userId = null, extraParams = {} }) => Promise } */,
|
|
23
45
|
shutDown,
|
|
24
46
|
};
|
|
25
47
|
}
|
|
@@ -122,7 +122,7 @@ th {
|
|
|
122
122
|
@media screen and (max-width: 640px) {
|
|
123
123
|
body {
|
|
124
124
|
font-size: 12px;
|
|
125
|
-
font-feature-settings:
|
|
125
|
+
font-feature-settings: "lnum";
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -252,7 +252,7 @@ th {
|
|
|
252
252
|
word-break: keep-all;
|
|
253
253
|
}
|
|
254
254
|
.bar-chart__bar__label:after {
|
|
255
|
-
content:
|
|
255
|
+
content: "";
|
|
256
256
|
width: 12px;
|
|
257
257
|
height: 12px;
|
|
258
258
|
background: var(--color-label);
|
package/src/public/js/App.js
CHANGED
|
@@ -303,8 +303,8 @@ class App extends Component {
|
|
|
303
303
|
type="${f.type}"
|
|
304
304
|
value=${deepGet(stats.metrics, f.key)}
|
|
305
305
|
span=${f.span || null}
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
percent=${f.type === "percent"}
|
|
307
|
+
histogram=${f.type === "histogram"}
|
|
308
308
|
/>
|
|
309
309
|
`;
|
|
310
310
|
})}
|
package/src/server/analytics.js
CHANGED
|
@@ -5,12 +5,8 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
import { readFile, writeFile } from "fs/promises";
|
|
6
6
|
import express from "express";
|
|
7
7
|
import log from "./logger.js";
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
iso,
|
|
11
|
-
defaultEndDate,
|
|
12
|
-
nowAsPstDate,
|
|
13
|
-
} from "../shared/utils.js";
|
|
8
|
+
import hearaldConfiguration from "./configuration.js";
|
|
9
|
+
import { omitProperties, defaultEndDate } from "../shared/utils.js";
|
|
14
10
|
import { loadEventsOverWindow } from "./serverEvents.js";
|
|
15
11
|
|
|
16
12
|
export function generateRawMetrics(events, rawMetricDefinitions) {
|
|
@@ -106,7 +102,8 @@ export function generateComputedMetric(metrics, definition, timeWindow) {
|
|
|
106
102
|
}
|
|
107
103
|
|
|
108
104
|
export function cacheFilePath(endDate, timeWindow) {
|
|
109
|
-
const endDateIso =
|
|
105
|
+
const endDateIso =
|
|
106
|
+
typeof endDate === "string" ? endDate : hearaldConfiguration.isoFn(endDate);
|
|
110
107
|
return `events/summary:${endDateIso}:${timeWindow}.json`;
|
|
111
108
|
}
|
|
112
109
|
|
|
@@ -213,7 +210,7 @@ export async function generateStats(statDefinitions, endDate, timeWindow) {
|
|
|
213
210
|
});
|
|
214
211
|
return {
|
|
215
212
|
metrics: nestedMetrics,
|
|
216
|
-
endDate:
|
|
213
|
+
endDate: hearaldConfiguration.isoFn(endDate),
|
|
217
214
|
timeWindow,
|
|
218
215
|
processedEventCount: allEvents.length,
|
|
219
216
|
};
|
|
@@ -224,7 +221,7 @@ export async function handleAnalyticsDataRequest({
|
|
|
224
221
|
response,
|
|
225
222
|
statDefinitions,
|
|
226
223
|
}) {
|
|
227
|
-
const defaultEndDateIso =
|
|
224
|
+
const defaultEndDateIso = hearaldConfiguration.isoFn(defaultEndDate());
|
|
228
225
|
const endDateIso = request.params.date
|
|
229
226
|
? request.params.date
|
|
230
227
|
: defaultEndDateIso;
|
|
@@ -282,7 +279,6 @@ export function createAnalyticsRouter({ statDefinitions }) {
|
|
|
282
279
|
response,
|
|
283
280
|
statDefinitions,
|
|
284
281
|
loadEventsOverWindow,
|
|
285
|
-
nowAsPstDate,
|
|
286
282
|
});
|
|
287
283
|
});
|
|
288
284
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { iso, nowAsPstDate } from "../shared/utils.js";
|
|
2
|
+
|
|
3
|
+
class Configuration {
|
|
4
|
+
constructor({
|
|
5
|
+
nowFn = nowAsPstDate,
|
|
6
|
+
isoFn = iso,
|
|
7
|
+
logLevel = "info",
|
|
8
|
+
getUserId = (req) => req.user?.id,
|
|
9
|
+
} = {}) {
|
|
10
|
+
this.nowFn = nowFn;
|
|
11
|
+
this.isoFn = isoFn;
|
|
12
|
+
this.logLevel = logLevel;
|
|
13
|
+
this.getUserId = getUserId;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setLogLevel(level) {
|
|
17
|
+
this.logLevel = level;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setNowFn(nowFn) {
|
|
21
|
+
this.nowFn = nowFn;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setGetUserId(getUserId) {
|
|
25
|
+
this.getUserId = getUserId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setIsoFn(isoFn) {
|
|
29
|
+
this.isoFn = isoFn;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default new Configuration();
|
package/src/server/logger.js
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
constructor(level) {
|
|
3
|
-
this.level = level || "info";
|
|
4
|
-
}
|
|
1
|
+
import hearaldConfiguration from "./configuration.js";
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
class Logger {
|
|
4
|
+
constructor(config = hearaldConfiguration) {
|
|
5
|
+
this.config = config;
|
|
8
6
|
}
|
|
9
7
|
|
|
10
8
|
debug(...args) {
|
|
11
|
-
if (this.
|
|
9
|
+
if (this.config.logLevel === "debug") {
|
|
12
10
|
console.log("[DEBUG]", ...args);
|
|
13
11
|
}
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
info(...args) {
|
|
17
|
-
if (this.
|
|
15
|
+
if (this.config.logLevel === "debug" || this.config.logLevel === "info") {
|
|
18
16
|
console.log("[INFO]", ...args);
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
warn(...args) {
|
|
23
|
-
if (this.
|
|
21
|
+
if (this.config.logLevel !== "error") {
|
|
24
22
|
console.warn("[WARN]", ...args);
|
|
25
23
|
}
|
|
26
24
|
}
|
package/src/server/server.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
// Express middleware for hearald event tracking
|
|
2
2
|
import express from "express";
|
|
3
|
-
import log from "./logger.js";
|
|
4
|
-
import { nowAsPstDate } from "../shared/utils.js";
|
|
5
|
-
import { writeEvent, closeEventFiles } from "./serverEvents.js";
|
|
6
|
-
import { createAnalyticsRouter } from "./analytics.js";
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
4
|
+
import hearaldConfiguration from "./configuration.js";
|
|
5
|
+
import log from "./logger.js";
|
|
6
|
+
import { writeEvent, closeEventFiles, VISIT_EVENT } from "./serverEvents.js";
|
|
11
7
|
|
|
12
8
|
export async function shutDown() {
|
|
13
9
|
await closeEventFiles();
|
|
@@ -29,11 +25,11 @@ export function eventEndpointMiddleware({ url = "/e", parseBody } = {}) {
|
|
|
29
25
|
({ e, u, p } = parseBody(req));
|
|
30
26
|
} else {
|
|
31
27
|
({ e = null, u = null, p = {} } = req.body || {});
|
|
32
|
-
if (e ===
|
|
28
|
+
if (e === VISIT_EVENT) {
|
|
33
29
|
p.ip = req.ip;
|
|
34
30
|
}
|
|
35
31
|
}
|
|
36
|
-
await
|
|
32
|
+
await writeEvent(hearaldConfiguration.nowFn(), { e, u, p });
|
|
37
33
|
} catch (err) {
|
|
38
34
|
// eslint-disable-next-line no-console
|
|
39
35
|
log.error("error writing event to event log", err);
|
|
@@ -42,3 +38,57 @@ export function eventEndpointMiddleware({ url = "/e", parseBody } = {}) {
|
|
|
42
38
|
});
|
|
43
39
|
return router;
|
|
44
40
|
}
|
|
41
|
+
|
|
42
|
+
export async function trackServerEvent({
|
|
43
|
+
event,
|
|
44
|
+
time = null,
|
|
45
|
+
userId,
|
|
46
|
+
params = {},
|
|
47
|
+
}) {
|
|
48
|
+
await writeEvent(time || hearaldConfiguration.nowFn(), {
|
|
49
|
+
e: event,
|
|
50
|
+
u: userId,
|
|
51
|
+
p: params,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function trackServerError({
|
|
56
|
+
error,
|
|
57
|
+
userId = null,
|
|
58
|
+
extraParams = {},
|
|
59
|
+
}) {
|
|
60
|
+
let message = "";
|
|
61
|
+
let stack = "";
|
|
62
|
+
if (error instanceof Error) {
|
|
63
|
+
message = error.message;
|
|
64
|
+
stack = error.stack || "";
|
|
65
|
+
} else if (typeof error === "string") {
|
|
66
|
+
message = error;
|
|
67
|
+
} else if (error && typeof error === "object") {
|
|
68
|
+
message = error.message || JSON.stringify(error);
|
|
69
|
+
stack = error.stack || "";
|
|
70
|
+
}
|
|
71
|
+
const params = {
|
|
72
|
+
message,
|
|
73
|
+
stack,
|
|
74
|
+
...extraParams,
|
|
75
|
+
};
|
|
76
|
+
await writeEvent(hearaldConfiguration.nowFn(), {
|
|
77
|
+
e: ERROR_EVENT,
|
|
78
|
+
u: userId,
|
|
79
|
+
p: params,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Express error-handling middleware
|
|
84
|
+
export function errorTrackingMiddleware({}) {
|
|
85
|
+
// eslint-disable-next-line no-unused-vars
|
|
86
|
+
return function (err, req, res, next) {
|
|
87
|
+
trackServerError({
|
|
88
|
+
error: err,
|
|
89
|
+
userId: hearaldConfiguration.getUserId(req),
|
|
90
|
+
extraParams: { url: req.originalUrl, method: req.method, ip: req.ip },
|
|
91
|
+
});
|
|
92
|
+
next(err);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { readFile, open as openFile, mkdir } from "fs/promises";
|
|
2
2
|
|
|
3
|
+
import hearaldConfiguration from "./configuration.js";
|
|
3
4
|
import log from "./logger.js";
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
export const VISIT_EVENT = "visit";
|
|
7
|
+
export const ERROR_EVENT = "error";
|
|
5
8
|
|
|
6
9
|
const eventFilePromises = new Map();
|
|
7
10
|
const lastWriteTimeouts = new Map();
|
|
@@ -11,14 +14,14 @@ export const openEventFile = async (fileName) => {
|
|
|
11
14
|
return openFile(`events/${fileName}.log`, "a");
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
export const writeEvent = async (time, event
|
|
17
|
+
export const writeEvent = async (time, event) => {
|
|
15
18
|
let fileName;
|
|
16
19
|
|
|
17
|
-
const dateString = isoFn(time);
|
|
20
|
+
const dateString = hearaldConfiguration.isoFn(time);
|
|
18
21
|
|
|
19
|
-
if (event.e ===
|
|
22
|
+
if (event.e === VISIT_EVENT) {
|
|
20
23
|
fileName = `${dateString}-visits`;
|
|
21
|
-
} else if (event.e ===
|
|
24
|
+
} else if (event.e === ERROR_EVENT) {
|
|
22
25
|
fileName = `${dateString}-errors`;
|
|
23
26
|
} else {
|
|
24
27
|
fileName = dateString;
|
|
@@ -91,12 +94,12 @@ export const readEventsFromFile = async (fileName) => {
|
|
|
91
94
|
}
|
|
92
95
|
};
|
|
93
96
|
|
|
94
|
-
export const loadEventsOverWindow = async (d, n, fileSuffix
|
|
97
|
+
export const loadEventsOverWindow = async (d, n, fileSuffix) => {
|
|
95
98
|
let events = [];
|
|
96
99
|
for (let i = 0; i < n; i++) {
|
|
97
100
|
const newDate = new Date(d);
|
|
98
101
|
newDate.setDate(d.getDate() - i);
|
|
99
|
-
let fileName = isoFn(newDate);
|
|
102
|
+
let fileName = hearaldConfiguration.isoFn(newDate);
|
|
100
103
|
if (fileSuffix) {
|
|
101
104
|
fileName += `-${fileSuffix}`;
|
|
102
105
|
}
|
package/src/shared/utils.js
CHANGED
|
@@ -10,8 +10,8 @@ export const pacificTimeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
|
10
10
|
export const pacificTimeOffsetHours = (date) =>
|
|
11
11
|
pacificTimeFormatter.format(date || new Date()).endsWith("PDT") ? -7 : -8;
|
|
12
12
|
|
|
13
|
-
export const
|
|
14
|
-
const rawDate =
|
|
13
|
+
export const nowAsPstDate = () => {
|
|
14
|
+
const rawDate = new Date();
|
|
15
15
|
const offset = pacificTimeOffsetHours(rawDate);
|
|
16
16
|
const pstDate = new Date();
|
|
17
17
|
pstDate.setUTCFullYear(
|
|
@@ -19,12 +19,10 @@ export const dateStringToPstDate = (dateString) => {
|
|
|
19
19
|
rawDate.getUTCMonth(),
|
|
20
20
|
rawDate.getUTCDate()
|
|
21
21
|
);
|
|
22
|
-
pstDate.setUTCHours(-1 * offset
|
|
22
|
+
pstDate.setUTCHours(-1 * offset);
|
|
23
23
|
return pstDate;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export const nowAsPstDate = () => dateStringToPstDate();
|
|
27
|
-
|
|
28
26
|
export function omitProperties(object, ...keys) {
|
|
29
27
|
let rest = object;
|
|
30
28
|
keys.forEach((key) => {
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// hearald/src/serverTrackError.js
|
|
2
|
-
// Server-side utility to track errors
|
|
3
|
-
|
|
4
|
-
export async function trackServerError({
|
|
5
|
-
error,
|
|
6
|
-
logEvent,
|
|
7
|
-
userId = null,
|
|
8
|
-
extraParams = {},
|
|
9
|
-
nowFn = () => new Date(),
|
|
10
|
-
}) {
|
|
11
|
-
let message = "";
|
|
12
|
-
let stack = "";
|
|
13
|
-
if (error instanceof Error) {
|
|
14
|
-
message = error.message;
|
|
15
|
-
stack = error.stack || "";
|
|
16
|
-
} else if (typeof error === "string") {
|
|
17
|
-
message = error;
|
|
18
|
-
} else if (error && typeof error === "object") {
|
|
19
|
-
message = error.message || JSON.stringify(error);
|
|
20
|
-
stack = error.stack || "";
|
|
21
|
-
}
|
|
22
|
-
const params = {
|
|
23
|
-
message,
|
|
24
|
-
stack,
|
|
25
|
-
...extraParams,
|
|
26
|
-
};
|
|
27
|
-
await logEvent(nowFn(), { e: "error", u: userId, p: params });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Express error-handling middleware
|
|
31
|
-
export function errorTrackingMiddleware({
|
|
32
|
-
logEvent,
|
|
33
|
-
nowFn = () => new Date(),
|
|
34
|
-
userIdExtractor = (req) => req.user?.id,
|
|
35
|
-
}) {
|
|
36
|
-
// eslint-disable-next-line no-unused-vars
|
|
37
|
-
return function (err, req, res, next) {
|
|
38
|
-
trackServerError({
|
|
39
|
-
error: err,
|
|
40
|
-
logEvent,
|
|
41
|
-
userId: userIdExtractor(req),
|
|
42
|
-
extraParams: { url: req.originalUrl, method: req.method, ip: req.ip },
|
|
43
|
-
nowFn,
|
|
44
|
-
});
|
|
45
|
-
next(err);
|
|
46
|
-
};
|
|
47
|
-
}
|