create-forgeon 0.3.4 → 0.3.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/package.json +1 -1
- package/src/modules/executor.test.mjs +10 -4
- package/src/modules/logger.mjs +12 -3
- package/templates/module-presets/logger/packages/logger/src/forgeon-logger.module.ts +4 -5
- package/templates/module-presets/logger/packages/logger/src/http-logging.middleware.ts +74 -0
- package/templates/module-presets/logger/packages/logger/src/index.ts +1 -1
package/package.json
CHANGED
|
@@ -572,10 +572,9 @@ describe('addModule', () => {
|
|
|
572
572
|
|
|
573
573
|
const mainTs = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'main.ts'), 'utf8');
|
|
574
574
|
assert.match(mainTs, /ForgeonLoggerService/);
|
|
575
|
-
assert.match(mainTs, /ForgeonHttpLoggingInterceptor/);
|
|
576
575
|
assert.match(mainTs, /bufferLogs: true/);
|
|
577
576
|
assert.match(mainTs, /app\.useLogger\(app\.get\(ForgeonLoggerService\)\);/);
|
|
578
|
-
assert.
|
|
577
|
+
assert.doesNotMatch(mainTs, /useGlobalInterceptors/);
|
|
579
578
|
|
|
580
579
|
const apiDockerfile = fs.readFileSync(
|
|
581
580
|
path.join(projectRoot, 'apps', 'api', 'Dockerfile'),
|
|
@@ -591,6 +590,13 @@ describe('addModule', () => {
|
|
|
591
590
|
);
|
|
592
591
|
assert.match(loggerTsconfig, /"extends": "\.\.\/\.\.\/tsconfig\.base\.node\.json"/);
|
|
593
592
|
|
|
593
|
+
const loggerModule = fs.readFileSync(
|
|
594
|
+
path.join(projectRoot, 'packages', 'logger', 'src', 'forgeon-logger.module.ts'),
|
|
595
|
+
'utf8',
|
|
596
|
+
);
|
|
597
|
+
assert.match(loggerModule, /ForgeonHttpLoggingMiddleware/);
|
|
598
|
+
assert.match(loggerModule, /consumer\.apply\(RequestIdMiddleware, ForgeonHttpLoggingMiddleware\)\.forRoutes\('\*'\);/);
|
|
599
|
+
|
|
594
600
|
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
595
601
|
assert.match(apiEnv, /LOGGER_LEVEL=log/);
|
|
596
602
|
assert.match(apiEnv, /LOGGER_HTTP_ENABLED=true/);
|
|
@@ -957,7 +963,7 @@ describe('addModule', () => {
|
|
|
957
963
|
|
|
958
964
|
const mainTs = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'main.ts'), 'utf8');
|
|
959
965
|
assert.match(mainTs, /ForgeonLoggerService/);
|
|
960
|
-
assert.
|
|
966
|
+
assert.doesNotMatch(mainTs, /ForgeonHttpLoggingInterceptor/);
|
|
961
967
|
} finally {
|
|
962
968
|
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
963
969
|
}
|
|
@@ -1058,7 +1064,7 @@ describe('addModule', () => {
|
|
|
1058
1064
|
const mainTs = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'main.ts'), 'utf8');
|
|
1059
1065
|
assert.match(mainTs, /setupSwagger\(app,\s*swaggerConfigService\)/);
|
|
1060
1066
|
assert.match(mainTs, /app\.useLogger\(app\.get\(ForgeonLoggerService\)\);/);
|
|
1061
|
-
assert.
|
|
1067
|
+
assert.doesNotMatch(mainTs, /useGlobalInterceptors/);
|
|
1062
1068
|
|
|
1063
1069
|
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
1064
1070
|
assert.match(apiPackage, /@forgeon\/swagger/);
|
package/src/modules/logger.mjs
CHANGED
|
@@ -44,10 +44,14 @@ function patchMain(targetRoot) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
47
|
+
content = content.replace(
|
|
48
|
+
"import { ForgeonHttpLoggingInterceptor, ForgeonLoggerService } from '@forgeon/logger';",
|
|
49
|
+
"import { ForgeonLoggerService } from '@forgeon/logger';",
|
|
50
|
+
);
|
|
47
51
|
content = ensureLineBefore(
|
|
48
52
|
content,
|
|
49
53
|
"import { NestFactory } from '@nestjs/core';",
|
|
50
|
-
"import {
|
|
54
|
+
"import { ForgeonLoggerService } from '@forgeon/logger';",
|
|
51
55
|
);
|
|
52
56
|
|
|
53
57
|
content = content.replace(
|
|
@@ -55,12 +59,16 @@ function patchMain(targetRoot) {
|
|
|
55
59
|
'const app = await NestFactory.create(AppModule, { bufferLogs: true });',
|
|
56
60
|
);
|
|
57
61
|
|
|
62
|
+
content = content.replace(
|
|
63
|
+
/\n\s*app\.useGlobalInterceptors\(app\.get\(ForgeonHttpLoggingInterceptor\)\);\s*/g,
|
|
64
|
+
'\n',
|
|
65
|
+
);
|
|
66
|
+
|
|
58
67
|
if (!content.includes('app.useLogger(app.get(ForgeonLoggerService));')) {
|
|
59
68
|
content = content.replace(
|
|
60
69
|
' const coreConfigService = app.get(CoreConfigService);',
|
|
61
70
|
` const coreConfigService = app.get(CoreConfigService);
|
|
62
|
-
app.useLogger(app.get(ForgeonLoggerService))
|
|
63
|
-
app.useGlobalInterceptors(app.get(ForgeonHttpLoggingInterceptor));`,
|
|
71
|
+
app.useLogger(app.get(ForgeonLoggerService));`,
|
|
64
72
|
);
|
|
65
73
|
}
|
|
66
74
|
|
|
@@ -179,6 +187,7 @@ function patchReadme(targetRoot) {
|
|
|
179
187
|
The logger add-module provides:
|
|
180
188
|
- request id middleware (default header: \`x-request-id\`)
|
|
181
189
|
- HTTP access logs with method/path/status/duration/ip/requestId
|
|
190
|
+
- HTTP access logs are emitted from middleware, so requests rejected by guards (for example 429 from rate-limit) are still logged
|
|
182
191
|
- Nest logger integration via \`app.useLogger(...)\`
|
|
183
192
|
|
|
184
193
|
It installs independently and intentionally does not add a dedicated API/web probe.
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
|
2
|
-
import {
|
|
2
|
+
import { ForgeonHttpLoggingMiddleware } from './http-logging.middleware';
|
|
3
3
|
import { ForgeonLoggerService } from './forgeon-logger.service';
|
|
4
4
|
import { LoggerConfigModule } from './logger-config.module';
|
|
5
5
|
import { RequestIdMiddleware } from './request-id.middleware';
|
|
6
6
|
|
|
7
7
|
@Module({
|
|
8
8
|
imports: [LoggerConfigModule],
|
|
9
|
-
providers: [RequestIdMiddleware,
|
|
10
|
-
exports: [LoggerConfigModule, ForgeonLoggerService
|
|
9
|
+
providers: [RequestIdMiddleware, ForgeonHttpLoggingMiddleware, ForgeonLoggerService],
|
|
10
|
+
exports: [LoggerConfigModule, ForgeonLoggerService],
|
|
11
11
|
})
|
|
12
12
|
export class ForgeonLoggerModule implements NestModule {
|
|
13
13
|
configure(consumer: MiddlewareConsumer): void {
|
|
14
|
-
consumer.apply(RequestIdMiddleware).forRoutes('*');
|
|
14
|
+
consumer.apply(RequestIdMiddleware, ForgeonHttpLoggingMiddleware).forRoutes('*');
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Injectable, NestMiddleware } from '@nestjs/common';
|
|
2
|
+
import { ForgeonLoggerService } from './forgeon-logger.service';
|
|
3
|
+
import { LoggerConfigService } from './logger-config.service';
|
|
4
|
+
|
|
5
|
+
type HeaderValue = string | string[] | undefined;
|
|
6
|
+
type HeadersRecord = Record<string, HeaderValue>;
|
|
7
|
+
|
|
8
|
+
interface RequestLike {
|
|
9
|
+
method?: string;
|
|
10
|
+
originalUrl?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
ip?: string;
|
|
13
|
+
requestId?: string;
|
|
14
|
+
headers?: HeadersRecord;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ResponseLike {
|
|
18
|
+
statusCode?: number;
|
|
19
|
+
once?: (event: string, listener: () => void) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type NextFunction = () => void;
|
|
23
|
+
|
|
24
|
+
@Injectable()
|
|
25
|
+
export class ForgeonHttpLoggingMiddleware implements NestMiddleware {
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly logger: ForgeonLoggerService,
|
|
28
|
+
private readonly loggerConfig: LoggerConfigService,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
use(request: RequestLike, response: ResponseLike, next: NextFunction): void {
|
|
32
|
+
if (!this.loggerConfig.httpEnabled) {
|
|
33
|
+
next();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const method = request.method ?? 'UNKNOWN';
|
|
38
|
+
const path = request.originalUrl ?? request.url ?? '/';
|
|
39
|
+
const ip = request.ip;
|
|
40
|
+
const requestId =
|
|
41
|
+
request.requestId ?? this.readHeader(request.headers, this.loggerConfig.requestIdHeader);
|
|
42
|
+
const startedAt = Date.now();
|
|
43
|
+
|
|
44
|
+
if (typeof response.once === 'function') {
|
|
45
|
+
response.once('finish', () => {
|
|
46
|
+
this.logger.logHttpRequest({
|
|
47
|
+
method,
|
|
48
|
+
path,
|
|
49
|
+
statusCode: response.statusCode ?? 200,
|
|
50
|
+
durationMs: Date.now() - startedAt,
|
|
51
|
+
requestId,
|
|
52
|
+
ip,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
next();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private readHeader(headers: HeadersRecord | undefined, name: string): string | undefined {
|
|
61
|
+
if (!headers) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const value = headers[name.toLowerCase()];
|
|
66
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(value) && typeof value[0] === 'string' && value[0].trim().length > 0) {
|
|
70
|
+
return value[0];
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -3,7 +3,7 @@ export * from './logger-config.loader';
|
|
|
3
3
|
export * from './logger-config.service';
|
|
4
4
|
export * from './logger-config.module';
|
|
5
5
|
export * from './forgeon-logger.service';
|
|
6
|
+
export * from './http-logging.middleware';
|
|
6
7
|
export * from './http-logging.interceptor';
|
|
7
8
|
export * from './request-id.middleware';
|
|
8
9
|
export * from './forgeon-logger.module';
|
|
9
|
-
|