Skip to content

Commit c01fba1

Browse files
committed
add activity log middleware and align response statue setting
1 parent a874313 commit c01fba1

File tree

10 files changed

+286
-28
lines changed

10 files changed

+286
-28
lines changed

packages/core/src/lib/cache-layer/cacheLayerRefresher.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import ms, { StringValue } from 'ms';
22
import { uniq } from 'lodash';
33
import { ToadScheduler, SimpleIntervalJob, AsyncTask } from 'toad-scheduler';
4-
import { inject, injectable } from 'inversify';
4+
import { inject, injectable, multiInject } from 'inversify';
55
import { TYPES } from '@vulcan-sql/core/types';
66
import { APISchema, IActivityLogger } from '@vulcan-sql/core/models';
77
import { ConfigurationError } from '../utils/errors';
@@ -28,15 +28,16 @@ export interface ICacheLayerRefresher {
2828
export class CacheLayerRefresher implements ICacheLayerRefresher {
2929
private cacheLoader: ICacheLayerLoader;
3030
private scheduler = new ToadScheduler();
31-
private activityLogger: IActivityLogger;
31+
private activityLoggers: IActivityLogger[];
3232
private logger = getLogger({ scopeName: 'CORE' });
3333

3434
constructor(
3535
@inject(TYPES.CacheLayerLoader) loader: ICacheLayerLoader,
36-
@inject(TYPES.Extension_ActivityLogger) activityLogger: IActivityLogger
36+
@multiInject(TYPES.Extension_ActivityLogger)
37+
activityLoggers: IActivityLogger[]
3738
) {
3839
this.cacheLoader = loader;
39-
this.activityLogger = activityLogger;
40+
this.activityLoggers = activityLoggers;
4041
}
4142

4243
public async start(
@@ -48,6 +49,7 @@ export class CacheLayerRefresher implements ICacheLayerRefresher {
4849
// check if the index name is duplicated more than one API schemas
4950
this.checkDuplicateIndex(schemas);
5051
// traverse each cache table of each schema
52+
const activityLogger = this.getActivityLogger();
5153
await Promise.all(
5254
schemas.map(async (schema) => {
5355
// skip the schema by return if not set the cache
@@ -83,11 +85,12 @@ export class CacheLayerRefresher implements ICacheLayerRefresher {
8385
sql,
8486
refreshResult,
8587
};
86-
await this.activityLogger.log(content).catch((err: any) => {
87-
this.logger.debug(
88-
`Failed to log activity after refreshing cache: ${err}`
89-
);
90-
});
88+
if (activityLogger)
89+
activityLogger.log(content).catch((err: any) => {
90+
this.logger.debug(
91+
`Failed to log activity after refreshing cache: ${err}`
92+
);
93+
});
9194
}
9295
}),
9396
{ preventOverrun: true, id: workerId }
@@ -111,11 +114,12 @@ export class CacheLayerRefresher implements ICacheLayerRefresher {
111114
sql,
112115
refreshResult,
113116
};
114-
await this.activityLogger.log(content).catch((err: any) => {
115-
this.logger.debug(
116-
`Failed to log activity after refreshing cache: ${err}`
117-
);
118-
});
117+
if (activityLogger)
118+
activityLogger.log(content).catch((err: any) => {
119+
this.logger.debug(
120+
`Failed to log activity after refreshing cache: ${err}`
121+
);
122+
});
119123
}
120124
}
121125
})
@@ -131,6 +135,14 @@ export class CacheLayerRefresher implements ICacheLayerRefresher {
131135
this.scheduler.stop();
132136
}
133137

138+
private getActivityLogger(): IActivityLogger | undefined {
139+
const activityLogger = this.activityLoggers.find((logger) =>
140+
logger.isEnabled()
141+
);
142+
143+
return activityLogger;
144+
}
145+
134146
private checkDuplicateCacheTableName(schemas: APISchema[]) {
135147
const tableNames = schemas
136148
// => [[table1, table2], [table1, table3], [table4]]

packages/core/src/lib/template-engine/compiler-environment/base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as nunjucks from 'nunjucks';
77
export abstract class BaseCompilerEnvironment extends nunjucks.Environment {
88
abstract getExtensions(): ExtensionBase[];
99

10+
// initialize template engines extensions
1011
public async initializeExtensions() {
1112
const extensions = this.getExtensions();
1213
for (const extension of extensions) {

packages/core/src/models/extensions/logger.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export enum ActivityLoggerType {
77
}
88

99
export interface IActivityLogger {
10+
isEnabled(): boolean;
1011
log(content: any): Promise<void>;
1112
}
1213

@@ -17,7 +18,7 @@ export abstract class BaseActivityLogger<ActivityLoggerTypeOption>
1718
{
1819
public abstract log(context: any): Promise<void>;
1920

20-
protected isEnabled(): boolean {
21+
public isEnabled(): boolean {
2122
const config = this.getConfig();
2223
if (!config) return false;
2324
if (config.enabled === true) return true;

packages/core/test/cache-layer/cacheLayerRefresher.spec.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jest.mock('../../src/lib/loggers/httpLogger', () => {
2525
...originalModule,
2626
HttpLogger: jest.fn().mockImplementation(() => {
2727
return {
28+
isEnabled: jest.fn().mockReturnValue(true),
2829
log: jest.fn().mockResolvedValue(true), // Spy on the add method
2930
};
3031
}),
@@ -122,7 +123,7 @@ describe('Test cache layer refresher', () => {
122123
] as Array<CacheLayerInfo>,
123124
},
124125
];
125-
const refresher = new CacheLayerRefresher(stubCacheLoader, mockLogger);
126+
const refresher = new CacheLayerRefresher(stubCacheLoader, [mockLogger]);
126127

127128
// Act, Assert
128129
await expect(() => refresher.start(schemas)).rejects.toThrow(
@@ -173,7 +174,7 @@ describe('Test cache layer refresher', () => {
173174
] as Array<CacheLayerInfo>,
174175
},
175176
];
176-
const refresher = new CacheLayerRefresher(stubCacheLoader, mockLogger);
177+
const refresher = new CacheLayerRefresher(stubCacheLoader, [mockLogger]);
177178

178179
// Act, Assert
179180
await expect(() => refresher.start(schemas)).rejects.toThrow(
@@ -219,7 +220,7 @@ describe('Test cache layer refresher', () => {
219220
];
220221
// Act
221222
const loader = new CacheLayerLoader(options, stubFactory as any);
222-
const refresher = new CacheLayerRefresher(loader, mockLogger);
223+
const refresher = new CacheLayerRefresher(loader, [mockLogger]);
223224
await refresher.start(schemas);
224225

225226
// Assert
@@ -295,7 +296,7 @@ describe('Test cache layer refresher', () => {
295296

296297
// Stub the load method to not do any thing.
297298
stubCacheLoader.load.resolves();
298-
const refresher = new CacheLayerRefresher(stubCacheLoader, mockLogger);
299+
const refresher = new CacheLayerRefresher(stubCacheLoader, [mockLogger]);
299300
// Act
300301
await refresher.start(schemas);
301302

@@ -366,7 +367,7 @@ describe('Test cache layer refresher', () => {
366367
];
367368
// Act
368369
const loader = new CacheLayerLoader(options, stubFactory as any);
369-
const refresher = new CacheLayerRefresher(loader, mockLogger);
370+
const refresher = new CacheLayerRefresher(loader, [mockLogger]);
370371
await refresher.start(schemas);
371372

372373
// Assert
@@ -413,7 +414,7 @@ describe('Test cache layer refresher', () => {
413414
// Act
414415
const loader = new CacheLayerLoader(options, stubFactory as any);
415416
stubCacheLoader.load.throws();
416-
const refresher = new CacheLayerRefresher(loader, mockLogger);
417+
const refresher = new CacheLayerRefresher(loader, [mockLogger]);
417418
await refresher.start(schemas);
418419

419420
// Assert

packages/extension-authenticator-canner/src/lib/authenticator/pat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class CannerPATAuthenticator extends BaseAuthenticator<CannerPATOptions>
7676
operationName: 'UserMe',
7777
variables: {},
7878
query:
79-
'query UserMe{userMe {accountRole attributes createdAt email groups {id name} lastName firstName username}}',
79+
'query UserMe{userMe {id accountRole attributes createdAt email groups {id name} lastName firstName username}}',
8080
},
8181
{
8282
headers: {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {
2+
TYPES as CORE_TYPES,
3+
BaseActivityLogger,
4+
VulcanInternalExtension,
5+
IActivityLoggerOptions,
6+
getLogger,
7+
} from '@vulcan-sql/core';
8+
import { Next, KoaContext, BuiltInMiddleware } from '@vulcan-sql/serve/models';
9+
import { inject, multiInject } from 'inversify';
10+
import moment = require('moment');
11+
12+
const logger = getLogger({ scopeName: 'SERVE' });
13+
14+
@VulcanInternalExtension('activity-log')
15+
export class ActivityLogMiddleware extends BuiltInMiddleware<IActivityLoggerOptions> {
16+
private activityLoggers: BaseActivityLogger<any>[];
17+
private activityLoggerMap: Record<string, BaseActivityLogger<any>> = {};
18+
constructor(
19+
@inject(CORE_TYPES.ExtensionConfig) config: any,
20+
@inject(CORE_TYPES.ExtensionName) name: string,
21+
@multiInject(CORE_TYPES.Extension_ActivityLogger)
22+
activityLoggers: BaseActivityLogger<any>[]
23+
) {
24+
super(config, name);
25+
this.activityLoggers = activityLoggers;
26+
}
27+
public override async onActivate(): Promise<void> {
28+
for (const logger of this.activityLoggers) {
29+
if (logger.isEnabled()) {
30+
const id = logger.getExtensionId();
31+
this.activityLoggerMap[id!] = logger;
32+
}
33+
}
34+
}
35+
public async handle(context: KoaContext, next: Next) {
36+
if (!this.enabled) return next();
37+
const logTime = moment.utc().format('YYYY-MM-DD HH:mm:ss');
38+
const startTime = Date.now();
39+
await next();
40+
const endTime = Date.now();
41+
const duration = endTime - startTime;
42+
const body = context.response.body as any;
43+
const error = body?.message;
44+
const user = context.state.user;
45+
for (const activityLogger of Object.values(this.activityLoggerMap)) {
46+
const activityLog = {
47+
logTime,
48+
duration,
49+
method: context.request.method,
50+
url: context.request.originalUrl,
51+
ip: context.request.ip,
52+
header: context.request.header,
53+
params: context.params,
54+
query: context.request.query,
55+
status: context.response.status,
56+
error,
57+
user,
58+
};
59+
activityLogger.log(activityLog).catch((e) => {
60+
logger.debug(`Error when logging activity: ${e}`);
61+
});
62+
}
63+
}
64+
}

packages/serve/src/lib/middleware/auth/authCredentialsMiddleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class AuthCredentialsMiddleware extends BaseAuthMiddleware {
6666
if (result.status === AuthStatus.INDETERMINATE) continue;
6767
// if state is failed, return directly
6868
if (result.status === AuthStatus.FAIL) {
69-
context.status = 401;
69+
context.response.status = 401;
7070
context.body = {
7171
type: result.type,
7272
message: result.message || 'verify token failed',

packages/serve/src/lib/middleware/auth/authRouterMiddleware.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class AuthRouterMiddleware extends BaseAuthMiddleware {
4040
private mountTokenEndpoint() {
4141
this.router.post(`/auth/token`, async (context: KoaContext) => {
4242
if (isEmpty(context.request.body)) {
43-
context.status = 400;
43+
context.response.status = 400;
4444
context.body = { message: 'Please provide request parameters.' };
4545
return;
4646
}
@@ -50,7 +50,7 @@ export class AuthRouterMiddleware extends BaseAuthMiddleware {
5050
const msg = `Please provide auth "type", supported types: ${Object.keys(
5151
this.options
5252
)}.`;
53-
context.status = 400;
53+
context.response.status = 400;
5454
context.body = { message: msg };
5555
return;
5656
}
@@ -59,7 +59,7 @@ export class AuthRouterMiddleware extends BaseAuthMiddleware {
5959
const msg = `auth type "${type}" does not support, only supported: ${Object.keys(
6060
this.options
6161
)}.`;
62-
context.status = 400;
62+
context.response.status = 400;
6363
context.body = { message: msg };
6464
return;
6565
}
@@ -69,7 +69,7 @@ export class AuthRouterMiddleware extends BaseAuthMiddleware {
6969
context.body = result;
7070
return;
7171
} catch (err) {
72-
context.status = 400;
72+
context.response.status = 400;
7373
context.body = {
7474
message: (err as Error).message,
7575
};
@@ -81,7 +81,7 @@ export class AuthRouterMiddleware extends BaseAuthMiddleware {
8181
// The route should work after the token authenticated
8282
this.router.get(`/auth/user-profile`, async (context: KoaContext) => {
8383
if (!context.state.user) {
84-
context.status = 404;
84+
context.response.status = 404;
8585
context.body = {
8686
message: 'User profile not found.',
8787
};

packages/serve/src/lib/middleware/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import { ClassType, ExtensionBase } from '@vulcan-sql/core';
2323
import { DocRouterMiddleware } from './docRouterMiddleware';
2424
import { ErrorHandlerMiddleware } from './errorHandlerMIddleware';
2525
import { CatalogRouterMiddleware } from './catalogRouterMiddleware';
26+
import { ActivityLogMiddleware } from './activityLogMiddleware';
2627

2728
// The array is the middleware running order
2829
export const BuiltInRouteMiddlewares: ClassType<ExtensionBase>[] = [
2930
RequestIdMiddleware,
31+
ActivityLogMiddleware,
3032
ErrorHandlerMiddleware,
3133
AccessLogMiddleware,
3234
CorsMiddleware,

0 commit comments

Comments
 (0)