@futdevpro/nts-dynamo 1.15.17 → 1.15.20

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.
Files changed (53) hide show
  1. package/_specifications/BACKLOG.md +4 -4
  2. package/build/_models/interfaces/global-log-settings.interface.d.ts +35 -0
  3. package/build/_models/interfaces/global-log-settings.interface.d.ts.map +1 -1
  4. package/build/_modules/admin-auth/_models/admin-api-key-config.interface.d.ts +32 -0
  5. package/build/_modules/admin-auth/_models/admin-api-key-config.interface.d.ts.map +1 -0
  6. package/build/_modules/admin-auth/_models/admin-api-key-config.interface.js +3 -0
  7. package/build/_modules/admin-auth/_models/admin-api-key-config.interface.js.map +1 -0
  8. package/build/_modules/admin-auth/admin-api-key.auth-service.d.ts +90 -0
  9. package/build/_modules/admin-auth/admin-api-key.auth-service.d.ts.map +1 -0
  10. package/build/_modules/admin-auth/admin-api-key.auth-service.js +195 -0
  11. package/build/_modules/admin-auth/admin-api-key.auth-service.js.map +1 -0
  12. package/build/_modules/admin-auth/index.d.ts +3 -0
  13. package/build/_modules/admin-auth/index.d.ts.map +1 -0
  14. package/build/_modules/admin-auth/index.js +6 -0
  15. package/build/_modules/admin-auth/index.js.map +1 -0
  16. package/build/_modules/logs/file-log.service.d.ts +87 -0
  17. package/build/_modules/logs/file-log.service.d.ts.map +1 -0
  18. package/build/_modules/logs/file-log.service.js +267 -0
  19. package/build/_modules/logs/file-log.service.js.map +1 -0
  20. package/build/_modules/logs/get-logs-routing-module.util.d.ts +19 -0
  21. package/build/_modules/logs/get-logs-routing-module.util.d.ts.map +1 -0
  22. package/build/_modules/logs/get-logs-routing-module.util.js +32 -0
  23. package/build/_modules/logs/get-logs-routing-module.util.js.map +1 -0
  24. package/build/_modules/logs/index.d.ts +5 -0
  25. package/build/_modules/logs/index.d.ts.map +1 -0
  26. package/build/_modules/logs/index.js +12 -0
  27. package/build/_modules/logs/index.js.map +1 -0
  28. package/build/_modules/logs/log-buffer.service.d.ts +38 -0
  29. package/build/_modules/logs/log-buffer.service.d.ts.map +1 -0
  30. package/build/_modules/logs/log-buffer.service.js +97 -0
  31. package/build/_modules/logs/log-buffer.service.js.map +1 -0
  32. package/build/_modules/logs/logs.controller.d.ts +27 -0
  33. package/build/_modules/logs/logs.controller.d.ts.map +1 -0
  34. package/build/_modules/logs/logs.controller.js +90 -0
  35. package/build/_modules/logs/logs.controller.js.map +1 -0
  36. package/build/_modules/logs/logs.service.d.ts +40 -0
  37. package/build/_modules/logs/logs.service.d.ts.map +1 -0
  38. package/build/_modules/logs/logs.service.js +97 -0
  39. package/build/_modules/logs/logs.service.js.map +1 -0
  40. package/package.json +1 -1
  41. package/pipeline.cicd.config.json +3 -1
  42. package/src/_models/interfaces/global-log-settings.interface.ts +36 -0
  43. package/src/_modules/admin-auth/_models/admin-api-key-config.interface.ts +33 -0
  44. package/src/_modules/admin-auth/admin-api-key.auth-service.spec.ts +200 -0
  45. package/src/_modules/admin-auth/admin-api-key.auth-service.ts +220 -0
  46. package/src/_modules/admin-auth/index.ts +2 -0
  47. package/src/_modules/logs/file-log.service.spec.ts +202 -0
  48. package/src/_modules/logs/file-log.service.ts +283 -0
  49. package/src/_modules/logs/get-logs-routing-module.util.ts +36 -0
  50. package/src/_modules/logs/index.ts +4 -0
  51. package/src/_modules/logs/log-buffer.service.ts +101 -0
  52. package/src/_modules/logs/logs.controller.ts +109 -0
  53. package/src/_modules/logs/logs.service.ts +100 -0
@@ -0,0 +1,202 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+
5
+ import { DyNTS_global_settings } from '../../_collections/global-settings.const';
6
+
7
+ import { DyNTS_FileLog_Service } from './file-log.service';
8
+
9
+
10
+ describe('| DyNTS_FileLog_Service', (): void => {
11
+ let tempDir: string;
12
+ let originalFileLogConfig: typeof DyNTS_global_settings.log_settings.file_log;
13
+
14
+ beforeEach((): void => {
15
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nts-filelog-spec-'));
16
+ originalFileLogConfig = DyNTS_global_settings.log_settings.file_log;
17
+ });
18
+
19
+ afterEach((): void => {
20
+ // Mindig allitsuk vissza az eredeti console-t es config-ot, hogy
21
+ // ne szivarogtassuk at a teszt envt.
22
+ DyNTS_FileLog_Service.getInstance()._teardownForTesting();
23
+ DyNTS_global_settings.log_settings.file_log = originalFileLogConfig;
24
+ try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch { /* swallow */ }
25
+ });
26
+
27
+ describe('| install() — gating', (): void => {
28
+ it('| no-op ha file_log config hianyzik', (): void => {
29
+ DyNTS_global_settings.log_settings.file_log = undefined;
30
+ DyNTS_FileLog_Service.getInstance().install();
31
+ expect(DyNTS_FileLog_Service.getInstance().isInstalled()).toBe(false);
32
+ });
33
+
34
+ it('| no-op ha enabled === false', (): void => {
35
+ DyNTS_global_settings.log_settings.file_log = { enabled: false, logDir: tempDir };
36
+ DyNTS_FileLog_Service.getInstance().install();
37
+ expect(DyNTS_FileLog_Service.getInstance().isInstalled()).toBe(false);
38
+ });
39
+
40
+ it('| install() idempotens — masodszorra is no-op', (): void => {
41
+ DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir };
42
+ const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance();
43
+ svc.install();
44
+ const firstPath: string = svc.getCurrentLogPath();
45
+ svc.install(); // ismet
46
+ expect(svc.getCurrentLogPath()).toBe(firstPath);
47
+ });
48
+ });
49
+
50
+ describe('| install() — file creation + intercept', (): void => {
51
+ it('| stdout iras a fajlba is bekerul', (): void => {
52
+ DyNTS_global_settings.log_settings.file_log = {
53
+ enabled: true,
54
+ logDir: tempDir,
55
+ filenamePrefix: 'spec-',
56
+ };
57
+ const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance();
58
+ svc.install();
59
+
60
+ const marker: string = `hello-from-spec-${Date.now()}\n`;
61
+ process.stdout.write(marker);
62
+
63
+ const logPath: string = svc.getCurrentLogPath();
64
+ expect(fs.existsSync(logPath)).toBe(true);
65
+ const contents: string = fs.readFileSync(logPath, 'utf-8');
66
+ expect(contents).toContain(marker.trim());
67
+ });
68
+
69
+ it('| stderr iras a fajlba is bekerul ha includeStderr=true', (): void => {
70
+ DyNTS_global_settings.log_settings.file_log = {
71
+ enabled: true,
72
+ logDir: tempDir,
73
+ filenamePrefix: 'spec-',
74
+ };
75
+ const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance();
76
+ svc.install();
77
+
78
+ const marker: string = `error-marker-${Date.now()}\n`;
79
+ process.stderr.write(marker);
80
+
81
+ const contents: string = fs.readFileSync(svc.getCurrentLogPath(), 'utf-8');
82
+ expect(contents).toContain(marker.trim());
83
+ });
84
+
85
+ it('| ANSI escape kodok strippelve a fajlban ha stripAnsi=true (default)', (): void => {
86
+ DyNTS_global_settings.log_settings.file_log = {
87
+ enabled: true,
88
+ logDir: tempDir,
89
+ filenamePrefix: 'spec-',
90
+ };
91
+ const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance();
92
+ svc.install();
93
+
94
+ process.stdout.write('\x1b[31mredtext\x1b[0m-payload\n');
95
+
96
+ const contents: string = fs.readFileSync(svc.getCurrentLogPath(), 'utf-8');
97
+ expect(contents).toContain('redtext-payload');
98
+ expect(contents).not.toContain('\x1b[31m');
99
+ expect(contents).not.toContain('\x1b[0m');
100
+ });
101
+
102
+ it('| ANSI megmarad ha stripAnsi=false', (): void => {
103
+ DyNTS_global_settings.log_settings.file_log = {
104
+ enabled: true,
105
+ logDir: tempDir,
106
+ filenamePrefix: 'spec-',
107
+ stripAnsi: false,
108
+ };
109
+ const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance();
110
+ svc.install();
111
+
112
+ process.stdout.write('\x1b[31mraw\x1b[0m\n');
113
+
114
+ const contents: string = fs.readFileSync(svc.getCurrentLogPath(), 'utf-8');
115
+ expect(contents).toContain('\x1b[31mraw\x1b[0m');
116
+ });
117
+ });
118
+
119
+ describe('| rotation', (): void => {
120
+ it('| uj fajl jon letre ha az iras meghaladja a maxFileSizeMb-t', (): void => {
121
+ DyNTS_global_settings.log_settings.file_log = {
122
+ enabled: true,
123
+ logDir: tempDir,
124
+ filenamePrefix: 'rotspec-',
125
+ // 1 byte limit → minden iras azonnal triggereli a rotaciot
126
+ maxFileSizeMb: 1 / (1024 * 1024),
127
+ };
128
+ const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance();
129
+ svc.install();
130
+
131
+ const firstPath: string = svc.getCurrentLogPath();
132
+ process.stdout.write('payload-1\n');
133
+ process.stdout.write('payload-2\n');
134
+
135
+ const secondPath: string = svc.getCurrentLogPath();
136
+ expect(secondPath).not.toBe(firstPath);
137
+ // Mindket fajl letezik a tempDir-ben
138
+ const files: string[] = fs.readdirSync(tempDir).filter((f: string) => f.startsWith('rotspec-'));
139
+ expect(files.length).toBeGreaterThanOrEqual(2);
140
+ });
141
+ });
142
+
143
+ describe('| retention', (): void => {
144
+ it('| install() torli a `retentionDays`-nel regebbi fajlokat (mtime backdated)', (): void => {
145
+ const oldFile: string = path.join(tempDir, 'spec-2020-01-01_00-00-00_abc.log');
146
+ fs.writeFileSync(oldFile, 'old');
147
+ const oldMs: number = Date.now() - 365 * 24 * 60 * 60 * 1000;
148
+ fs.utimesSync(oldFile, oldMs / 1000, oldMs / 1000);
149
+
150
+ DyNTS_global_settings.log_settings.file_log = {
151
+ enabled: true,
152
+ logDir: tempDir,
153
+ filenamePrefix: 'spec-',
154
+ retentionDays: 30,
155
+ maxFiles: 100, // count szabaly NE triggereljen
156
+ };
157
+ DyNTS_FileLog_Service.getInstance().install();
158
+
159
+ expect(fs.existsSync(oldFile)).toBe(false);
160
+ });
161
+
162
+ it('| install() retention by count — maxFiles-nel tobb fajl van, a legregebbi torlodik', (): void => {
163
+ // 3 fix mtime-u "regi" fajl letrehozasa (azonnali age szempontbol mind ok)
164
+ const now: number = Date.now();
165
+ const f1: string = path.join(tempDir, 'spec-2026-01-01_00-00-01_aaa.log');
166
+ const f2: string = path.join(tempDir, 'spec-2026-01-01_00-00-02_bbb.log');
167
+ const f3: string = path.join(tempDir, 'spec-2026-01-01_00-00-03_ccc.log');
168
+ fs.writeFileSync(f1, '1'); fs.utimesSync(f1, (now - 30000) / 1000, (now - 30000) / 1000);
169
+ fs.writeFileSync(f2, '2'); fs.utimesSync(f2, (now - 20000) / 1000, (now - 20000) / 1000);
170
+ fs.writeFileSync(f3, '3'); fs.utimesSync(f3, (now - 10000) / 1000, (now - 10000) / 1000);
171
+
172
+ DyNTS_global_settings.log_settings.file_log = {
173
+ enabled: true,
174
+ logDir: tempDir,
175
+ filenamePrefix: 'spec-',
176
+ maxFiles: 2, // a legregebbi (f1) torlodik
177
+ retentionDays: 36500, // age szabaly NE triggereljen
178
+ };
179
+ DyNTS_FileLog_Service.getInstance().install();
180
+
181
+ expect(fs.existsSync(f1)).toBe(false);
182
+ expect(fs.existsSync(f2)).toBe(true);
183
+ expect(fs.existsSync(f3)).toBe(true);
184
+ });
185
+ });
186
+
187
+ describe('| silent failure', (): void => {
188
+ it('| install() NEM crashel ha a logDir letrehozasa bukik', (): void => {
189
+ // Olyan path amit nem lehet letrehozni: egy meglévő fajl ALATTI subdir.
190
+ const blocker: string = path.join(tempDir, 'iam-a-file');
191
+ fs.writeFileSync(blocker, '');
192
+ const impossible: string = path.join(blocker, 'cant-create-subdir');
193
+
194
+ DyNTS_global_settings.log_settings.file_log = {
195
+ enabled: true,
196
+ logDir: impossible,
197
+ };
198
+ expect(() => DyNTS_FileLog_Service.getInstance().install()).not.toThrow();
199
+ expect(DyNTS_FileLog_Service.getInstance().isInstalled()).toBe(false);
200
+ });
201
+ });
202
+ });
@@ -0,0 +1,283 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ import { DyNTS_SingletonServiceBase } from '../../_services/base/singleton.service-base';
5
+ import { DyNTS_global_settings } from '../../_collections/global-settings.const';
6
+
7
+
8
+ /**
9
+ * Default ertekek a file-log config mezokhez. A `file_log` config nelkul
10
+ * az install() no-op (enabled === false).
11
+ */
12
+ const DEFAULT_LOG_DIR: string = './logs/server';
13
+ const DEFAULT_FILENAME_PREFIX: string = 'server-';
14
+ const DEFAULT_FILENAME_SUFFIX: string = '.log';
15
+ const DEFAULT_MAX_FILE_SIZE_MB: number = 50;
16
+ const DEFAULT_MAX_FILES: number = 10;
17
+ const DEFAULT_RETENTION_DAYS: number = 30;
18
+ const DEFAULT_STRIP_ANSI: boolean = true;
19
+ const DEFAULT_INCLUDE_STDOUT: boolean = true;
20
+ const DEFAULT_INCLUDE_STDERR: boolean = true;
21
+
22
+ /** ANSI escape code regex (szin/formazas kodok). */
23
+ const ANSI_ESCAPE_REGEX: RegExp = /\x1b\[[0-9;]*[a-zA-Z]/g;
24
+
25
+
26
+ /**
27
+ * File-based szerver log service — duplikalja a stdout/stderr-t per-session
28
+ * log fajl(ok)ba, miközben a console kimenet erintetlenul marad. Az
29
+ * in-memory ring buffer (`DyNTS_Logs_Service`) MELLETT mukodik, NEM
30
+ * helyette.
31
+ *
32
+ * Architecture:
33
+ * - `process.stdout.write` es `process.stderr.write` monkey-patch (NEM
34
+ * `console.*` — azt mar a `DyNTS_Logs_Service` patch-elheti)
35
+ * - Per-session fajl: `{logDir}/{prefix}YYYY-MM-DD_HH-MM-SS.log`
36
+ * - Sync `appendFileSync` iras — crash-safe, azonnali flush
37
+ * - Rotation: `maxFileSizeMb` atlepesenel uj session fajl
38
+ * - Retention: `maxFiles` (count) ES `retentionDays` (age) — ami elobb
39
+ * teljesul; install-kor + minden rotation utan futtatva
40
+ * - Silent failure: file IO hibak ELNYELODNEK (a file logger SOHASEM
41
+ * buktathatja el a szervert)
42
+ *
43
+ * Idempotens: tobbszori install() hivas no-op (a masodiktol).
44
+ *
45
+ * Hasznalat:
46
+ * DyNTS_global_settings.log_settings.file_log = { enabled: true, ... };
47
+ * DyNTS_FileLog_Service.getInstance().install(); // szerver startup
48
+ */
49
+ export class DyNTS_FileLog_Service extends DyNTS_SingletonServiceBase {
50
+
51
+ static getInstance(): DyNTS_FileLog_Service {
52
+ return DyNTS_FileLog_Service.getSingletonInstance() as DyNTS_FileLog_Service;
53
+ }
54
+
55
+ private installed: boolean = false;
56
+ private activeLogDir: string = '';
57
+ private filenamePrefix: string = DEFAULT_FILENAME_PREFIX;
58
+ private maxFileSizeBytes: number = DEFAULT_MAX_FILE_SIZE_MB * 1024 * 1024;
59
+ private maxFiles: number = DEFAULT_MAX_FILES;
60
+ private retentionDays: number = DEFAULT_RETENTION_DAYS;
61
+ private stripAnsiEnabled: boolean = DEFAULT_STRIP_ANSI;
62
+ private currentLogPath: string = '';
63
+ private currentLogSizeBytes: number = 0;
64
+
65
+ private originalStdoutWrite: typeof process.stdout.write | null = null;
66
+ private originalStderrWrite: typeof process.stderr.write | null = null;
67
+
68
+
69
+ /**
70
+ * Telepiti a file logger-t a `DyNTS_global_settings.log_settings.file_log`
71
+ * config alapjan. Ha `enabled === false` vagy hianyzik a config → no-op.
72
+ *
73
+ * Idempotens — masodszori hivas no-op (akkor is, ha kozben a config
74
+ * valtozott; a service uj install-jara teardownFor Testing() + install()).
75
+ */
76
+ install(): void {
77
+ if (this.installed) { return; }
78
+
79
+ const config = DyNTS_global_settings.log_settings?.file_log;
80
+ if (!config || !config.enabled) { return; }
81
+
82
+ // Config feldolgozas + default-ok
83
+ this.activeLogDir = config.logDir ?? DEFAULT_LOG_DIR;
84
+ this.filenamePrefix = config.filenamePrefix ?? DEFAULT_FILENAME_PREFIX;
85
+ this.maxFileSizeBytes = (config.maxFileSizeMb ?? DEFAULT_MAX_FILE_SIZE_MB) * 1024 * 1024;
86
+ this.maxFiles = config.maxFiles ?? DEFAULT_MAX_FILES;
87
+ this.retentionDays = config.retentionDays ?? DEFAULT_RETENTION_DAYS;
88
+ this.stripAnsiEnabled = config.stripAnsi ?? DEFAULT_STRIP_ANSI;
89
+ const includeStdout: boolean = config.includeStdout ?? DEFAULT_INCLUDE_STDOUT;
90
+ const includeStderr: boolean = config.includeStderr ?? DEFAULT_INCLUDE_STDERR;
91
+
92
+ // Log dir letrehozasa silently
93
+ try {
94
+ if (!fs.existsSync(this.activeLogDir)) {
95
+ fs.mkdirSync(this.activeLogDir, { recursive: true });
96
+ }
97
+ } catch {
98
+ // Ha a mkdir bukik (pl. permission denied), no-op — silent failure
99
+ return;
100
+ }
101
+
102
+ // Retention cleanup MIELOTT az uj session fajl letrejon (hogy a takaritas
103
+ // log uzenete ne keruljon a most induló fajlba)
104
+ this.cleanupOldLogs();
105
+
106
+ // Uj session fajl path + size init
107
+ this.currentLogPath = path.join(this.activeLogDir, this.buildSessionFilename());
108
+ this.currentLogSizeBytes = 0;
109
+
110
+ // Monkey-patch — az originalokat elmentjük (teardown miatt)
111
+ if (includeStdout) {
112
+ this.originalStdoutWrite = process.stdout.write.bind(process.stdout);
113
+ const self: DyNTS_FileLog_Service = this;
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ process.stdout.write = function patchedStdoutWrite(...args: any[]): boolean {
116
+ self.teeWrite(args[0]);
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
+ return (self.originalStdoutWrite as any)(...args);
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ } as any;
121
+ }
122
+ if (includeStderr) {
123
+ this.originalStderrWrite = process.stderr.write.bind(process.stderr);
124
+ const self: DyNTS_FileLog_Service = this;
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ process.stderr.write = function patchedStderrWrite(...args: any[]): boolean {
127
+ self.teeWrite(args[0]);
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
+ return (self.originalStderrWrite as any)(...args);
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
+ } as any;
132
+ }
133
+
134
+ this.installed = true;
135
+
136
+ // Session header — kulcs info egy helyen a fajl tetejen
137
+ this.teeWrite(
138
+ `===== DyNTS server log session started at ${new Date().toISOString()} =====\n` +
139
+ `===== PID: ${process.pid}, Node: ${process.version} =====\n` +
140
+ `===== Retention: ${this.retentionDays} days, max files: ${this.maxFiles}, max size: ${config.maxFileSizeMb ?? DEFAULT_MAX_FILE_SIZE_MB}MB =====\n\n`,
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Visszaadja az aktualis log fajl abszolut path-jat (csak akkor letezo,
146
+ * ha az install() sikeresen lefutott; egyebkent ures string).
147
+ */
148
+ getCurrentLogPath(): string {
149
+ return this.currentLogPath;
150
+ }
151
+
152
+ /**
153
+ * Telepitve van-e (csak akkor true, ha az enabled === true es a setup nem bukott).
154
+ */
155
+ isInstalled(): boolean {
156
+ return this.installed;
157
+ }
158
+
159
+ /**
160
+ * Test-only: visszallitja az eredeti stdout/stderr.write-okat, hogy a
161
+ * specfajlok egymas utan tisztán futhassanak. Production code NE hivja.
162
+ */
163
+ _teardownForTesting(): void {
164
+ if (this.originalStdoutWrite) {
165
+ process.stdout.write = this.originalStdoutWrite;
166
+ this.originalStdoutWrite = null;
167
+ }
168
+ if (this.originalStderrWrite) {
169
+ process.stderr.write = this.originalStderrWrite;
170
+ this.originalStderrWrite = null;
171
+ }
172
+ this.installed = false;
173
+ this.activeLogDir = '';
174
+ this.currentLogPath = '';
175
+ this.currentLogSizeBytes = 0;
176
+ }
177
+
178
+
179
+ /**
180
+ * Session fajl nev: `{prefix}YYYY-MM-DD_HH-MM-SS_RANDOM.log`. Az RANDOM
181
+ * suffix biztositja a unique nevet ha ugyanazon a masodpercen tobb fajl jon letre
182
+ * (pl. rotation kozvetlenul install utan, vagy specfajlok gyors egymas utan).
183
+ */
184
+ private buildSessionFilename(): string {
185
+ const now: Date = new Date();
186
+ const year: string = String(now.getFullYear());
187
+ const month: string = String(now.getMonth() + 1).padStart(2, '0');
188
+ const day: string = String(now.getDate()).padStart(2, '0');
189
+ const hour: string = String(now.getHours()).padStart(2, '0');
190
+ const minute: string = String(now.getMinutes()).padStart(2, '0');
191
+ const second: string = String(now.getSeconds()).padStart(2, '0');
192
+ const rand: string = Math.floor(Math.random() * 100000).toString(36);
193
+ return `${this.filenamePrefix}${year}-${month}-${day}_${hour}-${minute}-${second}_${rand}${DEFAULT_FILENAME_SUFFIX}`;
194
+ }
195
+
196
+
197
+ /**
198
+ * Egyetlen stdout/stderr iras tee-zese az aktualis log fajlba.
199
+ * Hibakat csendben elnyeli — a file logger SOHASEM blokkolja a szervert.
200
+ * Size-trackeli az aktualis fajlt es szukseg szerint rotalja.
201
+ */
202
+ private teeWrite(data: string | Uint8Array): void {
203
+ try {
204
+ if (!this.currentLogPath) { return; }
205
+
206
+ const asString: string = typeof data === 'string'
207
+ ? data
208
+ : Buffer.from(data).toString('utf-8');
209
+ const cleaned: string = this.stripAnsiEnabled ? asString.replace(ANSI_ESCAPE_REGEX, '') : asString;
210
+ const writeBytes: number = Buffer.byteLength(cleaned, 'utf-8');
211
+
212
+ fs.appendFileSync(this.currentLogPath, cleaned, 'utf-8');
213
+ this.currentLogSizeBytes += writeBytes;
214
+
215
+ // Rotation trigger
216
+ if (this.currentLogSizeBytes >= this.maxFileSizeBytes) {
217
+ this.rotate();
218
+ }
219
+ } catch {
220
+ // szandekosan elnyelve — silent failure
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Uj session fajl letrehozasa (az aktualis lezarodik). Retention cleanup-ot is
226
+ * vegrehajt, hogy a rotation termeszetesen tisztan tarttsa a log dir-t.
227
+ */
228
+ private rotate(): void {
229
+ try {
230
+ this.cleanupOldLogs();
231
+ this.currentLogPath = path.join(this.activeLogDir, this.buildSessionFilename());
232
+ this.currentLogSizeBytes = 0;
233
+ } catch {
234
+ // szandekosan elnyelve
235
+ }
236
+ }
237
+
238
+
239
+ /**
240
+ * Retention takaritas: ket szabaly ami ami elobb teljesul:
241
+ * 1) maxFiles — ha tobb mint N log fajl van, a legregebbieket torli
242
+ * 2) retentionDays — `retentionDays`-nel idosebb fajlokat torli
243
+ *
244
+ * Hibakat csendben elnyel. A torolt fajlokat es az okot NEM logoljuk, hogy
245
+ * ne kerüljön extra szennyezes a most induló session fajlba.
246
+ */
247
+ private cleanupOldLogs(): void {
248
+ try {
249
+ if (!fs.existsSync(this.activeLogDir)) { return; }
250
+
251
+ const cutoffMs: number = Date.now() - this.retentionDays * 24 * 60 * 60 * 1000;
252
+ const entries: string[] = fs.readdirSync(this.activeLogDir);
253
+
254
+ type FileInfo = { fullPath: string; mtimeMs: number };
255
+ const matchingFiles: FileInfo[] = [];
256
+
257
+ for (const entry of entries) {
258
+ if (!entry.startsWith(this.filenamePrefix) || !entry.endsWith(DEFAULT_FILENAME_SUFFIX)) { continue; }
259
+ const fullPath: string = path.join(this.activeLogDir, entry);
260
+ try {
261
+ const stats: fs.Stats = fs.statSync(fullPath);
262
+ matchingFiles.push({ fullPath: fullPath, mtimeMs: stats.mtimeMs });
263
+ } catch {
264
+ // fajlonkent elnyelve
265
+ }
266
+ }
267
+
268
+ // Sort: legujabb elol -> legregebbi hatul (csokkeno mtime)
269
+ matchingFiles.sort((a: FileInfo, b: FileInfo): number => b.mtimeMs - a.mtimeMs);
270
+
271
+ for (let i: number = 0; i < matchingFiles.length; i++) {
272
+ const file: FileInfo = matchingFiles[i];
273
+ const tooOldByAge: boolean = file.mtimeMs < cutoffMs;
274
+ const tooManyByCount: boolean = i >= this.maxFiles;
275
+ if (tooOldByAge || tooManyByCount) {
276
+ try { fs.unlinkSync(file.fullPath); } catch { /* fajlonkent elnyelve */ }
277
+ }
278
+ }
279
+ } catch {
280
+ // szandekosan elnyelve
281
+ }
282
+ }
283
+ }
@@ -0,0 +1,36 @@
1
+ import { Request, Response } from 'express';
2
+
3
+ import { DyNTS_RoutingModule_Settings } from '../../_models/interfaces/routing-module-settings.interface';
4
+ import { DyNTS_RoutingModule } from '../../_services/route/routing-module.service';
5
+ import { DyNTS_Logs_Controller, DyNTS_LogsController_Config } from './logs.controller';
6
+
7
+ /**
8
+ * Letrehoz egy /logs routing module-t a szerver logok lekeresehez.
9
+ *
10
+ * Hasznalat (auth NELKUL — barki elerheti):
11
+ * DyNTS_getLogsRoutingModule()
12
+ *
13
+ * Hasznalat (auth-TAL — a megadott preProcess fut elotte):
14
+ * DyNTS_getLogsRoutingModule({
15
+ * authPreProcess: authService.authenticate_sercretKey
16
+ * })
17
+ *
18
+ * FONTOS: A szerver indulasakor meg kell hivni:
19
+ * DyNTS_Logs_Service.getInstance().install()
20
+ * es a log_settings.logs_endpoint.enabled = true kell legyen.
21
+ */
22
+ export function DyNTS_getLogsRoutingModule(
23
+ config?: DyNTS_LogsController_Config
24
+ ): DyNTS_RoutingModule {
25
+ // Konfiguracio atadasa a controller-nek (static, mert a singleton elott kell)
26
+ if (config) {
27
+ DyNTS_Logs_Controller.configure(config);
28
+ }
29
+
30
+ const set: DyNTS_RoutingModule_Settings = {
31
+ route: '/logs',
32
+ controllers: [DyNTS_Logs_Controller.getInstance()],
33
+ };
34
+
35
+ return new DyNTS_RoutingModule(set);
36
+ }
@@ -0,0 +1,4 @@
1
+ export { DyNTS_Logs_Service } from './logs.service';
2
+ export { DyNTS_Logs_Controller, DyNTS_LogsController_Config } from './logs.controller';
3
+ export { DyNTS_getLogsRoutingModule } from './get-logs-routing-module.util';
4
+ export { DyNTS_FileLog_Service } from './file-log.service';
@@ -0,0 +1,101 @@
1
+
2
+ import { DyNTS_SingletonService } from '../../_services/base/singleton.service';
3
+
4
+ /**
5
+ * In-memory ring buffer a szerver console kimenethez.
6
+ * Interceptalja a console.log/error/warn/info hivasokat es eltarolja az utolso N sort.
7
+ *
8
+ * Hasznalat:
9
+ * DyNTS_LogBuffer_Service.getInstance().install(); // app startup-nal
10
+ * DyNTS_LogBuffer_Service.getInstance().getLines(100); // utolso 100 sor
11
+ */
12
+ export class DyNTS_LogBuffer_Service extends DyNTS_SingletonService {
13
+
14
+ static getInstance(): DyNTS_LogBuffer_Service {
15
+ return DyNTS_LogBuffer_Service.getSingletonInstance();
16
+ }
17
+
18
+ private readonly maxLines: number = 2000;
19
+ private readonly buffer: string[] = [];
20
+ private installed: boolean = false;
21
+
22
+ /**
23
+ * Interceptalja a console.log/error/warn/info kimeneteket es a bufferbe irja.
24
+ * Egyszer kell hivni, a szerver indulasakor (lehetoleg a legelso muvelet).
25
+ */
26
+ install(): void {
27
+ if (this.installed) { return; }
28
+ this.installed = true;
29
+
30
+ const self: DyNTS_LogBuffer_Service = this;
31
+
32
+ const originalLog: typeof console.log = console.log.bind(console);
33
+ const originalError: typeof console.error = console.error.bind(console);
34
+ const originalWarn: typeof console.warn = console.warn.bind(console);
35
+ const originalInfo: typeof console.info = console.info.bind(console);
36
+
37
+ console.log = (...args: any[]): void => {
38
+ self.addLine('LOG', args);
39
+ originalLog(...args);
40
+ };
41
+
42
+ console.error = (...args: any[]): void => {
43
+ self.addLine('ERR', args);
44
+ originalError(...args);
45
+ };
46
+
47
+ console.warn = (...args: any[]): void => {
48
+ self.addLine('WRN', args);
49
+ originalWarn(...args);
50
+ };
51
+
52
+ console.info = (...args: any[]): void => {
53
+ self.addLine('INF', args);
54
+ originalInfo(...args);
55
+ };
56
+ }
57
+
58
+ /**
59
+ * A log buffer be van-e kapcsolva (install megtortent-e).
60
+ */
61
+ isInstalled(): boolean {
62
+ return this.installed;
63
+ }
64
+
65
+ /**
66
+ * Az utolso N sor lekerdezese.
67
+ */
68
+ getLines(count: number = 200): string[] {
69
+ const start: number = Math.max(0, this.buffer.length - count);
70
+ return this.buffer.slice(start);
71
+ }
72
+
73
+ /**
74
+ * Az osszes buffered sor szama.
75
+ */
76
+ getLineCount(): number {
77
+ return this.buffer.length;
78
+ }
79
+
80
+ /**
81
+ * Buffer uritese.
82
+ */
83
+ clear(): void {
84
+ this.buffer.length = 0;
85
+ }
86
+
87
+ private addLine(level: string, args: any[]): void {
88
+ const timestamp: string = new Date().toISOString();
89
+ const message: string = args.map(formatArg).join(' ');
90
+ this.buffer.push(`${timestamp} [${level}] ${message}`);
91
+
92
+ if (this.buffer.length > this.maxLines) {
93
+ this.buffer.shift();
94
+ }
95
+ }
96
+ }
97
+
98
+ function formatArg(arg: any): string {
99
+ if (typeof arg === 'string') { return arg; }
100
+ try { return JSON.stringify(arg); } catch { return String(arg); }
101
+ }