Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"private": true,
"dependencies": {
"@koa/cors": "^3.3.0",
"bytes": "^3.1.2",
"class-validator": "^0.13.2",
"commander": "^9.4.0",
"dayjs": "^1.11.2",
Expand Down Expand Up @@ -42,6 +43,7 @@
"@nrwl/linter": "14.0.3",
"@nrwl/workspace": "14.0.3",
"@types/bcryptjs": "^2.4.2",
"@types/bytes": "^3.1.1",
"@types/from2": "^2.3.1",
"@types/glob": "^7.2.0",
"@types/inquirer": "^8.0.0",
Expand Down
51 changes: 25 additions & 26 deletions packages/core/src/lib/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export enum LoggingScope {
CORE = 'CORE',
BUILD = 'BUILD',
SERVE = 'SERVE',
AUDIT = 'AUDIT',
ACCESS_LOG = 'ACCESS_LOG',
}

type LoggingScopeTypes = keyof typeof LoggingScope;
Expand All @@ -21,32 +21,21 @@ export enum LoggingLevel {
FATAL = 'fatal',
}

type DisplayFilePathTypes = 'hidden' | 'displayAll' | 'hideNodeModulesOnly';

export interface LoggerOptions {
level?: LoggingLevel;
displayRequestId?: boolean;
displayFunctionName?: boolean;
displayFilePath?: DisplayFilePathTypes;
}

type LoggerMapConfig = {
[scope in LoggingScope]: LoggerOptions;
};

const defaultMapConfig: LoggerMapConfig = {
[LoggingScope.CORE]: {
level: LoggingLevel.DEBUG,
displayRequestId: false,
},
[LoggingScope.BUILD]: {
level: LoggingLevel.DEBUG,
displayRequestId: false,
},
[LoggingScope.SERVE]: {
level: LoggingLevel.DEBUG,
displayRequestId: false,
},
[LoggingScope.AUDIT]: {
level: LoggingLevel.DEBUG,
displayRequestId: false,
},
// The default logger options
const defaultLoggerOptions: LoggerOptions = {
level: LoggingLevel.DEBUG,
displayRequestId: false,
displayFilePath: 'hidden',
displayFunctionName: false,
};

export type AsyncRequestIdStorage = AsyncLocalStorage<{ requestId: string }>;
Expand All @@ -58,9 +47,11 @@ class LoggerFactory {
this.asyncReqIdStorage = new AsyncLocalStorage();

this.loggerMap = {
// Here, create default scope logger, we could add other package or extension logger name in here
[LoggingScope.CORE]: this.createLogger(LoggingScope.CORE),
[LoggingScope.BUILD]: this.createLogger(LoggingScope.BUILD),
[LoggingScope.SERVE]: this.createLogger(LoggingScope.SERVE),
[LoggingScope.ACCESS_LOG]: this.createLogger(LoggingScope.ACCESS_LOG),
};
}

Expand All @@ -84,7 +75,7 @@ class LoggerFactory {
return logger;
}
// if scope name does not exist in map or exist but would like to update config
const newLogger = this.createLogger(scopeName as LoggingScope, options);
const newLogger = this.createLogger(scopeName, options);
this.loggerMap[scopeName] = newLogger;
return newLogger;
}
Expand All @@ -95,17 +86,25 @@ class LoggerFactory {
minLevel: options.level || prevSettings.minLevel,
displayRequestId:
options.displayRequestId || prevSettings.displayRequestId,
displayFunctionName:
options.displayFunctionName || prevSettings.displayFunctionName,
displayFilePath: options.displayFilePath || prevSettings.displayFilePath,
});
}

private createLogger(name: LoggingScope, options?: LoggerOptions) {
private createLogger(name: string, options?: LoggerOptions) {
return new Logger({
name,
minLevel: options?.level || defaultMapConfig[name].level,
minLevel: options?.level || defaultLoggerOptions.level,
// use function call for requestId, then when logger get requestId, it will get newest store again
requestId: () => this.asyncReqIdStorage.getStore()?.requestId as string,
displayRequestId:
options?.displayRequestId || defaultMapConfig[name].displayRequestId,
options?.displayRequestId || defaultLoggerOptions.displayRequestId,
displayFunctionName:
options?.displayFunctionName ||
defaultLoggerOptions.displayFunctionName,
displayFilePath:
options?.displayFilePath || defaultLoggerOptions.displayFilePath,
});
}
}
Expand Down
33 changes: 33 additions & 0 deletions packages/serve/src/lib/middleware/accessLogMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
getLogger,
LoggerOptions,
VulcanInternalExtension,
} from '@vulcan-sql/core';
import * as bytes from 'bytes';
import { BuiltInMiddleware, KoaContext, Next } from '@vulcan-sql/serve/models';

@VulcanInternalExtension('access-log')
export class AccessLogMiddleware extends BuiltInMiddleware<LoggerOptions> {
private logger = getLogger({
scopeName: 'ACCESS_LOG',
options: this.getOptions(),
});

public async handle(context: KoaContext, next: Next) {
if (!this.enabled) return next();

const { request: req, response: resp, params } = context;

const reqSize = req.length ? bytes(req.length).toLowerCase() : 'none';
const respSize = resp.length ? bytes(resp.length).toLowerCase() : 'none';
Comment on lines +21 to +22
Copy link
Contributor

@oscar60310 oscar60310 Sep 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to print request/response size, but I can't get both of them in lab env, how do we get them to work?

Should we calculate the response size after other middlewares (after next() function)? Response data might be set after them.

Copy link
Contributor Author

@kokokuo kokokuo Sep 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @oscar60310, thanks for reviewing and suggesting, I have checked the lab env both, and like you said the request/response size (Content-Length) shows an undefined value ( Also includes integrating-testing and my app.spec test cases in serve package.

So I searching for the reason, and I the request header only have Content-Length when HTTP method is POST or PUT, because Content-Length only calculates payload data size, please check the references below and my test by sample with typescript-koa-starter to add test api:

POST request and return JSON format

POST-JSON

GET request and return JSON format

GET-JSON

For the response header, as we know, because we use the stream and the header will shows Transfer-Encoding: chunck, so Content-Length won't work.

GET request and return Stream format

GET-STREAM

POST request and return Stream format

POST-STREAM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I still keep the request and response size, because maybe we will have POST / PUT API in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the survey!

this.logger.info(
`--> ${req.ip} -- "${req.method} ${req.path}" -- size: ${reqSize}`
);
this.logger.info(` -> header: ${JSON.stringify(req.header)}`);
this.logger.info(` -> query: ${JSON.stringify(req.query)}`);
this.logger.info(` -> params: ${JSON.stringify(params)}`);
await next();
this.logger.info(`<-- status: ${resp.status} -- size: ${respSize}`);
this.logger.info(` <- header: ${JSON.stringify(resp.header)}`);
}
}
31 changes: 0 additions & 31 deletions packages/serve/src/lib/middleware/auditLogMiddleware.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/serve/src/lib/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './corsMiddleware';
export * from './requestIdMiddleware';
export * from './auditLogMiddleware';
export * from './accessLogMiddleware';
export * from './rateLimitMiddleware';
export * from './authMiddleware';
export * from './response-format';
Expand All @@ -11,18 +11,18 @@ import { CorsMiddleware } from './corsMiddleware';
import { AuthMiddleware } from './authMiddleware';
import { RateLimitMiddleware } from './rateLimitMiddleware';
import { RequestIdMiddleware } from './requestIdMiddleware';
import { AuditLoggingMiddleware } from './auditLogMiddleware';
import { AccessLogMiddleware } from './accessLogMiddleware';
import { ResponseFormatMiddleware } from './response-format';
import { EnforceHttpsMiddleware } from './enforceHttpsMiddleware';
import { ClassType, ExtensionBase } from '@vulcan-sql/core';
import { DocRouterMiddleware } from './docRouterMiddleware';

// The order is the middleware running order
export const BuiltInRouteMiddlewares: ClassType<ExtensionBase>[] = [
AccessLogMiddleware,
CorsMiddleware,
EnforceHttpsMiddleware,
RequestIdMiddleware,
AuditLoggingMiddleware,
RateLimitMiddleware,
AuthMiddleware,
ResponseFormatMiddleware,
Expand Down
17 changes: 14 additions & 3 deletions packages/serve/src/lib/middleware/rateLimitMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { RateLimit, RateLimitOptions } from 'koa2-ratelimit';
import { BuiltInMiddleware, KoaContext, Next } from '@vulcan-sql/serve/models';
import { VulcanInternalExtension } from '@vulcan-sql/core';
import { VulcanInternalExtension, TYPES as CORE_TYPES } from '@vulcan-sql/core';
import { inject } from 'inversify';

export { RateLimitOptions };

@VulcanInternalExtension('rate-limit')
export class RateLimitMiddleware extends BuiltInMiddleware<RateLimitOptions> {
private koaRateLimit = RateLimit.middleware(this.getOptions());
private options: RateLimitOptions;
private koaRateLimitFunc;
constructor(
@inject(CORE_TYPES.ExtensionConfig) config: any,
@inject(CORE_TYPES.ExtensionName) name: string
) {
super(config, name);
this.options = (this.getOptions() as RateLimitOptions) || { max: 60 };
if (!this.options['max']) this.options['max'] = 60;
this.koaRateLimitFunc = RateLimit.middleware(this.options);
}

public async handle(context: KoaContext, next: Next) {
if (!this.enabled) return next();
return this.koaRateLimit(context, next);
return this.koaRateLimitFunc(context, next);
}
}
3 changes: 2 additions & 1 deletion packages/serve/test/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ describe('Test vulcan server for calling restful APIs', () => {

// Assert
expect(response.body.data).toEqual(expected);
}
},
10000
);
});
Loading