badmfck-api-server 5.0.3 → 5.0.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.
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LOG_SOURCES = void 0;
7
+ exports.listLogSources = listLogSources;
8
+ exports.tailLog = tailLog;
9
+ const promises_1 = __importDefault(require("fs/promises"));
10
+ const fs_1 = require("fs");
11
+ const LogService_1 = require("../LogService");
12
+ exports.LOG_SOURCES = ["process-out", "process-err", "nginx-access", "nginx-error"];
13
+ const LABELS = {
14
+ "process-out": "Process stdout (pm2 out log)",
15
+ "process-err": "Process stderr (pm2 error log)",
16
+ "nginx-access": "nginx access log",
17
+ "nginx-error": "nginx error log",
18
+ };
19
+ const MAX_WINDOW = 512 * 1024;
20
+ const MAX_LINE = 4000;
21
+ const MAX_LINES = 2000;
22
+ function resolvePaths(opts) {
23
+ const usable = (p) => (p && p !== "/dev/null" ? p : null);
24
+ return {
25
+ "process-out": usable(opts?.processOut) ?? usable(process.env.pm_out_log_path),
26
+ "process-err": usable(opts?.processErr) ?? usable(process.env.pm_err_log_path),
27
+ "nginx-access": usable(opts?.nginxAccess),
28
+ "nginx-error": usable(opts?.nginxError),
29
+ };
30
+ }
31
+ const ANSI = /\x1b\[[0-9;]*[A-Za-z]/g;
32
+ function cleanLine(raw) {
33
+ const line = LogService_1.LogService.sanitize(raw.replace(/\r$/, "").replace(ANSI, ""));
34
+ return line.length > MAX_LINE ? line.slice(0, MAX_LINE) + `…(${line.length} chars)` : line;
35
+ }
36
+ async function listLogSources(opts) {
37
+ const paths = resolvePaths(opts);
38
+ return Promise.all(exports.LOG_SOURCES.map(async (id) => {
39
+ const path = paths[id];
40
+ const base = { id, label: LABELS[id], path, configured: !!path, exists: false, readable: false,
41
+ size: null, modifiedAt: null, reason: null };
42
+ if (!path)
43
+ return { ...base, reason: id.startsWith("process") ? "not running under pm2 and no path configured" : "path not configured (options.logFiles)" };
44
+ try {
45
+ const st = await promises_1.default.stat(path);
46
+ base.exists = true;
47
+ base.size = st.size;
48
+ base.modifiedAt = st.mtimeMs;
49
+ await promises_1.default.access(path, fs_1.constants.R_OK);
50
+ base.readable = true;
51
+ }
52
+ catch (e) {
53
+ base.reason = e?.code === "ENOENT" ? "file not found"
54
+ : e?.code === "EACCES" ? "permission denied for the node process user"
55
+ : String(e?.message ?? e);
56
+ }
57
+ return base;
58
+ }));
59
+ }
60
+ async function tailLog(id, opts, req = {}) {
61
+ const path = resolvePaths(opts)[id];
62
+ if (!path)
63
+ throw new Error(`log source "${id}" is not configured`);
64
+ const wantLines = Math.min(MAX_LINES, Math.max(1, Math.floor(req.lines ?? 200)));
65
+ const fh = await promises_1.default.open(path, "r");
66
+ try {
67
+ const { size } = await fh.stat();
68
+ const since = typeof req.since === "number" && Number.isFinite(req.since) ? Math.floor(req.since) : undefined;
69
+ let start;
70
+ let rotated = false, truncated = false, onBoundary = false;
71
+ if (since !== undefined && since >= 0 && since <= size) {
72
+ start = since;
73
+ onBoundary = true;
74
+ if (size - start > MAX_WINDOW) {
75
+ start = size - MAX_WINDOW;
76
+ truncated = true;
77
+ onBoundary = false;
78
+ }
79
+ }
80
+ else {
81
+ rotated = since !== undefined && since > size;
82
+ start = Math.max(0, size - MAX_WINDOW);
83
+ onBoundary = start === 0;
84
+ }
85
+ const buf = Buffer.alloc(size - start);
86
+ if (buf.length > 0)
87
+ await fh.read(buf, 0, buf.length, start);
88
+ const lastNl = buf.lastIndexOf(0x0a);
89
+ const firstNl = buf.indexOf(0x0a);
90
+ let begin = onBoundary ? 0 : (firstNl >= 0 ? firstNl + 1 : buf.length);
91
+ const end = lastNl >= 0 ? lastNl + 1 : 0;
92
+ if (begin > end)
93
+ begin = end;
94
+ const text = buf.subarray(begin, end).toString("utf-8");
95
+ let lines = text.length ? text.split("\n") : [];
96
+ if (lines.length && lines[lines.length - 1] === "")
97
+ lines.pop();
98
+ lines = lines.map(cleanLine);
99
+ const filter = typeof req.filter === "string" ? req.filter.trim().toLowerCase() : "";
100
+ if (filter)
101
+ lines = lines.filter(l => l.toLowerCase().includes(filter));
102
+ if (lines.length > wantLines)
103
+ lines = lines.slice(-wantLines);
104
+ return { source: id, path, size, cursor: start + end, rotated, truncated, lines };
105
+ }
106
+ finally {
107
+ await fh.close();
108
+ }
109
+ }
@@ -34,6 +34,8 @@ export declare class Monitor extends BaseEndpoint {
34
34
  httpstats(req: HTTPRequestVO): Promise<TransferPacketVO>;
35
35
  inventory(req: HTTPRequestVO): Promise<TransferPacketVO>;
36
36
  metrics(req: HTTPRequestVO): Promise<TransferPacketVO>;
37
+ logfiles(req: HTTPRequestVO): Promise<TransferPacketVO>;
38
+ logtail(req: HTTPRequestVO): Promise<TransferPacketVO>;
37
39
  metricsWeeks(req: HTTPRequestVO): Promise<TransferPacketVO>;
38
40
  metricsWeek(req: HTTPRequestVO): Promise<TransferPacketVO>;
39
41
  connections(req: HTTPRequestVO): Promise<TransferPacketVO>;
@@ -18,6 +18,7 @@ const MonitorService_1 = require("../MonitorService");
18
18
  const DBService_1 = require("../DBService");
19
19
  const SystemInfo_1 = require("./SystemInfo");
20
20
  const ProcessMonitor_1 = require("./ProcessMonitor");
21
+ const LogFiles_1 = require("./LogFiles");
21
22
  exports.S_MONITOR_REGISTRATE_ACTION = new badmfck_signal_1.Signal();
22
23
  exports.S_MONITOR_RESTART_REQUESTED = new badmfck_signal_1.Signal();
23
24
  const logMap = {
@@ -56,6 +57,17 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
56
57
  range: "",
57
58
  $__range_values: ["week", "day", "hour"]
58
59
  } },
60
+ { ignoreInterceptor: true, endpoint: "logfiles", handler: this.logfiles },
61
+ { ignoreInterceptor: true, endpoint: "logtail", handler: this.logtail, validationModel: {
62
+ source: "",
63
+ $__source_values: [...LogFiles_1.LOG_SOURCES],
64
+ lines: 0,
65
+ $__lines_optional: true,
66
+ since: 0,
67
+ $__since_optional: true,
68
+ filter: "",
69
+ $__filter_optional: true
70
+ } },
59
71
  { ignoreInterceptor: true, endpoint: "metricsWeeks", handler: this.metricsWeeks },
60
72
  { ignoreInterceptor: true, endpoint: "metricsWeek", handler: this.metricsWeek, validationModel: {
61
73
  weekKey: ""
@@ -205,6 +217,27 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
205
217
  const result = await MonitorService_1.REQ_STAT.request(req.data);
206
218
  return { data: result };
207
219
  }
220
+ async logfiles(req) {
221
+ await this.checkAuthentication(req);
222
+ return { data: await (0, LogFiles_1.listLogSources)(this.apiServiceOptions.logFiles) };
223
+ }
224
+ async logtail(req) {
225
+ await this.checkAuthentication(req);
226
+ const num = (v) => (v === undefined || v === null || v === "" ? undefined : Number(v));
227
+ try {
228
+ return { data: await (0, LogFiles_1.tailLog)(req.data.source, this.apiServiceOptions.logFiles, {
229
+ lines: num(req.data.lines),
230
+ since: num(req.data.since),
231
+ filter: req.data.filter,
232
+ }) };
233
+ }
234
+ catch (e) {
235
+ const details = e?.code === "ENOENT" ? "log file not found"
236
+ : e?.code === "EACCES" ? "permission denied: the node process user can't read this file (nginx logs are usually root:adm 0640 — add the user to the adm group or point access_log to a readable file)"
237
+ : String(e?.message ?? e);
238
+ throw { ...DefaultErrors_1.default.BAD_REQUEST, details };
239
+ }
240
+ }
208
241
  async metricsWeeks(req) {
209
242
  await this.checkAuthentication(req);
210
243
  return { data: await MonitorService_1.REQ_STAT_WEEKS.request() };
package/dist/index.d.ts CHANGED
@@ -21,3 +21,5 @@ import { Deploy } from "./apiServer/deployment/Deploy";
21
21
  import { Activate } from "./apiServer/deployment/Activate";
22
22
  import { InterceptorPayAuth } from "./apiServer/interceptors/InterceptorPayAuth";
23
23
  export { MicroserviceHost, MicroserviceClient, Http, ZipUtils, UID, YYYYMMDDHH, JSONStableStringify, APIService, Initializer, LocalRequest, ValidationModel, MysqlService, TimeframeService, Validator, LogService, DataProvider, ErrorUtils, ExternalService, DBService, Deploy, Activate, InterceptorPayAuth, S_MONITOR_REGISTRATE_ACTION, S_MONITOR_RESTART_REQUESTED, ProcessMonitor };
24
+ export type { IBlueGreenState, IBlueGreenStateOptions } from "./apiServer/deployment/BlueGreenState";
25
+ export { S_BLUEGREEN_STATE_CHANGED } from "./apiServer/deployment/BlueGreenState";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProcessMonitor = exports.S_MONITOR_RESTART_REQUESTED = exports.S_MONITOR_REGISTRATE_ACTION = exports.InterceptorPayAuth = exports.Activate = exports.Deploy = exports.DBService = exports.ExternalService = exports.ErrorUtils = exports.DataProvider = exports.LogService = exports.Validator = exports.TimeframeService = exports.MysqlService = exports.LocalRequest = exports.Initializer = exports.APIService = exports.JSONStableStringify = exports.YYYYMMDDHH = exports.UID = exports.ZipUtils = exports.Http = exports.MicroserviceClient = exports.MicroserviceHost = void 0;
3
+ exports.S_BLUEGREEN_STATE_CHANGED = exports.ProcessMonitor = exports.S_MONITOR_RESTART_REQUESTED = exports.S_MONITOR_REGISTRATE_ACTION = exports.InterceptorPayAuth = exports.Activate = exports.Deploy = exports.DBService = exports.ExternalService = exports.ErrorUtils = exports.DataProvider = exports.LogService = exports.Validator = exports.TimeframeService = exports.MysqlService = exports.LocalRequest = exports.Initializer = exports.APIService = exports.JSONStableStringify = exports.YYYYMMDDHH = exports.UID = exports.ZipUtils = exports.Http = exports.MicroserviceClient = exports.MicroserviceHost = void 0;
4
4
  const APIService_1 = require("./apiServer/APIService");
5
5
  Object.defineProperty(exports, "APIService", { enumerable: true, get: function () { return APIService_1.APIService; } });
6
6
  Object.defineProperty(exports, "Initializer", { enumerable: true, get: function () { return APIService_1.Initializer; } });
@@ -47,3 +47,5 @@ const Activate_1 = require("./apiServer/deployment/Activate");
47
47
  Object.defineProperty(exports, "Activate", { enumerable: true, get: function () { return Activate_1.Activate; } });
48
48
  const InterceptorPayAuth_1 = require("./apiServer/interceptors/InterceptorPayAuth");
49
49
  Object.defineProperty(exports, "InterceptorPayAuth", { enumerable: true, get: function () { return InterceptorPayAuth_1.InterceptorPayAuth; } });
50
+ var BlueGreenState_1 = require("./apiServer/deployment/BlueGreenState");
51
+ Object.defineProperty(exports, "S_BLUEGREEN_STATE_CHANGED", { enumerable: true, get: function () { return BlueGreenState_1.S_BLUEGREEN_STATE_CHANGED; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badmfck-api-server",
3
- "version": "5.0.3",
3
+ "version": "5.0.6",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",