observe-node 1.0.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/README.md +0 -0
- package/package.json +20 -0
- package/src/config.js +34 -0
- package/src/http/express.js +64 -0
- package/src/http/expressError.js +30 -0
- package/src/http/metricsExpress.js +15 -0
- package/src/index.js +8 -0
- package/src/logger/console.js +75 -0
- package/src/logger/crash.js +27 -0
- package/src/logger/patchConsole.js +43 -0
- package/src/logger/writer.js +72 -0
- package/src/metrics/handler.js +16 -0
- package/src/metrics/http.js +41 -0
- package/src/metrics/registry.js +12 -0
- package/src/schema/event.js +52 -0
- package/src/start.js +54 -0
- package/src/utils/context.js +23 -0
- package/src/utils/env.js +20 -0
- package/src/utils/ids.js +8 -0
- package/src/utils/time.js +11 -0
package/README.md
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "observe-node",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Structured logs + Prometheus metrics SDK for Node.js (Loki/Grafana ready)",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"files": ["src"],
|
|
8
|
+
"keywords": ["observability","logging","metrics","prometheus","loki","grafana"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "nodemon example.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"axios": "^1.13.5",
|
|
15
|
+
"prom-client": "^15.1.3"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"nodemon": "^3.1.14"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/config.js
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const { createEventEmitter } = require("./schema/event");
|
|
5
|
+
|
|
6
|
+
function defaultLogDir() {
|
|
7
|
+
// windows
|
|
8
|
+
if (process.platform === "win32") {
|
|
9
|
+
return path.join(os.homedir(), ".observe");
|
|
10
|
+
}
|
|
11
|
+
// linux/mac
|
|
12
|
+
return "/var/log/observe";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function loadConfig(userConfig) {
|
|
16
|
+
const config = {
|
|
17
|
+
app: userConfig.app || "unknown-app",
|
|
18
|
+
env: userConfig.env || process.env.NODE_ENV || "dev",
|
|
19
|
+
|
|
20
|
+
// ✅ output modes: "stdout" | "file" | "both"
|
|
21
|
+
output: userConfig.output || "file",
|
|
22
|
+
|
|
23
|
+
logDir: userConfig.logDir || process.env.OBSERVE_LOG_DIR || defaultLogDir(),
|
|
24
|
+
logFileName: userConfig.logFileName || `${userConfig.app || "unknown-app"}.log`,
|
|
25
|
+
|
|
26
|
+
metrics: userConfig.metrics !== false,
|
|
27
|
+
console: userConfig.console !== false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
config.emitEvent = createEventEmitter(config);
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { loadConfig };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/http/express.js
|
|
2
|
+
const { runWithContext } = require("../utils/context");
|
|
3
|
+
const { newId } = require("../utils/ids");
|
|
4
|
+
|
|
5
|
+
function getRoutePath(req) {
|
|
6
|
+
// Prefer express route pattern if available (best for metrics + grouping)
|
|
7
|
+
if (req.route && req.route.path) {
|
|
8
|
+
const base = req.baseUrl || "";
|
|
9
|
+
return `${base}${req.route.path}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Fallback: originalUrl without querystring
|
|
13
|
+
const raw = req.originalUrl || req.url || "";
|
|
14
|
+
return raw.split("?")[0] || raw;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function expressMiddleware(observe) {
|
|
18
|
+
const config = observe.__config;
|
|
19
|
+
|
|
20
|
+
return function observeExpress(req, res, next) {
|
|
21
|
+
const requestId =
|
|
22
|
+
req.headers["x-request-id"] ||
|
|
23
|
+
req.headers["x-correlation-id"] ||
|
|
24
|
+
newId();
|
|
25
|
+
|
|
26
|
+
// expose id to downstream services
|
|
27
|
+
res.setHeader("x-request-id", String(requestId));
|
|
28
|
+
|
|
29
|
+
const start = process.hrtime.bigint();
|
|
30
|
+
|
|
31
|
+
runWithContext({ request_id: String(requestId) }, () => {
|
|
32
|
+
res.on("finish", () => {
|
|
33
|
+
const end = process.hrtime.bigint();
|
|
34
|
+
const durationMs = Number(end - start) / 1_000_000;
|
|
35
|
+
|
|
36
|
+
const status = res.statusCode;
|
|
37
|
+
const level = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
|
|
38
|
+
|
|
39
|
+
const path = getRoutePath(req);
|
|
40
|
+
|
|
41
|
+
config.emitEvent({
|
|
42
|
+
event_name: "http.request",
|
|
43
|
+
level,
|
|
44
|
+
message: "HTTP request completed",
|
|
45
|
+
http: {
|
|
46
|
+
method: req.method,
|
|
47
|
+
path,
|
|
48
|
+
status,
|
|
49
|
+
duration_ms: Math.round(durationMs),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// metrics hook (if enabled)
|
|
54
|
+
if (typeof config._observeHttpMetric === "function") {
|
|
55
|
+
config._observeHttpMetric(req.method, path, status, durationMs);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
next();
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { expressMiddleware };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/http/expressError.js
|
|
2
|
+
function createExpressErrorMiddleware(observe, opts = {}) {
|
|
3
|
+
const {
|
|
4
|
+
includeStack = true,
|
|
5
|
+
} = opts;
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line no-unused-vars
|
|
8
|
+
return function observeExpressError(err, req, res, next) {
|
|
9
|
+
const status = err.status || err.statusCode || 500;
|
|
10
|
+
|
|
11
|
+
// your apps may set these:
|
|
12
|
+
const code = err.code; // e.g. "AUTH-401" / "WH-INV-001"
|
|
13
|
+
|
|
14
|
+
observe.emit({
|
|
15
|
+
event_name: "error",
|
|
16
|
+
level: "error",
|
|
17
|
+
message: err.message || "Unhandled error",
|
|
18
|
+
error: {
|
|
19
|
+
name: err.name || "Error",
|
|
20
|
+
code,
|
|
21
|
+
status,
|
|
22
|
+
stack: includeStack ? err.stack : undefined,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
next(err);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { createExpressErrorMiddleware };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/http/metricsExpress.js
|
|
2
|
+
const { metricsHandler } = require("../metrics/handler");
|
|
3
|
+
|
|
4
|
+
function metricsExpress(observe) {
|
|
5
|
+
return async function (req, res) {
|
|
6
|
+
try {
|
|
7
|
+
await metricsHandler(observe, req, res);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
res.statusCode = 500;
|
|
10
|
+
res.end("metrics error");
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { metricsExpress };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/logger/console.js
|
|
2
|
+
const util = require("util");
|
|
3
|
+
|
|
4
|
+
function safeStringify(args) {
|
|
5
|
+
try {
|
|
6
|
+
// util.format handles strings + objects like console does
|
|
7
|
+
return util.format(...args);
|
|
8
|
+
} catch {
|
|
9
|
+
try {
|
|
10
|
+
return args.map(a => (typeof a === "string" ? a : JSON.stringify(a))).join(" ");
|
|
11
|
+
} catch {
|
|
12
|
+
return "[unstringifiable]";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function initConsolePatch(config) {
|
|
18
|
+
if (config.console === false) return;
|
|
19
|
+
|
|
20
|
+
const original = {
|
|
21
|
+
log: console.log,
|
|
22
|
+
info: console.info,
|
|
23
|
+
warn: console.warn,
|
|
24
|
+
error: console.error,
|
|
25
|
+
debug: console.debug,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// prevent recursion when our writer uses console.log(JSON)
|
|
29
|
+
let inPatch = false;
|
|
30
|
+
|
|
31
|
+
function wrap(level, origFn) {
|
|
32
|
+
return function (...args) {
|
|
33
|
+
// always keep original console behavior
|
|
34
|
+
origFn.apply(console, args);
|
|
35
|
+
|
|
36
|
+
// avoid recursion
|
|
37
|
+
if (inPatch) return;
|
|
38
|
+
inPatch = true;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const msg = safeStringify(args);
|
|
42
|
+
|
|
43
|
+
config.emitEvent({
|
|
44
|
+
event_name: "log.console",
|
|
45
|
+
level: level === "error" ? "error" : level === "warn" ? "warn" : "info",
|
|
46
|
+
message: msg,
|
|
47
|
+
console: {
|
|
48
|
+
level,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
// never crash app
|
|
53
|
+
} finally {
|
|
54
|
+
inPatch = false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log = wrap("log", original.log);
|
|
60
|
+
console.info = wrap("info", original.info);
|
|
61
|
+
console.warn = wrap("warn", original.warn);
|
|
62
|
+
console.error = wrap("error", original.error);
|
|
63
|
+
console.debug = wrap("debug", original.debug);
|
|
64
|
+
|
|
65
|
+
// allow restore
|
|
66
|
+
config._restoreConsole = () => {
|
|
67
|
+
console.log = original.log;
|
|
68
|
+
console.info = original.info;
|
|
69
|
+
console.warn = original.warn;
|
|
70
|
+
console.error = original.error;
|
|
71
|
+
console.debug = original.debug;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { initConsolePatch };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/logger/crash.js
|
|
2
|
+
function initCrashHandlers(config) {
|
|
3
|
+
process.on("uncaughtException", (err) => {
|
|
4
|
+
config.emitEvent({
|
|
5
|
+
event_name: "process.crash",
|
|
6
|
+
level: "fatal",
|
|
7
|
+
error: {
|
|
8
|
+
type: err.name,
|
|
9
|
+
message: err.message,
|
|
10
|
+
stack: err.stack?.replace(/\n/g, " | ")
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
process.on("unhandledRejection", (reason) => {
|
|
17
|
+
config.emitEvent({
|
|
18
|
+
event_name: "process.rejection",
|
|
19
|
+
level: "error",
|
|
20
|
+
error: {
|
|
21
|
+
message: String(reason)
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { initCrashHandlers };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/logger/patchConsole.js
|
|
2
|
+
function safeToString(v) {
|
|
3
|
+
try {
|
|
4
|
+
if (typeof v === "string") return v;
|
|
5
|
+
return JSON.stringify(v);
|
|
6
|
+
} catch {
|
|
7
|
+
return String(v);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function patchConsole(observe, opts = {}) {
|
|
12
|
+
const { enabled = true } = opts;
|
|
13
|
+
if (!enabled) return () => {};
|
|
14
|
+
|
|
15
|
+
const original = {
|
|
16
|
+
log: console.log,
|
|
17
|
+
warn: console.warn,
|
|
18
|
+
error: console.error,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
console.log = (...args) => {
|
|
22
|
+
observe.emit({ event_name: "log", level: "info", message: args.map(safeToString).join(" "), log: { logger: "console", args } });
|
|
23
|
+
original.log(...args);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
console.warn = (...args) => {
|
|
27
|
+
observe.emit({ event_name: "log", level: "warn", message: args.map(safeToString).join(" "), log: { logger: "console", args } });
|
|
28
|
+
original.warn(...args);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
console.error = (...args) => {
|
|
32
|
+
observe.emit({ event_name: "log", level: "error", message: args.map(safeToString).join(" "), log: { logger: "console", args } });
|
|
33
|
+
original.error(...args);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return function restore() {
|
|
37
|
+
console.log = original.log;
|
|
38
|
+
console.warn = original.warn;
|
|
39
|
+
console.error = original.error;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { patchConsole };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/logger/writer.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
|
|
6
|
+
let stream = null;
|
|
7
|
+
|
|
8
|
+
function ensureDir(dir) {
|
|
9
|
+
try {
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function openStream(filePath) {
|
|
18
|
+
return fs.createWriteStream(filePath, { flags: "a" });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildFilePath(config) {
|
|
22
|
+
const primaryDir = config.logDir;
|
|
23
|
+
const primaryPath = path.join(primaryDir, config.logFileName);
|
|
24
|
+
|
|
25
|
+
// try primary
|
|
26
|
+
if (ensureDir(primaryDir)) {
|
|
27
|
+
try {
|
|
28
|
+
const s = openStream(primaryPath);
|
|
29
|
+
return { filePath: primaryPath, stream: s };
|
|
30
|
+
} catch {}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// fallback to home dir
|
|
34
|
+
const fallbackDir = path.join(os.homedir(), ".observe");
|
|
35
|
+
const fallbackPath = path.join(fallbackDir, config.logFileName);
|
|
36
|
+
ensureDir(fallbackDir);
|
|
37
|
+
const s2 = openStream(fallbackPath);
|
|
38
|
+
return { filePath: fallbackPath, stream: s2 };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function initWriter(config) {
|
|
42
|
+
const { filePath, stream: s } = buildFilePath(config);
|
|
43
|
+
stream = s;
|
|
44
|
+
config._logFilePath = filePath;
|
|
45
|
+
|
|
46
|
+
config.write = (event) => {
|
|
47
|
+
const line = JSON.stringify(event) + "\n";
|
|
48
|
+
|
|
49
|
+
// stdout
|
|
50
|
+
if (config.output === "stdout" || config.output === "both") {
|
|
51
|
+
try {
|
|
52
|
+
process.stdout.write(line);
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// file
|
|
57
|
+
if (config.output === "file" || config.output === "both") {
|
|
58
|
+
try {
|
|
59
|
+
stream.write(line);
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
config._closeWriter = () => {
|
|
65
|
+
try {
|
|
66
|
+
stream?.end();
|
|
67
|
+
} catch {}
|
|
68
|
+
stream = null;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { initWriter };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/metrics/handler.js
|
|
2
|
+
async function metricsHandler(observe, req, res) {
|
|
3
|
+
const config = observe.__config;
|
|
4
|
+
if (!config.metricsRegistry) {
|
|
5
|
+
res.statusCode = 404;
|
|
6
|
+
res.end("metrics disabled");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
res.setHeader("Content-Type", config.metricsRegistry.register.contentType);
|
|
11
|
+
const body = await config.metricsRegistry.register.metrics();
|
|
12
|
+
res.statusCode = 200;
|
|
13
|
+
res.end(body);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { metricsHandler };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/metrics/http.js
|
|
2
|
+
function initHttpMetrics(config) {
|
|
3
|
+
if (!config.metricsRegistry) return;
|
|
4
|
+
|
|
5
|
+
const client = config.metricsRegistry;
|
|
6
|
+
|
|
7
|
+
const httpReqCount = new client.Counter({
|
|
8
|
+
name: "observe_http_requests_total",
|
|
9
|
+
help: "Total HTTP requests",
|
|
10
|
+
labelNames: ["app", "env", "method", "path", "status"]
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const httpReqDuration = new client.Histogram({
|
|
14
|
+
name: "observe_http_request_duration_ms",
|
|
15
|
+
help: "HTTP request duration in ms",
|
|
16
|
+
labelNames: ["app", "env", "method", "path", "status"],
|
|
17
|
+
buckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Keep path label sane (avoid high-cardinality)
|
|
21
|
+
function normalizePath(path) {
|
|
22
|
+
// basic cleanup: remove query string
|
|
23
|
+
const p = String(path || "").split("?")[0];
|
|
24
|
+
// optional: collapse numeric ids (simple rule)
|
|
25
|
+
return p.replace(/\/\d+(\b|\/)/g, "/:id$1");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
config._observeHttpMetric = (method, path, status, durationMs) => {
|
|
29
|
+
const labels = {
|
|
30
|
+
app: config.app,
|
|
31
|
+
env: config.env,
|
|
32
|
+
method: String(method || "GET"),
|
|
33
|
+
path: normalizePath(path),
|
|
34
|
+
status: String(status || 0)
|
|
35
|
+
};
|
|
36
|
+
httpReqCount.inc(labels, 1);
|
|
37
|
+
httpReqDuration.observe(labels, durationMs);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { initHttpMetrics };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/schema/event.js
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const { getContext } = require("../utils/context");
|
|
4
|
+
const { newId } = require("../utils/ids");
|
|
5
|
+
|
|
6
|
+
function createEventEmitter(config) {
|
|
7
|
+
return function emitEvent(partial = {}) {
|
|
8
|
+
const ctx = getContext();
|
|
9
|
+
|
|
10
|
+
// pick only safe/known top-level from partial
|
|
11
|
+
const {
|
|
12
|
+
level,
|
|
13
|
+
event_name,
|
|
14
|
+
message,
|
|
15
|
+
request_id,
|
|
16
|
+
trace_id,
|
|
17
|
+
span_id,
|
|
18
|
+
...details
|
|
19
|
+
} = partial;
|
|
20
|
+
|
|
21
|
+
const event = {
|
|
22
|
+
schema_version: "1.0",
|
|
23
|
+
source: "observe-sdk",
|
|
24
|
+
event_id: newId(),
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
|
|
27
|
+
app: config.app,
|
|
28
|
+
env: config.env,
|
|
29
|
+
|
|
30
|
+
// nice to have for prod
|
|
31
|
+
service: config.service || config.app,
|
|
32
|
+
version: config.version,
|
|
33
|
+
host: os.hostname(),
|
|
34
|
+
pid: process.pid,
|
|
35
|
+
|
|
36
|
+
level: level || "info",
|
|
37
|
+
event_name,
|
|
38
|
+
message,
|
|
39
|
+
|
|
40
|
+
request_id: ctx?.request_id || request_id,
|
|
41
|
+
trace_id: ctx?.trace_id || trace_id,
|
|
42
|
+
span_id: ctx?.span_id || span_id,
|
|
43
|
+
|
|
44
|
+
// extra blocks: http, error, db, log, meta...
|
|
45
|
+
...details,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
config.write(event);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { createEventEmitter };
|
package/src/start.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/start.js
|
|
2
|
+
const { loadConfig } = require("./config");
|
|
3
|
+
const { initWriter } = require("./logger/writer");
|
|
4
|
+
const { initCrashHandlers } = require("./logger/crash");
|
|
5
|
+
const { initMetrics } = require("./metrics/registry");
|
|
6
|
+
const { initHttpMetrics } = require("./metrics/http");
|
|
7
|
+
const { expressMiddleware } = require("./http/express");
|
|
8
|
+
const { metricsExpress } = require("./http/metricsExpress");
|
|
9
|
+
const { initConsolePatch } = require("./logger/console");
|
|
10
|
+
|
|
11
|
+
function start(userConfig = {}) {
|
|
12
|
+
const config = loadConfig(userConfig);
|
|
13
|
+
|
|
14
|
+
initWriter(config);
|
|
15
|
+
initConsolePatch(config);
|
|
16
|
+
initCrashHandlers(config);
|
|
17
|
+
initMetrics(config);
|
|
18
|
+
initHttpMetrics(config);
|
|
19
|
+
|
|
20
|
+
// ✅ create object first
|
|
21
|
+
const observe = {
|
|
22
|
+
__config: config,
|
|
23
|
+
emit: config.emitEvent,
|
|
24
|
+
express: null,
|
|
25
|
+
metrics: null,
|
|
26
|
+
shutdown: null,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ✅ then assign functions
|
|
30
|
+
observe.express = () => expressMiddleware(observe);
|
|
31
|
+
observe.metrics = () => metricsExpress(observe);
|
|
32
|
+
observe.shutdown = () => config._closeWriter?.();
|
|
33
|
+
|
|
34
|
+
// events
|
|
35
|
+
config.emitEvent({ event_name: "process.start", level: "info" });
|
|
36
|
+
|
|
37
|
+
config.emitEvent({
|
|
38
|
+
event_name: "observe.writer",
|
|
39
|
+
level: "info",
|
|
40
|
+
message: `observe logging to: ${config._logFilePath}`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// graceful exit
|
|
44
|
+
const onExit = () => {
|
|
45
|
+
try { config._closeWriter?.(); } catch {}
|
|
46
|
+
process.exit(0);
|
|
47
|
+
};
|
|
48
|
+
process.on("SIGINT", onExit);
|
|
49
|
+
process.on("SIGTERM", onExit);
|
|
50
|
+
|
|
51
|
+
return observe;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = start;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/utils/context.js
|
|
2
|
+
const { AsyncLocalStorage } = require("async_hooks");
|
|
3
|
+
|
|
4
|
+
const als = new AsyncLocalStorage();
|
|
5
|
+
|
|
6
|
+
function runWithContext(ctx, fn) {
|
|
7
|
+
return als.run(ctx, fn);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function setContext(patch) {
|
|
11
|
+
const store = als.getStore() || {};
|
|
12
|
+
Object.assign(store, patch);
|
|
13
|
+
// if no store exists, create one
|
|
14
|
+
if (!als.getStore()) {
|
|
15
|
+
als.enterWith(store);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getContext() {
|
|
20
|
+
return als.getStore();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { runWithContext, setContext, getContext };
|
package/src/utils/env.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/utils/env.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
|
|
4
|
+
function isDocker() {
|
|
5
|
+
try {
|
|
6
|
+
if (fs.existsSync("/.dockerenv")) return true;
|
|
7
|
+
const cgroup = fs.readFileSync("/proc/1/cgroup", "utf8");
|
|
8
|
+
return cgroup.includes("docker") || cgroup.includes("kubepods") || cgroup.includes("containerd");
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function runtime() {
|
|
15
|
+
if (process.env.KUBERNETES_SERVICE_HOST) return "k8s";
|
|
16
|
+
if (isDocker()) return "docker";
|
|
17
|
+
return "host";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { isDocker, runtime };
|
package/src/utils/ids.js
ADDED