From 4bf24ab16ee105b21ff051c5771375345c28e495 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 30 Dec 2023 06:40:36 +0800 Subject: [PATCH 01/11] feat: add get all client interface for service factory (#3513) * chore(deps): update dependency ts-node to v10.9.2 * chore(deps): update dependency mm to v3.4.0 * chore(deps): update dependency @types/node to v18.19.3 * docs: update validate.md (#3490) Missing Body Reference in Instance Code * fix(deps): update bull monorepo to v5.10.2 (#3486) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update dependency cos-nodejs-sdk-v5 to v2.12.6 (#3487) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency sequelize to v6.35.2 (#3493) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix: add regenerate method for session security (#3499) * docs: sync validate.md * v3.13.6 * chore(deps): update dependency @opentelemetry/sdk-node to v0.46.0 * fix(deps): update dependency reflect-metadata to v0.2.1 (#3502) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update dependency leoric to v2.12.2 (#3501) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * v3.13.7 * chore(deps): update mikro-orm monorepo to v5.9.5 * chore: add get client interface for service factory --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: tommy.hu <13776119594@163.com> --- packages/core/src/common/loggerFactory.ts | 12 +++++++++++- packages/core/src/common/serviceFactory.ts | 8 ++++++++ packages/core/src/interface.ts | 2 ++ packages/core/src/service/loggerService.ts | 8 ++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/core/src/common/loggerFactory.ts b/packages/core/src/common/loggerFactory.ts index eb1943859bdb..ad19fc4b2abe 100644 --- a/packages/core/src/common/loggerFactory.ts +++ b/packages/core/src/common/loggerFactory.ts @@ -23,11 +23,13 @@ export abstract class LoggerFactory { export class DefaultConsoleLoggerFactory implements LoggerFactory { + private instance: Map = new Map(); createLogger(name: string, options: any): ILogger { + this.instance.set(name, console); return console; } getLogger(loggerName: string): ILogger { - return console; + return this.instance.get(loggerName); } close(loggerName?: string) {} removeLogger(loggerName: string) {} @@ -49,4 +51,12 @@ export class DefaultConsoleLoggerFactory createContextLogger(ctx: any, appLogger: ILogger): ILogger { return appLogger; } + + public getClients() { + return this.instance; + } + + public getClientKeys() { + return Array.from(this.instance.keys()); + } } diff --git a/packages/core/src/common/serviceFactory.ts b/packages/core/src/common/serviceFactory.ts index 9b4e691ee244..5bc76ef581f7 100644 --- a/packages/core/src/common/serviceFactory.ts +++ b/packages/core/src/common/serviceFactory.ts @@ -62,4 +62,12 @@ export abstract class ServiceFactory implements IServiceFactory { public getDefaultClientName(): string { return this.options['defaultClientName']; } + + public getClients() { + return this.clients; + } + + public getClientKeys() { + return Array.from(this.clients.keys()); + } } diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 0f27c6c08ad4..308b3e02e96a 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -1166,6 +1166,8 @@ export interface IServiceFactory { getName(): string; stop(): Promise; getDefaultClientName(): string; + getClients(): Map; + getClientKeys(): string[]; } export interface ISimulation { diff --git a/packages/core/src/service/loggerService.ts b/packages/core/src/service/loggerService.ts index 4be278996345..1f03e2335458 100644 --- a/packages/core/src/service/loggerService.ts +++ b/packages/core/src/service/loggerService.ts @@ -121,4 +121,12 @@ export class MidwayLoggerService extends ServiceFactory { contextOptions ); } + + public getClients() { + return this.clients; + } + + public getClientKeys() { + return Array.from(this.clients.keys()); + } } From f0b78bd1865a5b878e59abccdb64ebb566c36f26 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 30 Dec 2023 06:48:34 +0800 Subject: [PATCH 02/11] feat: add redis health check (#3473) * feat: add redis health check * fix: lint --- packages/core/src/common/serviceFactory.ts | 4 +++ packages/redis/src/configuration.ts | 32 +++++++++++++++++++--- packages/redis/src/manager.ts | 3 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/core/src/common/serviceFactory.ts b/packages/core/src/common/serviceFactory.ts index 5bc76ef581f7..4c785c188e02 100644 --- a/packages/core/src/common/serviceFactory.ts +++ b/packages/core/src/common/serviceFactory.ts @@ -46,6 +46,10 @@ export abstract class ServiceFactory implements IServiceFactory { } } + public getClients(): Map { + return this.clients; + } + public abstract getName(): string; protected abstract createClient( config, diff --git a/packages/redis/src/configuration.ts b/packages/redis/src/configuration.ts index a5a09c6b8fb3..0bc8761f72f0 100644 --- a/packages/redis/src/configuration.ts +++ b/packages/redis/src/configuration.ts @@ -1,4 +1,9 @@ -import { Configuration } from '@midwayjs/core'; +import { + Configuration, + HealthResult, + ILifeCycle, + IMidwayContainer, +} from '@midwayjs/core'; import { RedisServiceFactory } from './manager'; @Configuration({ @@ -11,13 +16,32 @@ import { RedisServiceFactory } from './manager'; }, ], }) -export class RedisConfiguration { - async onReady(container) { +export class RedisConfiguration implements ILifeCycle { + async onReady(container: IMidwayContainer) { await container.getAsync(RedisServiceFactory); } - async onStop(container): Promise { + async onStop(container: IMidwayContainer): Promise { const factory = await container.getAsync(RedisServiceFactory); await factory.stop(); } + + async onHealthCheck(container: IMidwayContainer): Promise { + const factory = await container.getAsync(RedisServiceFactory); + const clients = factory.getClients(); + + // find status not ready + let clientName: any; + for (const [name, instance] of clients) { + if (instance.status !== 'ready') { + clientName = name; + break; + } + } + + return { + status: !clientName, + reason: clientName ? `redis client "${clientName}" is not ready` : '', + }; + } } diff --git a/packages/redis/src/manager.ts b/packages/redis/src/manager.ts index 52927da5daa2..7e1d35a024ec 100644 --- a/packages/redis/src/manager.ts +++ b/packages/redis/src/manager.ts @@ -11,6 +11,7 @@ import { ScopeEnum, ServiceFactory, ServiceFactoryConfigOption, + ILogger, } from '@midwayjs/core'; import Redis from 'ioredis'; import * as assert from 'assert'; @@ -28,7 +29,7 @@ export class RedisServiceFactory extends ServiceFactory { } @Logger('coreLogger') - logger; + logger: ILogger; async createClient(config): Promise { let client; From 5e746ff19b0155a29d9ef4f5a89291bfdf36e8dd Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 30 Dec 2023 16:08:16 +0800 Subject: [PATCH 03/11] feat: support client priority (#3526) --- packages/core/src/common/serviceFactory.ts | 35 ++++- packages/core/src/interface.ts | 10 ++ .../core/test/common/serviceFactory.test.ts | 61 ++++++++- packages/redis/src/configuration.ts | 2 +- packages/redis/src/index.ts | 3 + packages/redis/src/manager.ts | 10 +- packages/redis/test/index.test.ts | 122 ++++++++++++++++-- 7 files changed, 218 insertions(+), 25 deletions(-) diff --git a/packages/core/src/common/serviceFactory.ts b/packages/core/src/common/serviceFactory.ts index 4c785c188e02..e8948df16446 100644 --- a/packages/core/src/common/serviceFactory.ts +++ b/packages/core/src/common/serviceFactory.ts @@ -1,11 +1,19 @@ import { extend } from '../util/extend'; import { IServiceFactory } from '../interface'; +export const DEFAULT_PRIORITY = { + L1: 'High', + L2: 'Medium', + L3: 'Low', +}; + /** * 多客户端工厂实现 */ export abstract class ServiceFactory implements IServiceFactory { protected clients: Map = new Map(); + protected priority; + protected options = {}; protected async initClients(options: any = {}): Promise { @@ -24,6 +32,13 @@ export abstract class ServiceFactory implements IServiceFactory { await this.createInstance(options.clients[id], id); } } + + // set priority + this.priority = options.priority || { + ...Array.from(this.clients.keys()).map(name => ({ + [name]: DEFAULT_PRIORITY.L2, + })), + }; } public get(id = 'default'): U { @@ -46,10 +61,6 @@ export abstract class ServiceFactory implements IServiceFactory { } } - public getClients(): Map { - return this.clients; - } - public abstract getName(): string; protected abstract createClient( config, @@ -74,4 +85,20 @@ export abstract class ServiceFactory implements IServiceFactory { public getClientKeys() { return Array.from(this.clients.keys()); } + + public getClientPriority(clientName: string) { + return this.priority[clientName] || DEFAULT_PRIORITY.L2; + } + + public isHighPriority(clientName: string) { + return this.getClientPriority(clientName) === DEFAULT_PRIORITY.L1; + } + + public isMediumPriority(clientName: string) { + return this.getClientPriority(clientName) === DEFAULT_PRIORITY.L2; + } + + public isLowPriority(clientName: string) { + return this.getClientPriority(clientName) === DEFAULT_PRIORITY.L3; + } } diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 308b3e02e96a..4b992d66160c 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -459,6 +459,9 @@ export interface MidwayCoreDefaultConfig { } } + +export type ClientPriorityValue = 'High' | 'Medium' | 'Low'; + export type ServiceFactoryConfigOption = { default?: PowerPartial; client?: PowerPartial; @@ -466,6 +469,9 @@ export type ServiceFactoryConfigOption = { [key: string]: PowerPartial; }; defaultClientName?: string; + clientPriority?: { + [key: string]: ClientPriorityValue; + } }; export type CreateDataSourceInstanceOptions = { @@ -1168,6 +1174,10 @@ export interface IServiceFactory { getDefaultClientName(): string; getClients(): Map; getClientKeys(): string[]; + getClientPriority(clientName: string): ClientPriorityValue; + isHighPriority(clientName: string): boolean; + isMediumPriority(clientName: string): boolean; + isLowPriority(clientName: string) : boolean; } export interface ISimulation { diff --git a/packages/core/test/common/serviceFactory.test.ts b/packages/core/test/common/serviceFactory.test.ts index 20c40bdf7cd3..44a0559e2ee5 100644 --- a/packages/core/test/common/serviceFactory.test.ts +++ b/packages/core/test/common/serviceFactory.test.ts @@ -1,4 +1,4 @@ -import { ServiceFactory } from '../../src'; +import { DEFAULT_PRIORITY, ServiceFactory } from '../../src'; describe('test/common/serviceFactory.test.ts', () => { @@ -76,4 +76,63 @@ describe('test/common/serviceFactory.test.ts', () => { }) expect(instance.getDefaultClientName()).toEqual('abc'); }); + + describe('Priority related tests', () => { + let instance; + + beforeEach(async () => { + instance = new TestServiceFactory(); + await instance.initClients({ + clients: { + high: {}, + medium: {}, + low: {} + }, + priority: { + high: DEFAULT_PRIORITY.L1, + medium: DEFAULT_PRIORITY.L2, + low: DEFAULT_PRIORITY.L3 + } + }); + }); + + it('should get correct client priority', () => { + expect(instance.getClientPriority('high')).toEqual(DEFAULT_PRIORITY.L1); + expect(instance.getClientPriority('medium')).toEqual(DEFAULT_PRIORITY.L2); + expect(instance.getClientPriority('low')).toEqual(DEFAULT_PRIORITY.L3); + }); + + it('should correctly identify high priority client', () => { + expect(instance.isHighPriority('high')).toBeTruthy(); + expect(instance.isHighPriority('medium')).toBeFalsy(); + expect(instance.isHighPriority('low')).toBeFalsy(); + }); + + it('should correctly identify medium priority client', () => { + expect(instance.isMediumPriority('high')).toBeFalsy(); + expect(instance.isMediumPriority('medium')).toBeTruthy(); + expect(instance.isMediumPriority('low')).toBeFalsy(); + }); + + it('should correctly identify low priority client', () => { + expect(instance.isLowPriority('high')).toBeFalsy(); + expect(instance.isLowPriority('medium')).toBeFalsy(); + expect(instance.isLowPriority('low')).toBeTruthy(); + }); + + it('should use default priority if not set', async () => { + const instance = new TestServiceFactory(); + await instance.initClients({ + clients: { + defaultPriorityClient: {}, + }, + }); + + expect(instance.getClientPriority('defaultPriorityClient')).toEqual(DEFAULT_PRIORITY.L2); + expect(instance.isHighPriority('defaultPriorityClient')).toBeFalsy(); + expect(instance.isMediumPriority('defaultPriorityClient')).toBeTruthy(); + expect(instance.isLowPriority('defaultPriorityClient')).toBeFalsy(); + }); + }); + }); diff --git a/packages/redis/src/configuration.ts b/packages/redis/src/configuration.ts index 0bc8761f72f0..25d2544bbdfe 100644 --- a/packages/redis/src/configuration.ts +++ b/packages/redis/src/configuration.ts @@ -33,7 +33,7 @@ export class RedisConfiguration implements ILifeCycle { // find status not ready let clientName: any; for (const [name, instance] of clients) { - if (instance.status !== 'ready') { + if (instance.status !== 'ready' && !factory.isLowPriority(name)) { clientName = name; break; } diff --git a/packages/redis/src/index.ts b/packages/redis/src/index.ts index 8afc53a6e002..7adfa2a7c3b9 100644 --- a/packages/redis/src/index.ts +++ b/packages/redis/src/index.ts @@ -1,3 +1,6 @@ +import Redis from 'ioredis'; + export { RedisConfiguration as Configuration } from './configuration'; export * from './manager'; export * from './interface'; +export { Redis as IORedis }; diff --git a/packages/redis/src/manager.ts b/packages/redis/src/manager.ts index 7e1d35a024ec..fdbc6ab11ade 100644 --- a/packages/redis/src/manager.ts +++ b/packages/redis/src/manager.ts @@ -21,17 +21,17 @@ import { RedisConfigOptions } from './interface'; @Scope(ScopeEnum.Singleton) export class RedisServiceFactory extends ServiceFactory { @Config('redis') - redisConfig: ServiceFactoryConfigOption; + protected redisConfig: ServiceFactoryConfigOption; @Init() - async init() { + protected async init() { await this.initClients(this.redisConfig); } @Logger('coreLogger') - logger: ILogger; + protected logger: ILogger; - async createClient(config): Promise { + protected async createClient(config): Promise { let client; if (config.cluster === true) { @@ -94,7 +94,7 @@ export class RedisServiceFactory extends ServiceFactory { return 'redis'; } - async destroyClient(redisInstance) { + protected async destroyClient(redisInstance) { try { if (redisInstance) { const canQuit = !['end', 'close'].includes(redisInstance.status); diff --git a/packages/redis/test/index.test.ts b/packages/redis/test/index.test.ts index 8b4561bacd59..ee2450e8d965 100644 --- a/packages/redis/test/index.test.ts +++ b/packages/redis/test/index.test.ts @@ -1,12 +1,17 @@ import { join } from 'path'; import { RedisService, RedisServiceFactory } from '../src'; +import * as redis from '../src'; import { close, createLightApp } from '@midwayjs/mock'; +import { RedisConfiguration } from '../src/configuration'; describe('/test/index.test.ts', () => { - it('test single client', async () => { - const app = await createLightApp(join(__dirname, './fixtures/base-app-single-client')); - const redisService = await app.getApplicationContext().getAsync(RedisService); + const app = await createLightApp( + join(__dirname, './fixtures/base-app-single-client') + ); + const redisService = await app + .getApplicationContext() + .getAsync(RedisService); // test event redisService.on('connect', () => { console.log('redis connect'); @@ -18,8 +23,12 @@ describe('/test/index.test.ts', () => { }); it('should single client supportTimeCommand = false', async () => { - const app = await createLightApp(join(__dirname, './fixtures/base-app-supportTimeCommand-false')); - const redisService = await app.getApplicationContext().getAsync(RedisService); + const app = await createLightApp( + join(__dirname, './fixtures/base-app-supportTimeCommand-false') + ); + const redisService = await app + .getApplicationContext() + .getAsync(RedisService); await redisService.set('foo', 'bar'); const result = await redisService.get('foo'); expect(result).toEqual('bar'); @@ -27,8 +36,12 @@ describe('/test/index.test.ts', () => { }); it('should multi client', async () => { - const app = await createLightApp(join(__dirname, './fixtures/base-app-multi-client')); - const redisServiceFactory = await app.getApplicationContext().getAsync(RedisServiceFactory); + const app = await createLightApp( + join(__dirname, './fixtures/base-app-multi-client') + ); + const redisServiceFactory = await app + .getApplicationContext() + .getAsync(RedisServiceFactory); const redis = await redisServiceFactory.get('cache'); await redis.set('foo', 'bar'); const result = await redis.get('foo'); @@ -37,8 +50,12 @@ describe('/test/index.test.ts', () => { }); it.skip('should sentinel', async () => { - const app = await createLightApp(join(__dirname, './fixtures/base-app-sentinel')); - const redisService = await app.getApplicationContext().getAsync(RedisService); + const app = await createLightApp( + join(__dirname, './fixtures/base-app-sentinel') + ); + const redisService = await app + .getApplicationContext() + .getAsync(RedisService); await redisService.set('foo', 'bar'); const result = await redisService.get('foo'); expect(result).toEqual('bar'); @@ -46,8 +63,12 @@ describe('/test/index.test.ts', () => { }); it('support custom command defination', async () => { - const app = await createLightApp(join(__dirname, './fixtures/base-app-single-client')); - const redisService = await app.getApplicationContext().getAsync(RedisService); + const app = await createLightApp( + join(__dirname, './fixtures/base-app-single-client') + ); + const redisService = await app + .getApplicationContext() + .getAsync(RedisService); redisService.defineCommand('myecho', { numberOfKeys: 2, lua: 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', @@ -59,9 +80,9 @@ describe('/test/index.test.ts', () => { }); it('should fail when unable to connect redis', async () => { - await expect(createLightApp(join(__dirname, './fixtures/base-app-bad-client'))) - .rejects - .toThrow('connect ETIMEDOUT'); + await expect( + createLightApp(join(__dirname, './fixtures/base-app-bad-client')) + ).rejects.toThrow('connect ETIMEDOUT'); }); it('should throw error when instance not found', async () => { @@ -71,4 +92,77 @@ describe('/test/index.test.ts', () => { await service.init(); }).rejects.toThrowError(/instance not found/); }); + + it('should test health check', async () => { + const app = await createLightApp('', { + imports: [redis], + globalConfig: { + redis: { + clients: { + default: { + port: 6379, + host: '127.0.0.1', + }, + }, + }, + }, + }); + const redisServiceFactory = await app + .getApplicationContext() + .getAsync(RedisServiceFactory); + const client = redisServiceFactory.get('default'); + // light app 不执行 configuration 的 onReady + expect(client.status).toEqual('connect'); + const configuration = await app + .getApplicationContext() + .getAsync(RedisConfiguration); + const result = await configuration.onHealthCheck( + app.getApplicationContext() + ); + expect(result).toMatchInlineSnapshot(` + { + "reason": "redis client "default" is not ready", + "status": false, + } + `); + await close(app); + }); + + it('should test health check when client is low level', async () => { + const app = await createLightApp('', { + imports: [redis], + globalConfig: { + redis: { + clients: { + default: { + port: 6379, + host: '127.0.0.1', + }, + }, + priority: { + default: 'Low', + }, + }, + }, + }); + const redisServiceFactory = await app + .getApplicationContext() + .getAsync(RedisServiceFactory); + const client = redisServiceFactory.get('default'); + // light app 不执行 configuration 的 onReady + expect(client.status).toEqual('connect'); + const configuration = await app + .getApplicationContext() + .getAsync(RedisConfiguration); + const result = await configuration.onHealthCheck( + app.getApplicationContext() + ); + expect(result).toMatchInlineSnapshot(` + { + "reason": "", + "status": true, + } + `); + await close(app); + }); }); From 505635477b81786119cf3334aa741b2d18786bb0 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 30 Dec 2023 18:24:22 +0800 Subject: [PATCH 04/11] feat: new cache manager component (#3492) * fix(deps): update dependency ws to v8.16.0 (#3524) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update dependency axios to v1.6.3 (#3523) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: add cache code * feat: add cache-manager and cache-manager-redis * fix: store options * chore: upgrade cache-manager * chore: sync cache-manager * fix: typings * fix: test --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../cache/CHANGELOG.md | 0 {packages => packages-legacy}/cache/README.md | 0 .../cache/index.d.ts | 0 .../cache/jest.config.js | 0 .../cache/jest.setup.js | 0 .../cache/package.json | 2 +- .../cache/src/config/config.default.ts | 0 .../cache/src/configuration.ts | 0 .../cache/src/decorator/cacheKey.ts | 0 .../cache/src/index.ts | 0 .../cache/src/service/cache.ts | 0 .../cache/test/cache.test.ts | 0 .../test/fixtures/cache-manager/package.json | 0 .../cache-manager/src/configuration.ts | 0 .../cache-manager/src/service/user.service.ts | 0 .../cache/tsconfig.json | 0 .../cache/typedoc.json | 0 packages/cache-manager-redis/README.md | 12 + packages/cache-manager-redis/jest.config.js | 8 + packages/cache-manager-redis/jest.setup.js | 2 + packages/cache-manager-redis/package.json | 40 ++ packages/cache-manager-redis/src/index.ts | 12 + packages/cache-manager-redis/src/store.ts | 76 ++++ .../cache-manager-redis/test/index.test.ts | 91 ++++ packages/cache-manager-redis/tsconfig.json | 11 + packages/cache-manager-redis/typedoc.json | 4 + packages/cache-manager/CHANGELOG.md | 408 ++++++++++++++++++ packages/cache-manager/README.md | 12 + packages/cache-manager/index.d.ts | 9 + packages/cache-manager/jest.config.js | 8 + packages/cache-manager/jest.setup.js | 2 + packages/cache-manager/package.json | 40 ++ packages/cache-manager/src/configuration.ts | 65 +++ .../cache-manager/src/decorator/cacheKey.ts | 22 + packages/cache-manager/src/factory.ts | 190 ++++++++ packages/cache-manager/src/index.ts | 5 + packages/cache-manager/src/interface.ts | 35 ++ packages/cache-manager/test/factory.test.ts | 255 +++++++++++ .../fixtures/cache-decorator/package.json | 3 + .../cache-decorator/src/configuration.ts | 27 ++ .../src/service/user.service.ts | 20 + .../test/fixtures/cache-manager/package.json | 3 + .../cache-manager/src/configuration.ts | 27 ++ .../cache-manager/src/service/user.service.ts | 21 + packages/cache-manager/test/index.test.ts | 63 +++ packages/cache-manager/tsconfig.json | 11 + packages/cache-manager/typedoc.json | 4 + packages/redis/index.d.ts | 3 - packages/redis/src/index.ts | 2 +- 49 files changed, 1488 insertions(+), 5 deletions(-) rename {packages => packages-legacy}/cache/CHANGELOG.md (100%) rename {packages => packages-legacy}/cache/README.md (100%) rename {packages => packages-legacy}/cache/index.d.ts (100%) rename {packages => packages-legacy}/cache/jest.config.js (100%) rename {packages => packages-legacy}/cache/jest.setup.js (100%) rename {packages => packages-legacy}/cache/package.json (95%) rename {packages => packages-legacy}/cache/src/config/config.default.ts (100%) rename {packages => packages-legacy}/cache/src/configuration.ts (100%) rename {packages => packages-legacy}/cache/src/decorator/cacheKey.ts (100%) rename {packages => packages-legacy}/cache/src/index.ts (100%) rename {packages => packages-legacy}/cache/src/service/cache.ts (100%) rename {packages => packages-legacy}/cache/test/cache.test.ts (100%) rename {packages => packages-legacy}/cache/test/fixtures/cache-manager/package.json (100%) rename {packages => packages-legacy}/cache/test/fixtures/cache-manager/src/configuration.ts (100%) rename {packages => packages-legacy}/cache/test/fixtures/cache-manager/src/service/user.service.ts (100%) rename {packages => packages-legacy}/cache/tsconfig.json (100%) rename {packages => packages-legacy}/cache/typedoc.json (100%) create mode 100644 packages/cache-manager-redis/README.md create mode 100644 packages/cache-manager-redis/jest.config.js create mode 100644 packages/cache-manager-redis/jest.setup.js create mode 100644 packages/cache-manager-redis/package.json create mode 100644 packages/cache-manager-redis/src/index.ts create mode 100644 packages/cache-manager-redis/src/store.ts create mode 100644 packages/cache-manager-redis/test/index.test.ts create mode 100644 packages/cache-manager-redis/tsconfig.json create mode 100644 packages/cache-manager-redis/typedoc.json create mode 100644 packages/cache-manager/CHANGELOG.md create mode 100644 packages/cache-manager/README.md create mode 100644 packages/cache-manager/index.d.ts create mode 100644 packages/cache-manager/jest.config.js create mode 100644 packages/cache-manager/jest.setup.js create mode 100644 packages/cache-manager/package.json create mode 100644 packages/cache-manager/src/configuration.ts create mode 100644 packages/cache-manager/src/decorator/cacheKey.ts create mode 100644 packages/cache-manager/src/factory.ts create mode 100644 packages/cache-manager/src/index.ts create mode 100644 packages/cache-manager/src/interface.ts create mode 100644 packages/cache-manager/test/factory.test.ts create mode 100644 packages/cache-manager/test/fixtures/cache-decorator/package.json create mode 100644 packages/cache-manager/test/fixtures/cache-decorator/src/configuration.ts create mode 100644 packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts create mode 100644 packages/cache-manager/test/fixtures/cache-manager/package.json create mode 100644 packages/cache-manager/test/fixtures/cache-manager/src/configuration.ts create mode 100644 packages/cache-manager/test/fixtures/cache-manager/src/service/user.service.ts create mode 100644 packages/cache-manager/test/index.test.ts create mode 100644 packages/cache-manager/tsconfig.json create mode 100644 packages/cache-manager/typedoc.json diff --git a/packages/cache/CHANGELOG.md b/packages-legacy/cache/CHANGELOG.md similarity index 100% rename from packages/cache/CHANGELOG.md rename to packages-legacy/cache/CHANGELOG.md diff --git a/packages/cache/README.md b/packages-legacy/cache/README.md similarity index 100% rename from packages/cache/README.md rename to packages-legacy/cache/README.md diff --git a/packages/cache/index.d.ts b/packages-legacy/cache/index.d.ts similarity index 100% rename from packages/cache/index.d.ts rename to packages-legacy/cache/index.d.ts diff --git a/packages/cache/jest.config.js b/packages-legacy/cache/jest.config.js similarity index 100% rename from packages/cache/jest.config.js rename to packages-legacy/cache/jest.config.js diff --git a/packages/cache/jest.setup.js b/packages-legacy/cache/jest.setup.js similarity index 100% rename from packages/cache/jest.setup.js rename to packages-legacy/cache/jest.setup.js diff --git a/packages/cache/package.json b/packages-legacy/cache/package.json similarity index 95% rename from packages/cache/package.json rename to packages-legacy/cache/package.json index afb722a599e9..9eb66ca0c32c 100644 --- a/packages/cache/package.json +++ b/packages-legacy/cache/package.json @@ -4,6 +4,7 @@ "description": "midway cache manager", "main": "dist/index.js", "typings": "index.d.ts", + "private": true, "scripts": { "build": "tsc", "test": "node --require=ts-node/register ../../node_modules/.bin/jest --runInBand", @@ -32,7 +33,6 @@ "@midwayjs/mock": "^3.13.9" }, "dependencies": { - "@types/cache-manager": "3.4.3", "cache-manager": "3.6.3" } } diff --git a/packages/cache/src/config/config.default.ts b/packages-legacy/cache/src/config/config.default.ts similarity index 100% rename from packages/cache/src/config/config.default.ts rename to packages-legacy/cache/src/config/config.default.ts diff --git a/packages/cache/src/configuration.ts b/packages-legacy/cache/src/configuration.ts similarity index 100% rename from packages/cache/src/configuration.ts rename to packages-legacy/cache/src/configuration.ts diff --git a/packages/cache/src/decorator/cacheKey.ts b/packages-legacy/cache/src/decorator/cacheKey.ts similarity index 100% rename from packages/cache/src/decorator/cacheKey.ts rename to packages-legacy/cache/src/decorator/cacheKey.ts diff --git a/packages/cache/src/index.ts b/packages-legacy/cache/src/index.ts similarity index 100% rename from packages/cache/src/index.ts rename to packages-legacy/cache/src/index.ts diff --git a/packages/cache/src/service/cache.ts b/packages-legacy/cache/src/service/cache.ts similarity index 100% rename from packages/cache/src/service/cache.ts rename to packages-legacy/cache/src/service/cache.ts diff --git a/packages/cache/test/cache.test.ts b/packages-legacy/cache/test/cache.test.ts similarity index 100% rename from packages/cache/test/cache.test.ts rename to packages-legacy/cache/test/cache.test.ts diff --git a/packages/cache/test/fixtures/cache-manager/package.json b/packages-legacy/cache/test/fixtures/cache-manager/package.json similarity index 100% rename from packages/cache/test/fixtures/cache-manager/package.json rename to packages-legacy/cache/test/fixtures/cache-manager/package.json diff --git a/packages/cache/test/fixtures/cache-manager/src/configuration.ts b/packages-legacy/cache/test/fixtures/cache-manager/src/configuration.ts similarity index 100% rename from packages/cache/test/fixtures/cache-manager/src/configuration.ts rename to packages-legacy/cache/test/fixtures/cache-manager/src/configuration.ts diff --git a/packages/cache/test/fixtures/cache-manager/src/service/user.service.ts b/packages-legacy/cache/test/fixtures/cache-manager/src/service/user.service.ts similarity index 100% rename from packages/cache/test/fixtures/cache-manager/src/service/user.service.ts rename to packages-legacy/cache/test/fixtures/cache-manager/src/service/user.service.ts diff --git a/packages/cache/tsconfig.json b/packages-legacy/cache/tsconfig.json similarity index 100% rename from packages/cache/tsconfig.json rename to packages-legacy/cache/tsconfig.json diff --git a/packages/cache/typedoc.json b/packages-legacy/cache/typedoc.json similarity index 100% rename from packages/cache/typedoc.json rename to packages-legacy/cache/typedoc.json diff --git a/packages/cache-manager-redis/README.md b/packages/cache-manager-redis/README.md new file mode 100644 index 000000000000..fd2145aa921f --- /dev/null +++ b/packages/cache-manager-redis/README.md @@ -0,0 +1,12 @@ +# midway cache manager redis store + +[![Package Quality](http://npm.packagequality.com/shield/midway-core.svg)](http://packagequality.com/#?package=midway-core) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/midwayjs/midway/pulls) + +this is a sub package for midway. + +Document: [https://midwayjs.org](https://midwayjs.org) + +## License + +[MIT]((http://github.com/midwayjs/midway/blob/master/LICENSE)) diff --git a/packages/cache-manager-redis/jest.config.js b/packages/cache-manager-redis/jest.config.js new file mode 100644 index 000000000000..784df0fb05b0 --- /dev/null +++ b/packages/cache-manager-redis/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/test/fixtures'], + coveragePathIgnorePatterns: ['/test/', '/dist/'], + setupFilesAfterEnv: ['./jest.setup.js'], + coverageProvider: 'v8', +}; diff --git a/packages/cache-manager-redis/jest.setup.js b/packages/cache-manager-redis/jest.setup.js new file mode 100644 index 000000000000..53c7930592d0 --- /dev/null +++ b/packages/cache-manager-redis/jest.setup.js @@ -0,0 +1,2 @@ +process.env.MIDWAY_TS_MODE = 'true'; +jest.setTimeout(30000); diff --git a/packages/cache-manager-redis/package.json b/packages/cache-manager-redis/package.json new file mode 100644 index 000000000000..03a4d5c58b30 --- /dev/null +++ b/packages/cache-manager-redis/package.json @@ -0,0 +1,40 @@ +{ + "name": "@midwayjs/cache-manager-redis", + "version": "3.13.5", + "description": "midway redis store for cache manager", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "test": "node --require=ts-node/register ../../node_modules/.bin/jest --runInBand", + "cov": "node --require=ts-node/register ../../node_modules/.bin/jest --runInBand --coverage --forceExit" + }, + "author": "", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git@github.com:midwayjs/midway.git" + }, + "keywords": [ + "midway", + "cache", + "store", + "redis" + ], + "engines": { + "node": ">=12" + }, + "devDependencies": { + "@midwayjs/core": "^3.13.5", + "@midwayjs/cache-manager": "^3.13.5", + "@midwayjs/mock": "^3.13.5", + "cache-manager": "5.3.1" + }, + "dependencies": { + "@midwayjs/redis": "^3.13.5" + } +} diff --git a/packages/cache-manager-redis/src/index.ts b/packages/cache-manager-redis/src/index.ts new file mode 100644 index 000000000000..1d34607149a8 --- /dev/null +++ b/packages/cache-manager-redis/src/index.ts @@ -0,0 +1,12 @@ +import { IMidwayContainer } from '@midwayjs/core'; +import { RedisServiceFactory } from '@midwayjs/redis'; +import { Config } from 'cache-manager'; +import { createRedisStore } from './store'; + +export function createStore(instanceName: string) { + return async (options: Config, container: IMidwayContainer) => { + const redisServiceFactory = await container.getAsync(RedisServiceFactory); + const redisInstance = redisServiceFactory.get(instanceName); + return createRedisStore(redisInstance, options); + }; +} diff --git a/packages/cache-manager-redis/src/store.ts b/packages/cache-manager-redis/src/store.ts new file mode 100644 index 000000000000..c1f57fa62c77 --- /dev/null +++ b/packages/cache-manager-redis/src/store.ts @@ -0,0 +1,76 @@ +import type { Config, Store } from 'cache-manager'; +import { Redis } from '@midwayjs/redis'; +import { MidwayCommonError } from '@midwayjs/core'; + +const getVal = (value: unknown) => JSON.stringify(value) || '"undefined"'; +export interface RedisStore extends Store { + readonly isCacheable: (value: unknown) => boolean; +} + +export function createRedisStore(redisCache: Redis, options?: Config) { + const isCacheable = + options?.isCacheable || (value => value !== undefined && value !== null); + + const keys = (pattern: string) => redisCache.keys(pattern); + + return { + async get(key: string) { + const val = await redisCache.get(key); + if (val === undefined || val === null) return undefined; + else return JSON.parse(val) as T; + }, + async set(key, value, ttl) { + if (!isCacheable(value)) + throw new MidwayCommonError(`"${value}" is not a cacheable value`); + const t = ttl === undefined ? options?.ttl : ttl; + if (t !== undefined && t !== 0) + await redisCache.set(key, getVal(value), 'PX', t); + else await redisCache.set(key, getVal(value)); + }, + async mset(args, ttl) { + const t = ttl === undefined ? options?.ttl : ttl; + if (t !== undefined && t !== 0) { + const multi = redisCache.multi(); + for (const [key, value] of args) { + if (!isCacheable(value)) + throw new MidwayCommonError( + `"${getVal(value)}" is not a cacheable value` + ); + multi.set(key, getVal(value), 'PX', t); + } + await multi.exec(); + } else + await redisCache.mset( + args.flatMap(([key, value]) => { + if (!isCacheable(value)) + throw new Error(`"${getVal(value)}" is not a cacheable value`); + return [key, getVal(value)] as [string, string]; + }) + ); + }, + mget: (...args) => + redisCache + .mget(args) + .then(x => + x.map(x => + x === null || x === undefined + ? undefined + : (JSON.parse(x) as unknown) + ) + ), + async mdel(...args) { + await redisCache.del(args); + }, + async del(key) { + await redisCache.del(key); + }, + ttl: async key => redisCache.pttl(key), + keys: (pattern = '*') => keys(pattern), + reset: () => { + throw new MidwayCommonError( + 'flushdb() is too dangerous, if necessary, please use redisServiceFactory.get(client) to get the instance and call it manually.' + ); + }, + isCacheable, + } as RedisStore; +} diff --git a/packages/cache-manager-redis/test/index.test.ts b/packages/cache-manager-redis/test/index.test.ts new file mode 100644 index 000000000000..71929f4be408 --- /dev/null +++ b/packages/cache-manager-redis/test/index.test.ts @@ -0,0 +1,91 @@ +import * as cacheManager from '@midwayjs/cache-manager'; +import * as redis from '@midwayjs/redis'; +import { createLightApp, close } from '@midwayjs/mock'; +import { createStore } from '../src'; +import { CachingFactory } from '@midwayjs/cache-manager'; + +describe('cache-manager-redis', () => { + it('should test single caching', async () => { + const app = await createLightApp('', { + imports: [ + cacheManager, + redis, + ], + globalConfig: { + cacheManager: { + client: { + store: createStore('default'), + options: { + ttl: 10, + } + } + }, + redis: { + client: { + port: 6379, + host: '127.0.0.1', + } + } + }, + }); + + const cachingFactory = await app.getApplicationContext().getAsync(CachingFactory); + const caching = cachingFactory.getCaching('default'); + await caching.set('foo', 'bar', 1000); + const result = await caching.get('foo'); + expect(result).toEqual('bar'); + + + try { + await caching.reset(); + } catch (e) { + expect(e.message).toMatch('flushdb() is too dangerous'); + } + + await close(app); + }); + + it('should test multi caching', async () => { + const app = await createLightApp('', { + imports: [ + redis, + cacheManager, + ], + globalConfig: { + cacheManager: { + client: { + store: [ + { + store: createStore('default'), + options: { + ttl: 10, + } + } + ] + } + }, + redis: { + client: { + port: 6379, + host: '127.0.0.1', + } + } + } + }); + + + const cachingFactory = await app.getApplicationContext().getAsync(CachingFactory); + const caching = cachingFactory.getMultiCaching('default'); + await caching.mset([['foo', 'bar']], 5); + + const result = await caching.mget('foo'); + expect(result).toEqual(['bar']); + + // mdel + await caching.mdel('foo'); + const result2 = await caching.mget('foo'); + expect(result2).toEqual([undefined]); + + await close(app); + }); +}); diff --git a/packages/cache-manager-redis/tsconfig.json b/packages/cache-manager-redis/tsconfig.json new file mode 100644 index 000000000000..324fe88c9b14 --- /dev/null +++ b/packages/cache-manager-redis/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compileOnSave": true, + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/cache-manager-redis/typedoc.json b/packages/cache-manager-redis/typedoc.json new file mode 100644 index 000000000000..f593f276c273 --- /dev/null +++ b/packages/cache-manager-redis/typedoc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"] +} diff --git a/packages/cache-manager/CHANGELOG.md b/packages/cache-manager/CHANGELOG.md new file mode 100644 index 000000000000..72fa280ac482 --- /dev/null +++ b/packages/cache-manager/CHANGELOG.md @@ -0,0 +1,408 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0](https://github.com/midwayjs/midway/compare/v3.6.1...v3.7.0) (2022-10-29) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.6.0](https://github.com/midwayjs/midway/compare/v3.5.3...v3.6.0) (2022-10-10) + +### Features + +- add guard ([#2345](https://github.com/midwayjs/midway/issues/2345)) ([1b952a1](https://github.com/midwayjs/midway/commit/1b952a1b09adbb88ff3cff9a2974eb1e37ce53a5)) + +## [3.5.3](https://github.com/midwayjs/midway/compare/v3.5.2...v3.5.3) (2022-09-25) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.5.1](https://github.com/midwayjs/midway/compare/v3.5.0...v3.5.1) (2022-09-06) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.5.0](https://github.com/midwayjs/midway/compare/v3.4.13...v3.5.0) (2022-08-29) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.13](https://github.com/midwayjs/midway/compare/v3.4.12...v3.4.13) (2022-08-24) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.12](https://github.com/midwayjs/midway/compare/v3.4.11...v3.4.12) (2022-08-20) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.11](https://github.com/midwayjs/midway/compare/v3.4.10...v3.4.11) (2022-08-16) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.10](https://github.com/midwayjs/midway/compare/v3.4.9...v3.4.10) (2022-08-12) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.9](https://github.com/midwayjs/midway/compare/v3.4.8...v3.4.9) (2022-08-10) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.7](https://github.com/midwayjs/midway/compare/v3.4.6...v3.4.7) (2022-08-01) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.6](https://github.com/midwayjs/midway/compare/v3.4.5...v3.4.6) (2022-07-31) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.4](https://github.com/midwayjs/midway/compare/v3.4.3...v3.4.4) (2022-07-25) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.3](https://github.com/midwayjs/midway/compare/v3.4.2...v3.4.3) (2022-07-21) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.4.1](https://github.com/midwayjs/midway/compare/v3.4.0...v3.4.1) (2022-07-20) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0](https://github.com/midwayjs/midway/compare/v3.4.0-beta.12...v3.4.0) (2022-07-20) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.12](https://github.com/midwayjs/midway/compare/v3.4.0-beta.11...v3.4.0-beta.12) (2022-07-20) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.11](https://github.com/midwayjs/midway/compare/v3.4.0-beta.10...v3.4.0-beta.11) (2022-07-19) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.10](https://github.com/midwayjs/midway/compare/v3.4.0-beta.9...v3.4.0-beta.10) (2022-07-18) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.9](https://github.com/midwayjs/midway/compare/v3.4.0-beta.8...v3.4.0-beta.9) (2022-07-14) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.8](https://github.com/midwayjs/midway/compare/v3.4.0-beta.7...v3.4.0-beta.8) (2022-07-12) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.7](https://github.com/midwayjs/midway/compare/v3.4.0-beta.6...v3.4.0-beta.7) (2022-07-12) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.6](https://github.com/midwayjs/midway/compare/v3.4.0-beta.5...v3.4.0-beta.6) (2022-07-07) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.5](https://github.com/midwayjs/midway/compare/v3.4.0-beta.4...v3.4.0-beta.5) (2022-07-07) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.4.0-beta.4](https://github.com/midwayjs/midway/compare/v3.4.0-beta.3...v3.4.0-beta.4) (2022-07-04) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.3.12](https://github.com/midwayjs/midway/compare/v3.3.11...v3.3.12) (2022-06-02) + +### Bug Fixes + +- cache typings ([#2018](https://github.com/midwayjs/midway/issues/2018)) ([8db4e69](https://github.com/midwayjs/midway/commit/8db4e698e534da3eb7b4a37eeb7485b4fe34b977)) + +## [3.3.5](https://github.com/midwayjs/midway/compare/v3.3.4...v3.3.5) (2022-04-27) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.3.4](https://github.com/midwayjs/midway/compare/v3.3.3...v3.3.4) (2022-04-21) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.3.2](https://github.com/midwayjs/midway/compare/v3.3.1...v3.3.2) (2022-04-13) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.3.1](https://github.com/midwayjs/midway/compare/v3.3.0...v3.3.1) (2022-04-11) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.3.0](https://github.com/midwayjs/midway/compare/v3.2.2...v3.3.0) (2022-04-07) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.2.2](https://github.com/midwayjs/midway/compare/v3.2.1...v3.2.2) (2022-03-30) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.2.1](https://github.com/midwayjs/midway/compare/v3.2.0...v3.2.1) (2022-03-27) + +### Bug Fixes + +- swagger ui replace json path ([#1860](https://github.com/midwayjs/midway/issues/1860)) ([0f3728d](https://github.com/midwayjs/midway/commit/0f3728daccba12923f23f5b498c7dda13ced36d7)) + +# [3.2.0](https://github.com/midwayjs/midway/compare/v3.1.6...v3.2.0) (2022-03-25) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.1.6](https://github.com/midwayjs/midway/compare/v3.1.5...v3.1.6) (2022-03-21) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.1.5](https://github.com/midwayjs/midway/compare/v3.1.4...v3.1.5) (2022-03-18) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.1.2](https://github.com/midwayjs/midway/compare/v3.1.1...v3.1.2) (2022-03-15) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.1.1](https://github.com/midwayjs/midway/compare/v3.1.0...v3.1.1) (2022-03-09) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.1.0](https://github.com/midwayjs/midway/compare/v3.0.13...v3.1.0) (2022-03-07) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.13](https://github.com/midwayjs/midway/compare/v3.0.12...v3.0.13) (2022-03-01) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.11](https://github.com/midwayjs/midway/compare/v3.0.10...v3.0.11) (2022-02-25) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.10](https://github.com/midwayjs/midway/compare/v3.0.9...v3.0.10) (2022-02-24) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.9](https://github.com/midwayjs/midway/compare/v3.0.8...v3.0.9) (2022-02-21) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.7](https://github.com/midwayjs/midway/compare/v3.0.6...v3.0.7) (2022-02-17) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.6](https://github.com/midwayjs/midway/compare/v3.0.5...v3.0.6) (2022-02-13) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.5](https://github.com/midwayjs/midway/compare/v3.0.4...v3.0.5) (2022-02-10) + +**Note:** Version bump only for package @midwayjs/cache + +## [3.0.4](https://github.com/midwayjs/midway/compare/v3.0.3...v3.0.4) (2022-02-09) + +### Bug Fixes + +- supertest typings and createFunctionApp ([#1642](https://github.com/midwayjs/midway/issues/1642)) ([484f4f4](https://github.com/midwayjs/midway/commit/484f4f41b3b9e889d4d285f4871a0b37fa51e73f)) + +## [3.0.2](https://github.com/midwayjs/midway/compare/v3.0.1...v3.0.2) (2022-01-24) + +### Bug Fixes + +- singleton invoke request scope not valid ([#1622](https://github.com/midwayjs/midway/issues/1622)) ([f97c063](https://github.com/midwayjs/midway/commit/f97c0632107b47cf357d17774a4e4bb5233bba57)) + +## [3.0.1](https://github.com/midwayjs/midway/compare/v3.0.0...v3.0.1) (2022-01-24) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0](https://github.com/midwayjs/midway/compare/v3.0.0-beta.17...v3.0.0) (2022-01-20) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.17](https://github.com/midwayjs/midway/compare/v3.0.0-beta.16...v3.0.0-beta.17) (2022-01-18) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.16](https://github.com/midwayjs/midway/compare/v3.0.0-beta.15...v3.0.0-beta.16) (2022-01-11) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.15](https://github.com/midwayjs/midway/compare/v3.0.0-beta.14...v3.0.0-beta.15) (2022-01-07) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.14](https://github.com/midwayjs/midway/compare/v3.0.0-beta.13...v3.0.0-beta.14) (2022-01-04) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.13](https://github.com/midwayjs/midway/compare/v3.0.0-beta.12...v3.0.0-beta.13) (2021-12-30) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.12](https://github.com/midwayjs/midway/compare/v3.0.0-beta.11...v3.0.0-beta.12) (2021-12-28) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.11](https://github.com/midwayjs/midway/compare/v3.0.0-beta.10...v3.0.0-beta.11) (2021-12-21) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.10](https://github.com/midwayjs/midway/compare/v3.0.0-beta.9...v3.0.0-beta.10) (2021-12-20) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.9](https://github.com/midwayjs/midway/compare/v3.0.0-beta.8...v3.0.0-beta.9) (2021-12-09) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.8](https://github.com/midwayjs/midway/compare/v3.0.0-beta.7...v3.0.0-beta.8) (2021-12-08) + +### Bug Fixes + +- express routing middleware takes effect at the controller level ([#1364](https://github.com/midwayjs/midway/issues/1364)) ([b9272e0](https://github.com/midwayjs/midway/commit/b9272e0971003443304b0c53815be31a0061b4bd)) + +# [3.0.0-beta.7](https://github.com/midwayjs/midway/compare/v3.0.0-beta.6...v3.0.0-beta.7) (2021-12-03) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.6](https://github.com/midwayjs/midway/compare/v3.0.0-beta.5...v3.0.0-beta.6) (2021-11-26) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.5](https://github.com/midwayjs/midway/compare/v3.0.0-beta.4...v3.0.0-beta.5) (2021-11-25) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.4](https://github.com/midwayjs/midway/compare/v3.0.0-beta.3...v3.0.0-beta.4) (2021-11-24) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.3](https://github.com/midwayjs/midway/compare/v3.0.0-beta.2...v3.0.0-beta.3) (2021-11-18) + +### Features + +- add component and framework config definition ([#1367](https://github.com/midwayjs/midway/issues/1367)) ([b2fe615](https://github.com/midwayjs/midway/commit/b2fe6157f99659471ff1333eca0b86bb889f61a3)) + +# [3.0.0-beta.2](https://github.com/midwayjs/midway/compare/v3.0.0-beta.1...v3.0.0-beta.2) (2021-11-16) + +**Note:** Version bump only for package @midwayjs/cache + +# [3.0.0-beta.1](https://github.com/midwayjs/midway/compare/v2.12.4...v3.0.0-beta.1) (2021-11-14) + +### Features + +- add redis component ([#1270](https://github.com/midwayjs/midway/issues/1270)) ([09c993a](https://github.com/midwayjs/midway/commit/09c993ac308d26fa9c742a659471c3f4cf5c5782)) + +## [2.12.3](https://github.com/midwayjs/midway/compare/v2.12.2...v2.12.3) (2021-08-09) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.12.1](https://github.com/midwayjs/midway/compare/v2.12.0...v2.12.1) (2021-08-01) + +**Note:** Version bump only for package @midwayjs/cache + +# [2.12.0](https://github.com/midwayjs/midway/compare/v2.11.7...v2.12.0) (2021-07-30) + +### Features + +- enhance cache for [#1103](https://github.com/midwayjs/midway/issues/1103) ([#1189](https://github.com/midwayjs/midway/issues/1189)) ([562236c](https://github.com/midwayjs/midway/commit/562236cfa5970d47454f26d92c350165d73a63cd)) + +## [2.11.6](https://github.com/midwayjs/midway/compare/v2.11.5...v2.11.6) (2021-07-16) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.11.5](https://github.com/midwayjs/midway/compare/v2.11.4...v2.11.5) (2021-07-15) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.11.4](https://github.com/midwayjs/midway/compare/v2.11.3...v2.11.4) (2021-07-06) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.11.3](https://github.com/midwayjs/midway/compare/v2.11.2...v2.11.3) (2021-07-02) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.11.2](https://github.com/midwayjs/midway/compare/v2.11.1...v2.11.2) (2021-06-28) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.11.1](https://github.com/midwayjs/midway/compare/v2.11.0...v2.11.1) (2021-06-19) + +**Note:** Version bump only for package @midwayjs/cache + +# [2.11.0](https://github.com/midwayjs/midway/compare/v2.10.19...v2.11.0) (2021-06-10) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.18](https://github.com/midwayjs/midway/compare/v2.10.17...v2.10.18) (2021-05-26) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.14](https://github.com/midwayjs/midway/compare/v2.10.13...v2.10.14) (2021-05-11) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.13](https://github.com/midwayjs/midway/compare/v2.10.12...v2.10.13) (2021-05-08) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.12](https://github.com/midwayjs/midway/compare/v2.10.11...v2.10.12) (2021-05-07) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.11](https://github.com/midwayjs/midway/compare/v2.10.10...v2.10.11) (2021-04-29) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.10](https://github.com/midwayjs/midway/compare/v2.10.9...v2.10.10) (2021-04-24) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.9](https://github.com/midwayjs/midway/compare/v2.10.8...v2.10.9) (2021-04-21) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.8](https://github.com/midwayjs/midway/compare/v2.10.7...v2.10.8) (2021-04-21) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.7](https://github.com/midwayjs/midway/compare/v2.10.6...v2.10.7) (2021-04-17) + +### Bug Fixes + +- add event name args ([#986](https://github.com/midwayjs/midway/issues/986)) ([bfd8232](https://github.com/midwayjs/midway/commit/bfd82320aee8600d8fa30bd2821a0e68c80fd755)) +- format ([#997](https://github.com/midwayjs/midway/issues/997)) ([456cc14](https://github.com/midwayjs/midway/commit/456cc14513bdb000d1aa3130e9719caf7a8a803f)) + +## [2.10.6](https://github.com/midwayjs/midway/compare/v2.10.5...v2.10.6) (2021-04-14) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.5](https://github.com/midwayjs/midway/compare/v2.10.4...v2.10.5) (2021-04-13) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.4](https://github.com/midwayjs/midway/compare/v2.10.3...v2.10.4) (2021-04-10) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.3](https://github.com/midwayjs/midway/compare/v2.10.2...v2.10.3) (2021-04-07) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.10.2](https://github.com/midwayjs/midway/compare/v2.10.1...v2.10.2) (2021-04-05) + +**Note:** Version bump only for package @midwayjs/cache + +# [2.10.0](https://github.com/midwayjs/midway/compare/v2.9.3...v2.10.0) (2021-04-02) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.9.2](https://github.com/midwayjs/midway/compare/v2.9.1...v2.9.2) (2021-03-27) + +**Note:** Version bump only for package @midwayjs/cache + +## [2.9.1](https://github.com/midwayjs/midway/compare/v2.9.0...v2.9.1) (2021-03-24) + +**Note:** Version bump only for package @midwayjs/cache + +# [2.9.0](https://github.com/midwayjs/midway/compare/v2.8.13...v2.9.0) (2021-03-22) + +### Features + +- add midway cache ([#911](https://github.com/midwayjs/midway/issues/911)) ([cc49eee](https://github.com/midwayjs/midway/commit/cc49eee739ba6d2c37b9270b6cf5239afde4a912)) +- support bootstrap load config first ([#931](https://github.com/midwayjs/midway/issues/931)) ([ae9ed26](https://github.com/midwayjs/midway/commit/ae9ed261aacdb483d3a9a612be79fff384503bcc)) diff --git a/packages/cache-manager/README.md b/packages/cache-manager/README.md new file mode 100644 index 000000000000..db84d169a29e --- /dev/null +++ b/packages/cache-manager/README.md @@ -0,0 +1,12 @@ +# midway cache manager + +[![Package Quality](http://npm.packagequality.com/shield/midway-core.svg)](http://packagequality.com/#?package=midway-core) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/midwayjs/midway/pulls) + +this is a sub package for midway. + +Document: [https://midwayjs.org](https://midwayjs.org) + +## License + +[MIT]((http://github.com/midwayjs/midway/blob/master/LICENSE)) diff --git a/packages/cache-manager/index.d.ts b/packages/cache-manager/index.d.ts new file mode 100644 index 000000000000..d8c12df93f29 --- /dev/null +++ b/packages/cache-manager/index.d.ts @@ -0,0 +1,9 @@ +import { CacheManagerOptions } from './dist/index'; + +export * from './dist/index'; + +declare module '@midwayjs/core/dist/interface' { + interface MidwayConfig { + cacheManager?: ServiceFactoryConfigOption; + } +} diff --git a/packages/cache-manager/jest.config.js b/packages/cache-manager/jest.config.js new file mode 100644 index 000000000000..850003a58606 --- /dev/null +++ b/packages/cache-manager/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/test/fixtures'], + coveragePathIgnorePatterns: ['/test/', '/dist/', '/node_modules/'], + setupFilesAfterEnv: ['./jest.setup.js'], + coverageProvider: 'v8', +}; diff --git a/packages/cache-manager/jest.setup.js b/packages/cache-manager/jest.setup.js new file mode 100644 index 000000000000..53c7930592d0 --- /dev/null +++ b/packages/cache-manager/jest.setup.js @@ -0,0 +1,2 @@ +process.env.MIDWAY_TS_MODE = 'true'; +jest.setTimeout(30000); diff --git a/packages/cache-manager/package.json b/packages/cache-manager/package.json new file mode 100644 index 000000000000..e5f66cf49322 --- /dev/null +++ b/packages/cache-manager/package.json @@ -0,0 +1,40 @@ +{ + "name": "@midwayjs/cache-manager", + "version": "3.13.5", + "description": "midway cache manager", + "main": "dist/index.js", + "typings": "index.d.ts", + "scripts": { + "build": "tsc", + "test": "node --require=ts-node/register ../../node_modules/.bin/jest --runInBand", + "cov": "node --require=ts-node/register ../../node_modules/.bin/jest --runInBand --coverage --forceExit" + }, + "author": "", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "index.d.ts" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git@github.com:midwayjs/midway.git" + }, + "keywords": [ + "midway", + "cache" + ], + "engines": { + "node": ">=12" + }, + "devDependencies": { + "@midwayjs/core": "^3.13.5", + "@midwayjs/mock": "^3.13.5", + "@midwayjs/redis": "^3.13.5", + "cache-manager-ioredis-yet": "1.2.2" + }, + "dependencies": { + "cache-manager": "5.3.2", + "promise-coalesce": "1.1.1" + } +} diff --git a/packages/cache-manager/src/configuration.ts b/packages/cache-manager/src/configuration.ts new file mode 100644 index 000000000000..4da15e2b7b48 --- /dev/null +++ b/packages/cache-manager/src/configuration.ts @@ -0,0 +1,65 @@ +import { + Configuration, + ILifeCycle, + IMidwayContainer, + Inject, + JoinPoint, + MidwayDecoratorService, + REQUEST_OBJ_CTX_KEY, +} from '@midwayjs/core'; +import { CACHE_DECORATOR_KEY } from './decorator/cacheKey'; +import { CachingFactory } from './factory'; + +@Configuration({ + namespace: 'cacheManager', + importConfigs: [ + { + default: { + cacheManager: {}, + }, + }, + ], +}) +export class CacheConfiguration implements ILifeCycle { + @Inject() + decoratorService: MidwayDecoratorService; + + cacheService: CachingFactory; + + async onReady(container: IMidwayContainer) { + // init factory and caching instance + this.cacheService = await container.getAsync(CachingFactory); + // register @Caching decorator implementation + this.decoratorService.registerMethodHandler( + CACHE_DECORATOR_KEY, + ({ metadata }) => { + return { + around: async (joinPoint: JoinPoint) => { + const cachingInstance = this.cacheService.get( + metadata.cacheInstanceName + ); + + if (typeof metadata.cacheKey === 'function') { + metadata.cacheKey = await metadata.cacheKey({ + methodArgs: joinPoint.args, + ctx: joinPoint.target[REQUEST_OBJ_CTX_KEY], + target: joinPoint.target, + }); + } + + if (typeof metadata.cacheKey === 'string') { + return cachingInstance.methodWrap( + metadata.cacheKey, + joinPoint.proceed, + joinPoint.args, + metadata.ttl + ); + } else { + return joinPoint.proceed(...joinPoint.args); + } + }, + }; + } + ); + } +} diff --git a/packages/cache-manager/src/decorator/cacheKey.ts b/packages/cache-manager/src/decorator/cacheKey.ts new file mode 100644 index 000000000000..6878c3ff5062 --- /dev/null +++ b/packages/cache-manager/src/decorator/cacheKey.ts @@ -0,0 +1,22 @@ +import { createCustomMethodDecorator, IMidwayContext } from '@midwayjs/core'; + +export const CACHE_DECORATOR_KEY = 'cache-manager:caching'; +export type CachingDecoratorKeyOptions = + | string + | ((options: { + methodArgs: any[]; + ctx?: IMidwayContext; + target: any; + }) => string); + +export function Caching( + cacheInstanceName: string, + cacheKey: CachingDecoratorKeyOptions, + ttl?: number +) { + return createCustomMethodDecorator(CACHE_DECORATOR_KEY, { + cacheInstanceName, + cacheKey, + ttl, + }); +} diff --git a/packages/cache-manager/src/factory.ts b/packages/cache-manager/src/factory.ts new file mode 100644 index 000000000000..95fee45be912 --- /dev/null +++ b/packages/cache-manager/src/factory.ts @@ -0,0 +1,190 @@ +import { + ApplicationContext, + Config, + IMidwayContainer, + Init, + MidwayCommonError, + Provide, + Scope, + ScopeEnum, + ServiceFactory, + ServiceFactoryConfigOption, +} from '@midwayjs/core'; +import { caching, multiCaching, WrapTTL, CachingConfig } from 'cache-manager'; +import { + CacheManagerOptions, + MidwayCache, + MidwayMultiCache, + MidwayUnionCache, +} from './interface'; +import { coalesceAsync } from 'promise-coalesce'; + +@Provide() +@Scope(ScopeEnum.Singleton) +export class CachingFactory extends ServiceFactory { + @Config('cacheManager') + protected cacheManagerConfig: ServiceFactoryConfigOption; + + @ApplicationContext() + protected applicationContext: IMidwayContainer; + + @Init() + protected async init() { + await this.initClients(this.cacheManagerConfig); + } + + protected async createClient( + config: CacheManagerOptions, + clientName: string + ): Promise { + // multi cache + if (Array.isArray(config.store)) { + const newFactory = []; + for (const storeConfig of config.store) { + if (typeof storeConfig === 'string') { + if (!this.has(storeConfig)) { + throw new MidwayCommonError( + `cache instance "${storeConfig}" not found in "${clientName}", please check your configuration.` + ); + } + newFactory.push(this.get(storeConfig)); + } else if (typeof storeConfig === 'function') { + newFactory.push(await storeConfig()); + } else if (storeConfig['wrap']) { + // wrap is a caching object method + newFactory.push(storeConfig['wrap']); + } else if (typeof storeConfig === 'object') { + if (typeof storeConfig.store === 'function') { + storeConfig.store = await storeConfig.store( + storeConfig['options'] || {}, + this.applicationContext + ); + } + if (!storeConfig.store) { + throw new MidwayCommonError( + `cache instance "${clientName}" store is undefined, please check your configuration.` + ); + } + newFactory.push( + await caching(storeConfig.store, storeConfig['options']) + ); + } else { + throw new MidwayCommonError('invalid cache config'); + } + } + const cacheInstance = await multiCaching(newFactory); + return this.wrapMultiCaching(cacheInstance, newFactory); + } else { + // single cache + if (typeof config.store === 'function') { + config.store = await config.store( + config['options'] || {}, + this.applicationContext + ); + } + if (!config.store) { + throw new MidwayCommonError( + `cache instance "${clientName}" store is undefined, please check your configuration.` + ); + } + const cacheInstance = await caching(config.store, config['options']); + return this.wrapCaching(cacheInstance, config['options']); + } + } + + public wrapCaching( + cacheInstance: MidwayCache, + cachingArgs?: CachingConfig + ): MidwayCache { + if (cacheInstance.methodWrap) { + return cacheInstance; + } + + cacheInstance.methodWrap = async ( + key: string, + fn: (...args) => Promise, + fnArgs: any[], + ttl?: WrapTTL + ): Promise => { + const store = cacheInstance.store; + return coalesceAsync(key, async () => { + const value = await store.get(key); + if (value === undefined) { + const result = await fn(...fnArgs); + const cacheTTL = typeof ttl === 'function' ? ttl(result) : ttl; + await store.set(key, result, cacheTTL); + return result; + } else if (cachingArgs?.refreshThreshold) { + const cacheTTL = typeof ttl === 'function' ? ttl(value) : ttl; + const remainingTtl = await store.ttl(key); + if ( + remainingTtl !== -1 && + remainingTtl < cachingArgs.refreshThreshold + ) { + // fn(...fnArgs).then(result => store.set(key, result, cacheTTL)); + coalesceAsync(`+++${key}`, () => fn(...fnArgs)).then(result => + store.set(key, result, cacheTTL) + ); + } + } + return value; + }); + }; + + return cacheInstance; + } + + public wrapMultiCaching( + cacheInstance: MidwayMultiCache, + caches: MidwayCache[] + ): MidwayMultiCache { + if (cacheInstance.methodWrap) { + return cacheInstance; + } + + cacheInstance.methodWrap = async ( + key: string, + fn: (...args) => Promise, + fnArgs: any[], + ttl?: WrapTTL + ): Promise => { + let value: T | undefined; + let i = 0; + for (; i < caches.length; i++) { + try { + value = await caches[i].get(key); + if (value !== undefined) break; + } catch (e) { + // ignore + } + } + if (value === undefined) { + const result = await fn(...fnArgs); + const cacheTTL = typeof ttl === 'function' ? ttl(result) : ttl; + await cacheInstance.set(key, result, cacheTTL); + return result; + } else { + const cacheTTL = typeof ttl === 'function' ? ttl(value) : ttl; + Promise.all( + caches.slice(0, i).map(cache => cache.set(key, value, cacheTTL)) + ).then(); + caches[i].methodWrap(key, fn, fnArgs, ttl).then(); // call wrap for store for internal refreshThreshold logic, see: src/caching.ts caching.wrap + } + return value; + }; + + return cacheInstance; + } + + getName(): string { + return 'cache-manager'; + } + + public getCaching(cacheKey: string): MidwayCache { + return this.get(cacheKey); + } + + public getMultiCaching(cacheKey: string): MidwayMultiCache { + return this.get(cacheKey); + } +} diff --git a/packages/cache-manager/src/index.ts b/packages/cache-manager/src/index.ts new file mode 100644 index 000000000000..828bf2582284 --- /dev/null +++ b/packages/cache-manager/src/index.ts @@ -0,0 +1,5 @@ +export { CacheConfiguration as Configuration } from './configuration'; +export * from './interface'; +export * from './factory'; +export * from './decorator/cacheKey'; +export * as CacheManager from 'cache-manager'; diff --git a/packages/cache-manager/src/interface.ts b/packages/cache-manager/src/interface.ts new file mode 100644 index 000000000000..1030447a7277 --- /dev/null +++ b/packages/cache-manager/src/interface.ts @@ -0,0 +1,35 @@ +import { Cache, Store, MemoryConfig, FactoryStore, FactoryConfig, MultiCache, WrapTTL } from 'cache-manager'; + +export type SingleCacheOptions = { + store: 'memory'; + options?: MemoryConfig; +} | { + store: S | (() => S | Promise); +} | { + store: FactoryStore; + options?: FactoryConfig>[0]>, +} + +export type CacheManagerOptions = SingleCacheOptions | { + store: Array | (() => Cache | Promise)>; +} + +export type MidwayCache = Cache & { + methodWrap?: ( + key: string, + fn: (...args) => Promise, + fnArgs: any[], + ttl?: WrapTTL + ) => Promise +} + +export type MidwayMultiCache = MultiCache & { + methodWrap?: ( + key: string, + fn: (...args) => Promise, + fnArgs: any[], + ttl?: WrapTTL + ) => Promise +} + +export type MidwayUnionCache = MidwayCache | MidwayMultiCache; diff --git a/packages/cache-manager/test/factory.test.ts b/packages/cache-manager/test/factory.test.ts new file mode 100644 index 000000000000..f58df48872a0 --- /dev/null +++ b/packages/cache-manager/test/factory.test.ts @@ -0,0 +1,255 @@ +import { CachingFactory } from '../src'; +import { sleep } from '@midwayjs/core'; +import { redisStore } from 'cache-manager-ioredis-yet'; + +describe('CachingFactory', () => { + it('should correctly initialize clients', async () => { + const factory = new CachingFactory(); + await factory['init'](); + expect(factory['clients'].size).toBe(0); + }); + + it('should correctly create a client', async () => { + const factory = new CachingFactory(); + factory['cacheManagerConfig'] = { + clients: { + test: { + store: 'memory', + }, + }, + } + await factory['init'](); + expect(factory['clients'].size).toBe(1); + const caching = factory.getCaching('test'); + expect(caching).toBeDefined(); + expect(caching.methodWrap).toBeDefined(); + expect(factory.getName()).toBe('cache-manager'); + }); + + it('should correctly create a multi client', async () => { + const factory = new CachingFactory(); + factory['cacheManagerConfig'] = { + clients: { + default: { + store: 'memory', + }, + test: { + store: [ + 'default', + { + store: 'memory', + }, + ], + }, + }, + } + await factory['init'](); + expect(factory['clients'].size).toBe(2); + const caching = factory.getMultiCaching('test'); + expect(caching).toBeDefined(); + expect(caching.methodWrap).toBeDefined(); + + let result = await caching.methodWrap('cache-manager-key', async () => { + return 1; + }, [], 10); + expect(result).toBe(1); + result = await caching.methodWrap('cache-manager-key', async () => { + return 2; + }, [], 10); + expect(result).toBe(1); + }); + + it('should correctly create a client with ttl function', async () => { + const factory = new CachingFactory(); + factory['cacheManagerConfig'] = { + clients: { + default: { + store: 'memory', + }, + }, + }; + + await factory['init'](); + expect(factory['clients'].size).toBe(1); + + const caching = factory.getCaching('default'); + expect(caching).toBeDefined(); + expect(caching.methodWrap).toBeDefined(); + + let result = await caching.methodWrap('cache-manager-key', async () => { + return 1; + }, [], () => 10); + expect(result).toBe(1); + result = await caching.methodWrap('cache-manager-key', async () => { + return 2; + }, [], () => 10); + expect(result).toBe(1); + + await sleep(20); + + result = await caching.methodWrap('cache-manager-key', async () => { + return 3; + }, [], () => 100); + expect(result).toBe(3); + + result = await caching.methodWrap('cache-manager-key', async () => { + return 4; + }, [], () => 1); + expect(result).toBe(3); + + result = await caching.methodWrap('cache-manager-key', async () => { + return 5; + }, [], () => 1); + expect(result).toBe(3); + + result = await caching.methodWrap('key1', async () => { + return 5; + }, [], () => 1); + expect(result).toBe(5); + }); + + it('should correctly create a client with refreshThreshold', async () => { + const factory = new CachingFactory(); + factory['cacheManagerConfig'] = { + clients: { + default: { + store: 'memory', + options: { + refreshThreshold: 50, + ttl: 100, + }, + }, + }, + }; + + await factory['init'](); + expect(factory['clients'].size).toBe(1); + + const caching = factory.getCaching('default'); + expect(caching).toBeDefined(); + expect(caching.methodWrap).toBeDefined(); + + let result = await caching.methodWrap('cache-manager-key', async (v) => { + return v; + }, [1]); + expect(result).toBe(1); + + result = await caching.methodWrap('cache-manager-key', async (v) => { + return v; + }, [2]); + expect(result).toBe(1); + + await sleep(60); + + // 这里是后台刷新,所以拿到的还是 1 + result = await caching.methodWrap('cache-manager-key', async (v) => { + return v; + }, [3]); + expect(result).toBe(1); + + // 这里拿到的是上一次刷新的结果 + result = await caching.methodWrap('cache-manager-key', async (v) => { + return v; + }, [4]); + expect(result).toBe(3); + }); + + it('should correctly create a client with refreshThreshold and ttl function', async () => { + const factory = new CachingFactory(); + factory['cacheManagerConfig'] = { + clients: { + default: { + store: 'memory', + options: { + ttl: 100, + }, + }, + }, + }; + + await factory['init'](); + expect(factory['clients'].size).toBe(1); + + const caching = factory.getCaching('default'); + expect(caching).toBeDefined(); + expect(caching.methodWrap).toBeDefined(); + + let result = await caching.methodWrap('cache-manager-key', async (v) => { + return v; + }, [1], () => 10); + expect(result).toBe(1); + + await sleep(20); + + // 这里上一个设置的过期了,所以重新设置生效了 + result = await caching.methodWrap('cache-manager-key', async (v) => { + return v; + }, [2], () => 100); + expect(result).toBe(2); + + await sleep(60); + + // 因为值还是缓存着,没有拿到最新的值, ttl 设置没生效 + result = await caching.methodWrap('cache-manager-key', async (v) => { + return v; + }, [3], () => 10); + expect(result).toBe(2); + }); + + it('should test multi with not defined client', async () => { + const factory = new CachingFactory(); + factory['cacheManagerConfig'] = { + clients: { + test: { + store: [ + 'default', + ] + }, + }, + }; + + let error; + try { + await factory['init'](); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toBe('cache instance "default" not found in "test", please check your configuration.'); + }); + + it('should test redis store and close it', async () => { + const factory = new CachingFactory(); + factory['cacheManagerConfig'] = { + clients: { + default: { + store: redisStore, + options: { + port: 6379, + host: 'localhost', + ttl: 10, + }, + }, + }, + }; + + await factory['init'](); + expect(factory['clients'].size).toBe(1); + + const caching = factory.getCaching('default'); + expect(caching).toBeDefined(); + + // get/set + await caching.set('cache-manager-key', 'value'); + let result = await caching.get('cache-manager-key'); + expect(result).toBe('value'); + + // ttl + await sleep(20); + result = await caching.get('cache-manager-key'); + expect(result).toBeUndefined(); + + await (caching.store as any).client.quit(); + }); + +}); diff --git a/packages/cache-manager/test/fixtures/cache-decorator/package.json b/packages/cache-manager/test/fixtures/cache-decorator/package.json new file mode 100644 index 000000000000..0c6d96230994 --- /dev/null +++ b/packages/cache-manager/test/fixtures/cache-decorator/package.json @@ -0,0 +1,3 @@ +{ + "name": "my-midway-project" +} diff --git a/packages/cache-manager/test/fixtures/cache-decorator/src/configuration.ts b/packages/cache-manager/test/fixtures/cache-decorator/src/configuration.ts new file mode 100644 index 000000000000..b33a830af15e --- /dev/null +++ b/packages/cache-manager/test/fixtures/cache-decorator/src/configuration.ts @@ -0,0 +1,27 @@ +import { Configuration } from '@midwayjs/core'; +import * as cacheComponent from '../../../../src'; + +@Configuration({ + imports: [ + cacheComponent + ], + importConfigs: [ + { + default: { + cacheManager: { + clients: { + default: { + store: 'memory', + options: { + max: 100, + ttl: 10, + }, + }, + }, + } + } + } + ] +}) +export class ContainerLifeCycle { +} diff --git a/packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts b/packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts new file mode 100644 index 000000000000..fc6f497969e2 --- /dev/null +++ b/packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts @@ -0,0 +1,20 @@ +import { Provide } from '@midwayjs/core'; +import { Caching } from '../../../../../src'; + +@Provide() +export class UserService { + + @Caching('default', 'abc', 10) + async getUser(name: string){ + return name; + } + + @Caching('default', 'error', 10) + async getUserThrowError(name: string){ + if (name === 'harry') { + return name; + } else { + throw new Error('error'); + } + } +} diff --git a/packages/cache-manager/test/fixtures/cache-manager/package.json b/packages/cache-manager/test/fixtures/cache-manager/package.json new file mode 100644 index 000000000000..0c6d96230994 --- /dev/null +++ b/packages/cache-manager/test/fixtures/cache-manager/package.json @@ -0,0 +1,3 @@ +{ + "name": "my-midway-project" +} diff --git a/packages/cache-manager/test/fixtures/cache-manager/src/configuration.ts b/packages/cache-manager/test/fixtures/cache-manager/src/configuration.ts new file mode 100644 index 000000000000..b33a830af15e --- /dev/null +++ b/packages/cache-manager/test/fixtures/cache-manager/src/configuration.ts @@ -0,0 +1,27 @@ +import { Configuration } from '@midwayjs/core'; +import * as cacheComponent from '../../../../src'; + +@Configuration({ + imports: [ + cacheComponent + ], + importConfigs: [ + { + default: { + cacheManager: { + clients: { + default: { + store: 'memory', + options: { + max: 100, + ttl: 10, + }, + }, + }, + } + } + } + ] +}) +export class ContainerLifeCycle { +} diff --git a/packages/cache-manager/test/fixtures/cache-manager/src/service/user.service.ts b/packages/cache-manager/test/fixtures/cache-manager/src/service/user.service.ts new file mode 100644 index 000000000000..c075c43e7134 --- /dev/null +++ b/packages/cache-manager/test/fixtures/cache-manager/src/service/user.service.ts @@ -0,0 +1,21 @@ +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayCache } from '../../../../../src'; + +@Provide() +export class UserService { + + @InjectClient(CachingFactory, 'default') + cache: MidwayCache + + async setUser(name: string, value: string){ + await this.cache.set(name, value); + } + + async getUser(name: string){ + return await this.cache.get(name); + } + + async reset(){ + await this.cache.reset(); + } +} diff --git a/packages/cache-manager/test/index.test.ts b/packages/cache-manager/test/index.test.ts new file mode 100644 index 000000000000..0bb78e75c48b --- /dev/null +++ b/packages/cache-manager/test/index.test.ts @@ -0,0 +1,63 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import { createLightApp, close } from '@midwayjs/mock'; +import { caching } from 'cache-manager'; +import { sleep } from '@midwayjs/core'; + +describe(`index.test.ts`, ()=>{ + + describe('cache manager', () => { + it('test cache manager wrap method', async () => { + const memoryCache = await caching('memory', { + max: 100, + ttl: 10 + }); + let i = 0; + const cached = await memoryCache.wrap('cache-manager-key', async () => { + return i++; + }); + expect(cached).toEqual(0); + }); + }); + + it(`test cache`, async () => { + const app = await createLightApp(path.join(__dirname, './fixtures/cache-manager')); + const appCtx = app.getApplicationContext(); + + const userService: any = await appCtx.getAsync('userService'); + assert((await userService.getUser(`name`)) === undefined); + await userService.setUser('name', 'stone-jin'); + assert((await userService.getUser(`name`)) === 'stone-jin'); + await userService.setUser('name', {name: '123'}); + assert(JSON.stringify(await userService.getUser('name')) === JSON.stringify({name: '123'})); + await userService.reset(); + assert((await userService.getUser(`name`)) === undefined); + }); + + it(`test cache decorator`, async () => { + const app = await createLightApp(path.join(__dirname, './fixtures/cache-decorator')); + const appCtx = app.getApplicationContext(); + + const userService: any = await appCtx.getAsync('userService'); + expect((await userService.getUser('harry'))).toEqual('harry'); + expect((await userService.getUser('harry1'))).toEqual('harry'); + expect((await userService.getUser('harry2'))).toEqual('harry'); + + await sleep(20); + expect((await userService.getUser('harry3'))).toEqual('harry3'); + + // throw error + let err; + try { + await userService.getUserThrowError('harry1'); + } catch (e) { + err = e; + } + expect(err.message).toEqual('error'); + // cache + expect((await userService.getUserThrowError('harry'))).toEqual('harry'); + expect((await userService.getUserThrowError('harry1'))).toEqual('harry'); + + await close(app); + }); +}) diff --git a/packages/cache-manager/tsconfig.json b/packages/cache-manager/tsconfig.json new file mode 100644 index 000000000000..324fe88c9b14 --- /dev/null +++ b/packages/cache-manager/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compileOnSave": true, + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/cache-manager/typedoc.json b/packages/cache-manager/typedoc.json new file mode 100644 index 000000000000..f593f276c273 --- /dev/null +++ b/packages/cache-manager/typedoc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"] +} diff --git a/packages/redis/index.d.ts b/packages/redis/index.d.ts index 22a74280a5d5..b661ab3370e8 100644 --- a/packages/redis/index.d.ts +++ b/packages/redis/index.d.ts @@ -1,7 +1,4 @@ -import { ClusterNode, ClusterOptions } from 'ioredis'; -import * as Redis from 'ioredis'; import { RedisConfigOptions } from './dist'; - export * from './dist/index'; // Single Redis diff --git a/packages/redis/src/index.ts b/packages/redis/src/index.ts index 7adfa2a7c3b9..99c602bdda40 100644 --- a/packages/redis/src/index.ts +++ b/packages/redis/src/index.ts @@ -3,4 +3,4 @@ import Redis from 'ioredis'; export { RedisConfiguration as Configuration } from './configuration'; export * from './manager'; export * from './interface'; -export { Redis as IORedis }; +export { Redis }; From 43008deea7cbca94578a260c1650074b6f0478dd Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sun, 31 Dec 2023 19:37:05 +0800 Subject: [PATCH 05/11] feat: visit swagger json without include ui-dist & support custom ui (#3528) * feat: support new render function * chore: update typings * chore: update test * fix: default json url --- packages/core/src/error/framework.ts | 10 ++ packages/swagger/index.html | 32 +++++ packages/swagger/package.json | 3 +- packages/swagger/src/config/config.default.ts | 4 +- packages/swagger/src/index.ts | 5 + packages/swagger/src/interfaces/index.ts | 11 +- packages/swagger/src/swaggerMiddleware.ts | 136 ++++++------------ packages/swagger/src/ui/render.ts | 102 +++++++++++++ .../test/fixtures/cats/src/configuration.ts | 2 + packages/swagger/test/index.test.ts | 4 + 10 files changed, 211 insertions(+), 98 deletions(-) create mode 100644 packages/swagger/index.html create mode 100644 packages/swagger/src/ui/render.ts diff --git a/packages/core/src/error/framework.ts b/packages/core/src/error/framework.ts index f47f48b026ff..7605d4006f91 100644 --- a/packages/core/src/error/framework.ts +++ b/packages/core/src/error/framework.ts @@ -23,6 +23,7 @@ export const FrameworkErrorEnum = registerErrorCode('midway', { INVOKE_METHOD_FORBIDDEN: 10018, CODE_INVOKE_TIMEOUT: 10019, MAIN_FRAMEWORK_MISSING: 10020, + INVALID_CONFIG_PROPERTY: 10021, } as const); export class MidwayCommonError extends MidwayError { @@ -218,3 +219,12 @@ export class MidwayMainFrameworkMissingError extends MidwayError { ); } } + +export class MidwayInvalidConfigPropertyError extends MidwayError { + constructor(propertyName: string) { + super( + `Invalid config property "${propertyName}", please check your configuration.`, + FrameworkErrorEnum.INVALID_CONFIG_PROPERTY + ); + } +} diff --git a/packages/swagger/index.html b/packages/swagger/index.html new file mode 100644 index 000000000000..a0ffe4150367 --- /dev/null +++ b/packages/swagger/index.html @@ -0,0 +1,32 @@ + + + + + + + SwaggerUI + + + +
+ + + + + diff --git a/packages/swagger/package.json b/packages/swagger/package.json index a939a824278c..9017432257c5 100644 --- a/packages/swagger/package.json +++ b/packages/swagger/package.json @@ -6,7 +6,8 @@ "files": [ "dist/**/*.js", "dist/**/*.d.ts", - "index.d.ts" + "index.d.ts", + "index.html" ], "devDependencies": { "@midwayjs/core": "^3.13.7", diff --git a/packages/swagger/src/config/config.default.ts b/packages/swagger/src/config/config.default.ts index efe7c17f0b02..436612c9a464 100644 --- a/packages/swagger/src/config/config.default.ts +++ b/packages/swagger/src/config/config.default.ts @@ -1,8 +1,10 @@ import { SwaggerOptions } from '../interfaces'; - +import { renderSwaggerUIRemote } from '../ui/render'; export const swagger: SwaggerOptions = { title: 'My Project', description: 'This is a swagger-ui for midwayjs project', version: '1.0.0', swaggerPath: '/swagger-ui', + swaggerUIRender: renderSwaggerUIRemote, + swaggerUIRenderOptions: {}, }; diff --git a/packages/swagger/src/index.ts b/packages/swagger/src/index.ts index 5a8a1d0f84b8..5c655f7c1873 100644 --- a/packages/swagger/src/index.ts +++ b/packages/swagger/src/index.ts @@ -3,3 +3,8 @@ export * from './interfaces'; export { SwaggerConfiguration as Configuration } from './configuration'; export { SwaggerExplorer } from './swaggerExplorer'; export { SwaggerMiddleware } from './swaggerMiddleware'; +export { + renderSwaggerUIRemote, + renderSwaggerUIDist, + renderJSON, +} from './ui/render'; diff --git a/packages/swagger/src/interfaces/index.ts b/packages/swagger/src/interfaces/index.ts index 5e3fd3c9d690..6a926ddb2e96 100644 --- a/packages/swagger/src/interfaces/index.ts +++ b/packages/swagger/src/interfaces/index.ts @@ -3,7 +3,8 @@ * @see https://github.com/OAI/OpenAPI-Specification/blob/3.0.0-rc0/versions/3.0.md */ -import type { RouterOption } from '@midwayjs/core'; +import { RouterOption } from '@midwayjs/core'; +import { SwaggerExplorer } from '../swaggerExplorer'; export interface OpenAPIObject { openapi: string; @@ -424,4 +425,12 @@ export interface SwaggerOptions { webRouter: RouterOption ) => string; }; + + swaggerUIRender?: ( + config: SwaggerOptions, + swaggerExplorer: SwaggerExplorer, + swaggerRenderOptions?: any + ) => (pathname: string) => Promise<{ ext: string; content: any }>; + + swaggerUIRenderOptions?: Record; } diff --git a/packages/swagger/src/swaggerMiddleware.ts b/packages/swagger/src/swaggerMiddleware.ts index d6b7f305628f..fec0b6d35461 100644 --- a/packages/swagger/src/swaggerMiddleware.ts +++ b/packages/swagger/src/swaggerMiddleware.ts @@ -3,7 +3,6 @@ import { IMidwayApplication, IMidwayContext, NextFunction, - safeRequire, Config, Init, Inject, @@ -12,10 +11,8 @@ import { ScopeEnum, MidwayFrameworkType, MidwayEnvironmentService, - MidwayCommonError, + MidwayInvalidConfigPropertyError, } from '@midwayjs/core'; -import { readFileSync } from 'fs'; -import { join, extname } from 'path'; import type { SwaggerOptions } from './interfaces'; import { SwaggerExplorer } from './swaggerExplorer'; @@ -27,7 +24,9 @@ export class SwaggerMiddleware @Config('swagger') private swaggerConfig: SwaggerOptions; - private swaggerUiAssetPath: string; + private swaggerRender: ( + pathname: string + ) => Promise<{ ext: string; content: string }>; @Inject() private swaggerExplorer: SwaggerExplorer; @@ -37,115 +36,62 @@ export class SwaggerMiddleware @Init() async init() { - const { getAbsoluteFSPath } = safeRequire('swagger-ui-dist'); - if (getAbsoluteFSPath) { - this.swaggerUiAssetPath = getAbsoluteFSPath(); - } else { - throw new MidwayCommonError('swagger-ui-dist is not installed'); + if (typeof this.swaggerConfig.swaggerUIRender !== 'function') { + throw new MidwayInvalidConfigPropertyError('swagger.swaggerRender'); } + this.swaggerRender = this.swaggerConfig.swaggerUIRender( + this.swaggerConfig, + this.swaggerExplorer + ); } resolve(app: IMidwayApplication) { if (app.getFrameworkType() === MidwayFrameworkType.WEB_EXPRESS) { return async (req: any, res: any, next: NextFunction) => { const pathname = req.path; - if ( - !this.swaggerUiAssetPath || - pathname.indexOf(this.swaggerConfig.swaggerPath) === -1 - ) { - return next(); - } - const arr = pathname.split('/'); - let lastName = arr.pop(); - if (lastName === 'index.json') { - res.send(this.swaggerExplorer.getData()); - return; - } - if (!lastName) { - lastName = 'index.html'; - } + const renderResult = await this.swaggerRender(pathname); - let content: Buffer | string = readFileSync( - join(this.swaggerUiAssetPath, lastName) - ); - if ( - lastName === 'index.html' || - lastName === 'swagger-initializer.js' - ) { - content = content.toString('utf8'); - content = this.replaceInfo(content); - } - const ext = extname(lastName); - if (ext === '.js') { - res.type('application/javascript'); - } else if (ext === '.map') { - res.type('application/json'); - } else if (ext === '.css') { - res.type('text/css'); - } else if (ext === '.png') { - res.type('image/png'); + if (renderResult) { + const { ext, content } = renderResult; + if (ext === '.js') { + res.type('application/javascript'); + } else if (ext === '.map') { + res.type('application/json'); + } else if (ext === '.css') { + res.type('text/css'); + } else if (ext === '.png') { + res.type('image/png'); + } + res.send(content); + } else { + return next(); } - - res.send(content); }; } else { return async (ctx: IMidwayContext, next: NextFunction) => { const pathname = (ctx as any).path; - if ( - !this.swaggerUiAssetPath || - pathname.indexOf(this.swaggerConfig.swaggerPath) === -1 - ) { - return next(); - } - const arr = pathname.split('/'); - let lastName = arr.pop(); - if (lastName === 'index.json') { - (ctx as any).body = this.swaggerExplorer.getData(); - return; - } - if (!lastName) { - lastName = 'index.html'; - } + const renderResult = await this.swaggerRender(pathname); - let content: Buffer | string = readFileSync( - join(this.swaggerUiAssetPath, lastName) - ); - if ( - lastName === 'index.html' || - lastName === 'swagger-initializer.js' - ) { - content = content.toString('utf8'); - content = this.replaceInfo(content); - } - const ext = extname(lastName); - if (ext === '.js') { - (ctx as any).set('Content-Type', 'application/javascript'); - } else if (ext === '.map') { - (ctx as any).set('Content-Type', 'application/json'); - } else if (ext === '.css') { - (ctx as any).set('Content-Type', 'text/css'); - } else if (ext === '.png') { - (ctx as any).set('Content-Type', 'image/png'); - } + if (renderResult) { + const { ext, content } = renderResult; + if (ext === '.js') { + (ctx as any).set('Content-Type', 'application/javascript'); + } else if (ext === '.map') { + (ctx as any).set('Content-Type', 'application/json'); + } else if (ext === '.css') { + (ctx as any).set('Content-Type', 'text/css'); + } else if (ext === '.png') { + (ctx as any).set('Content-Type', 'image/png'); + } - (ctx as any).body = content; + (ctx as any).body = content; + } else { + return next(); + } }; } } - replaceInfo(content: string): string { - let str = `location.href.replace('${this.swaggerConfig.swaggerPath}/index.html', '${this.swaggerConfig.swaggerPath}/index.json'),\n validatorUrl: null,`; - if (this.swaggerConfig.displayOptions) { - Object.keys(this.swaggerConfig.displayOptions).forEach(key => { - str += `\n${key}: ${this.swaggerConfig.displayOptions[key]},`; - }); - } - return content.replace( - '"https://petstore.swagger.io/v2/swagger.json",', - str - ); - } - static getName() { return 'swagger'; } diff --git a/packages/swagger/src/ui/render.ts b/packages/swagger/src/ui/render.ts new file mode 100644 index 000000000000..243d86372e9c --- /dev/null +++ b/packages/swagger/src/ui/render.ts @@ -0,0 +1,102 @@ +import { MidwayCommonError, safeRequire } from '@midwayjs/core'; +import type { SwaggerOptions } from '../interfaces'; +import { readFileSync } from 'fs'; +import { extname, join } from 'path'; +import { SwaggerExplorer } from '../swaggerExplorer'; + +export function renderSwaggerUIDist( + swaggerConfig: SwaggerOptions, + swaggerExplorer: SwaggerExplorer +) { + const { getAbsoluteFSPath } = safeRequire('swagger-ui-dist'); + if (!getAbsoluteFSPath) { + throw new MidwayCommonError('swagger-ui-dist is not installed'); + } + + function replaceInfo(content: string): string { + let str = `location.href.replace('${swaggerConfig.swaggerPath}/index.html', '${swaggerConfig.swaggerPath}/index.json'),\n validatorUrl: null,`; + if (swaggerConfig.displayOptions) { + Object.keys(swaggerConfig.displayOptions).forEach(key => { + str += `\n${key}: ${swaggerConfig.displayOptions[key]},`; + }); + } + return content.replace( + '"https://petstore.swagger.io/v2/swagger.json",', + str + ); + } + + const swaggerUiAssetPath = getAbsoluteFSPath(); + + return async (pathname: string): Promise<{ ext: string; content: any }> => { + if ( + !swaggerUiAssetPath || + pathname.indexOf(swaggerConfig.swaggerPath) === -1 + ) { + return; + } + + const arr = pathname.split('/'); + let lastName = arr.pop(); + if (lastName === 'index.json') { + return { ext: 'json', content: swaggerExplorer.getData() }; + } + if (!lastName) { + lastName = 'index.html'; + } + + let content: Buffer | string = readFileSync( + join(swaggerUiAssetPath, lastName) + ); + if (lastName === 'index.html' || lastName === 'swagger-initializer.js') { + content = content.toString('utf8'); + content = replaceInfo(content); + } + const ext = extname(lastName); + return { ext, content }; + }; +} + +export function renderJSON( + swaggerConfig: SwaggerOptions, + swaggerExplorer: SwaggerExplorer +) { + return async (pathname: string) => { + if (pathname.indexOf(swaggerConfig.swaggerPath) === -1) { + return; + } + if (pathname === 'index.json') { + return { ext: 'json', content: swaggerExplorer.getData() }; + } + return; + }; +} + +export function renderSwaggerUIRemote( + swaggerConfig: SwaggerOptions, + swaggerExplorer: SwaggerExplorer, + swaggerRenderOptions: any +) { + const indexPagePath = + swaggerRenderOptions?.indexPagePath || join(__dirname, '../../index.html'); + return async (pathname: string) => { + if (pathname.indexOf(swaggerConfig.swaggerPath) === -1) { + return; + } + + const arr = pathname.split('/'); + let lastName = arr.pop(); + if (lastName === 'index.json') { + return { ext: 'json', content: swaggerExplorer.getData() }; + } + if (!lastName) { + lastName = 'index.html'; + } + + if (lastName === 'index.html') { + const content = readFileSync(indexPagePath, 'utf8'); + return { ext: 'html', content }; + } + return; + }; +} diff --git a/packages/swagger/test/fixtures/cats/src/configuration.ts b/packages/swagger/test/fixtures/cats/src/configuration.ts index 9f760c06ad59..02c6b14e76b4 100644 --- a/packages/swagger/test/fixtures/cats/src/configuration.ts +++ b/packages/swagger/test/fixtures/cats/src/configuration.ts @@ -1,6 +1,7 @@ import { Configuration } from '@midwayjs/core'; import * as koa from '@midwayjs/koa'; import * as swagger from '../../../../src'; +import { renderSwaggerUIDist } from '../../../../src'; @Configuration({ importConfigs: [{ @@ -10,6 +11,7 @@ import * as swagger from '../../../../src'; swagger: { auth: [{authType: 'basic', name: 'bbb'}, {authType: 'bearer', name: 'ttt'}], tagSortable: true, + swaggerUIRender: renderSwaggerUIDist, }, } }], diff --git a/packages/swagger/test/index.test.ts b/packages/swagger/test/index.test.ts index eecc1b6f2f4a..79681c75e48d 100644 --- a/packages/swagger/test/index.test.ts +++ b/packages/swagger/test/index.test.ts @@ -95,6 +95,10 @@ describe('/test/index.test.ts', () => { const body = result.body; expect(body).toMatchSnapshot(); console.log(JSON.stringify(body)); + + const result1 = await createHttpRequest(app).get('/swagger-ui/index.html'); + expect(result1.type).toEqual('text/html'); + expect(result1.text).toMatch(/window/); await close(app); }); From 03dc28ce338c964e090551ce1682b32c05451245 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Wed, 10 Jan 2024 17:39:42 +0800 Subject: [PATCH 06/11] feat: upgrade mikro to v6 (#3549) * chore: try to upgrade v6 * fix: create method for v6 * docs: add new feature * chore: add logger typings --- packages/mikro/package.json | 8 +- packages/mikro/src/configuration.ts | 8 +- packages/mikro/src/dataSourceManager.ts | 10 ++ packages/mikro/src/interface.ts | 4 +- .../fixtures/base-fn-origin/sqlite-schema.sql | 1 + .../src/config/config.default.ts | 22 +++- site/docs/extensions/mikro.md | 121 ++++++++++++++++-- .../current/extensions/mikro.md | 117 ++++++++++++++++- 8 files changed, 264 insertions(+), 27 deletions(-) diff --git a/packages/mikro/package.json b/packages/mikro/package.json index b3adf7fbf48d..0e36f5a046f4 100644 --- a/packages/mikro/package.json +++ b/packages/mikro/package.json @@ -11,11 +11,9 @@ "devDependencies": { "@midwayjs/core": "^3.13.7", "@midwayjs/mock": "^3.13.9", - "@mikro-orm/core": "5.9.7", - "@mikro-orm/entity-generator": "5.9.7", - "@mikro-orm/sql-highlighter": "1.0.1", - "@mikro-orm/sqlite": "5.9.7", - "sqlite3": "5.1.7" + "@mikro-orm/core": "6.0.2", + "@mikro-orm/entity-generator": "6.0.2", + "@mikro-orm/sqlite": "6.0.2" }, "author": { "name": "czy88840616", diff --git a/packages/mikro/src/configuration.ts b/packages/mikro/src/configuration.ts index 27813c9ba5fa..fcfea2cdd2d5 100644 --- a/packages/mikro/src/configuration.ts +++ b/packages/mikro/src/configuration.ts @@ -108,7 +108,13 @@ export class MikroConfiguration implements ILifeCycle { // https://mikro-orm.io/docs/identity-map this.applicationManager.getApplications().forEach(app => { app.useMiddleware(async (ctx, next) => { - return await RequestContext.createAsync(entityManagers, next); + if (RequestContext['createAsync']) { + // mikro-orm 5.x + return await RequestContext['createAsync'](entityManagers, next); + } else { + // mikro-orm 6.x + return await RequestContext.create(entityManagers, next); + } }); }); } diff --git a/packages/mikro/src/dataSourceManager.ts b/packages/mikro/src/dataSourceManager.ts index 4d8f66a62f83..f01f5c1944f0 100644 --- a/packages/mikro/src/dataSourceManager.ts +++ b/packages/mikro/src/dataSourceManager.ts @@ -6,6 +6,7 @@ import { Inject, ScopeEnum, DataSourceManager, + MidwayLoggerService, } from '@midwayjs/core'; import { MikroORM, IDatabaseDriver, Connection } from '@mikro-orm/core'; @@ -20,6 +21,9 @@ export class MikroDataSourceManager extends DataSourceManager< @Inject() baseDir: string; + @Inject() + loggerService: MidwayLoggerService; + @Init() async init() { await this.initDataSource(this.mikroConfig, this.baseDir); @@ -33,6 +37,12 @@ export class MikroDataSourceManager extends DataSourceManager< config: any, dataSourceName: string ): Promise>> { + if (config.logger && typeof config.logger === 'string') { + const logger = this.loggerService.getLogger(config.logger); + config.logger = message => { + logger.info(message); + }; + } return MikroORM.init(config); } diff --git a/packages/mikro/src/interface.ts b/packages/mikro/src/interface.ts index e14f66f34bb4..dbebbf8f42c2 100644 --- a/packages/mikro/src/interface.ts +++ b/packages/mikro/src/interface.ts @@ -1,4 +1,6 @@ import { Options, Configuration, IDatabaseDriver } from '@mikro-orm/core'; import { DataSourceManagerConfigOption } from '@midwayjs/core'; -export type MikroConfigOptions = DataSourceManagerConfigOption | Configuration>; +export type MikroConfigOptions = DataSourceManagerConfigOption<(Options | Configuration) & { + logger?: string | ((message: string) => void); +}>; diff --git a/packages/mikro/test/fixtures/base-fn-origin/sqlite-schema.sql b/packages/mikro/test/fixtures/base-fn-origin/sqlite-schema.sql index c7a2bbd2f9ef..bcd8bd328a6b 100644 --- a/packages/mikro/test/fixtures/base-fn-origin/sqlite-schema.sql +++ b/packages/mikro/test/fixtures/base-fn-origin/sqlite-schema.sql @@ -10,6 +10,7 @@ CREATE TABLE `terms_accepted` tinyint(1) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, + `age` integer DEFAULT NULL, `born` datetime DEFAULT NULL, `favourite_book_id` int(11) DEFAULT NULL ); diff --git a/packages/mikro/test/fixtures/base-fn-origin/src/config/config.default.ts b/packages/mikro/test/fixtures/base-fn-origin/src/config/config.default.ts index 5293724bc825..8a0e7a464f06 100644 --- a/packages/mikro/test/fixtures/base-fn-origin/src/config/config.default.ts +++ b/packages/mikro/test/fixtures/base-fn-origin/src/config/config.default.ts @@ -1,18 +1,34 @@ import { Author, BaseEntity, Book, BookTag, Publisher } from '../entity'; -import { SqlHighlighter } from '@mikro-orm/sql-highlighter'; import { join } from 'path'; +import { SqliteDriver } from '@mikro-orm/sqlite'; export default (appInfo) => { return { + midwayLogger: { + clients: { + mikroLogger: { + disableFile: false, + transports: { + console: { + autoColors: false, + }, + file: { + fileLogName: 'mikro.log', + } + }, + } + } + }, mikro: { dataSource: { default: { entities: [Author, Book, BookTag, Publisher, BaseEntity], dbName: join(__dirname, '../../test.sqlite'), - type: 'sqlite', - highlighter: new SqlHighlighter(), + driver: SqliteDriver, debug: true, allowGlobalContext: true, + logger: 'mikroLogger', + colors: false, } }, defaultDataSourceName: 'default', diff --git a/site/docs/extensions/mikro.md b/site/docs/extensions/mikro.md index 9f782abc17fa..18927038dc77 100644 --- a/site/docs/extensions/mikro.md +++ b/site/docs/extensions/mikro.md @@ -1,3 +1,6 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # MikroORM 本章节介绍用户如何在 midway 中使用 MikroORM。 MikroORM 是基于数据映射器、工作单元和身份映射模式的 Node.js 的 TypeScript ORM。 @@ -15,6 +18,14 @@ MikroORM 的官网文档在 [这里](https://mikro-orm.io/docs)。 | 包含独立日志 | ❌ | + +## 关于升级 + +* 从 `v3.14.0` 版本的组件开始,支持 mikro v5/v6 版本,由于 mikro v5 到 v6 有较大的变化,如从 mikro 老版本升级请提前阅读 [Upgrading from v5 to v6](https://mikro-orm.io/docs/upgrading-v5-to-v6) +* 组件示例已更新为 v6 版本 + + + ## 安装组件 @@ -31,7 +42,7 @@ $ npm i @midwayjs/mikro@3 @mikro-orm/core --save { "dependencies": { "@midwayjs/mikro": "^3.0.0", - "@mikro-orm/core": "^5.3.1", + "@mikro-orm/core": "^6.0.2", // ... }, "devDependencies": { @@ -48,10 +59,10 @@ $ npm i @midwayjs/mikro@3 @mikro-orm/core --save { "dependencies": { // sqlite - "@mikro-orm/sqlite": "^5.3.1", + "@mikro-orm/sqlite": "^6.0.2", // mysql - "@mikro-orm/mysql": "^5.3.1", + "@mikro-orm/mysql": "^6.0.2", }, "devDependencies": { // ... @@ -77,7 +88,7 @@ import { join } from 'path'; @Configuration({ imports: [ // ... - mikro // 加载 mikro 组件 + mikro // 加载 mikro 组件 ], importConfigs: [ join(__dirname, './config') @@ -167,11 +178,40 @@ export class Book extends BaseEntity { ### 配置数据源 +mikro v5 和 v6 略有不同。 + + + + ```typescript +// src/config/config.default +import { Author, BaseEntity, Book, BookTag, Publisher } from '../entity'; +import { join } from 'path'; +import { SqliteDriver } from '@mikro-orm/sqlite'; + +export default (appInfo) => { + return { + mikro: { + dataSource: { + default: { + entities: [Author, Book, BookTag, Publisher, BaseEntity], + dbName: join(__dirname, '../../test.sqlite'), + driver: SqliteDriver, // 这里使用了 sqlite 做示例 + allowGlobalContext: true, + } + } + } + } +} +``` + + + + +```typescript // src/config/config.default import { Author, BaseEntity, Book, BookTag, Publisher } from '../entity'; -import { SqlHighlighter } from '@mikro-orm/sql-highlighter'; import { join } from 'path'; export default (appInfo) => { @@ -182,28 +222,29 @@ export default (appInfo) => { entities: [Author, Book, BookTag, Publisher, BaseEntity], dbName: join(__dirname, '../../test.sqlite'), type: 'sqlite', // 这里使用了 sqlite 做示例 - highlighter: new SqlHighlighter(), - debug: true, allowGlobalContext: true, } } } } } - ``` + + + 如需以目录扫描形式关联,请参考 [数据源管理](../data_source)。 ### 增删查改 -在业务代码中,可以使用`InjectRepository`注入`Repository`对象执行简单的查询操作。其它的增删改操作可以通过配合`EntityManger`的`persist`和`flush`接口来实现,使用`InjectEntityManager`可以直接注入`EntityManager`对象,也可以通过`repository.getEntityManager()`获取。 +在业务代码中,可以使用 `InjectRepository` 注入 `Repository` 对象执行简单的查询操作。其它的增删改操作可以通过配合`EntityManger ` 的 `persist` 和 `flush` 接口来实现,使用 `InjectEntityManager` 可以直接注入 `EntityManager` 对象,也可以通过`repository.getEntityManager()`获取。 :::caution -从5.7版本开始,MikroORM将原来`Repository`上`persist`和`flush`等接口标为*弃用*,并计划在v6版本中[彻底移除](https://github.com/mikro-orm/mikro-orm/discussions/3989),建议直接调用`EntityManager`上的相关接口。 +* 1、从 5.7 版本开始,MikroORM 将原来 `Repository` 上 `persist` 和 `flush` 等接口标为*弃用*,并计划在 v6 版本中 [彻底移除](https://github.com/mikro-orm/mikro-orm/discussions/3989),建议直接调用`EntityManager`上的相关接口 +* 2、v6 已经彻底 [弃用](https://mikro-orm.io/docs/upgrading-v5-to-v6#removed-methods-from-entityrepository) 上述接口 ::: @@ -296,6 +337,66 @@ export class MainConfiguration { +### 日志 + +可以通过配置将 midway 的 logger 添加到 mikro 中,用于记录 sql 等信息。 + +```typescript +// src/config/config.default.ts +exporg default { + midwayLogger: { + clients: { + mikroLogger: { + // ... + } + } + }, + mikro: { + dataSource: { + default: { + entities: [Author, Book, BookTag, Publisher, BaseEntity], + // ... + logger: 'mikroLogger', + } + }, + } +} +``` + +默认情况下 mikro 自带颜色,也会将其写入文件,可以通过配置关闭。 + +```typescript +// src/config/config.default.ts +exporg default { + midwayLogger: { + clients: { + mikroLogger: { + transports: { + console: { + autoColors: false, + }, + file: { + fileLogName: 'mikro.log', + }, + }, + } + } + }, + mikro: { + dataSource: { + default: { + entities: [Author, Book, BookTag, Publisher, BaseEntity], + // ... + logger: 'mikroLogger', + colors: false, + } + }, + } +} +``` + + + ## 常见问题 diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/mikro.md b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/mikro.md index 2aa32f3487c4..3f3a4c3a793e 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/mikro.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/mikro.md @@ -1,3 +1,6 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # MikroORM This section describes how users use MikroORM in midway. MikroORM is the TypeScript ORM of Node.js based on data mapper, working unit and identity mapping mode. @@ -15,6 +18,14 @@ Related information: | Contains independent logs | ❌ | + +## About upgrade + +* Starting from the `v3.14.0` version of the component, mikro v5/v6 versions are supported. Since there are major changes from mikro v5 to v6, if you want to upgrade from an old version of mikro, please read [Upgrading from v5 to v6](https:/ /mikro-orm.io/docs/upgrading-v5-to-v6) +* Component examples updated to v6 + + + ## Installation Components @@ -31,7 +42,7 @@ Or reinstall the following dependencies in `package.json`. { "dependencies": { "@midwayjs/mikro": "^3.0.0", - "@mikro-orm/core": "^5.3.1 ", + "@mikro-orm/core": "^6.0.2", // ... }, "devDependencies": { @@ -48,10 +59,10 @@ For example: { "dependencies": { // sqlite - "@mikro-orm/sqlite": "^5.3.1 ", + "@mikro-orm/sqlite": "^6.0.2", // mysql - "@mikro-orm/mysql": "^5.3.1 ", + "@mikro-orm/mysql": "^6.0.2", }, "devDependencies": { // ... @@ -168,11 +179,41 @@ export class Book extends BaseEntity { ### Configure the data source +mikro v5 and v6 are slightly different. + + + + +```typescript +// src/config/config.default +import { Author, BaseEntity, Book, BookTag, Publisher } from '../entity'; +import { join } from 'path'; +import { SqliteDriver } from '@mikro-orm/sqlite'; + +export default (appInfo) => { + return { + mikro: { + dataSource: { + default: { + entities: [Author, Book, BookTag, Publisher, BaseEntity], + dbName: join(__dirname, '../../test.sqlite'), + driver: SqliteDriver, // SQLite is used as an example here. + allowGlobalContext: true, + } + } + } + } +} +``` + + + + + ```typescript // src/config/config.default import { Author, BaseEntity, Book, BookTag, Publisher } from '../entity'; -import { SqlHighlighter } from '@mikro-orm/sql-highlighter'; import { join } from 'path'; export default (appInfo) => { @@ -183,8 +224,6 @@ export default (appInfo) => { entities: [Author, Book, BookTag, Publisher, BaseEntity] dbName: join(__dirname, '../../test.sqlite') Type: 'sqlite', // SQLite is used as an example here. - highlighter: new SqlHighlighter() - debug: true allowGlobalContext: true } } @@ -194,6 +233,9 @@ export default (appInfo) => { ``` + + + For association in the form of a directory scan, please refer to [Data Source Management](../data_source). @@ -205,7 +247,8 @@ You can also get `EntityManager` by calling `repository.getEntityManger()`. :::caution -Since v5.7, `persist` and `flush` etc. on `Repository` (shortcuts to methods on `EntityManager`) were marked as *deprecated*, and [planned to remove them in v6](https://github.com/mikro-orm/mikro-orm/discussions/3989). Please use those APIs on `EntityManger` directly instead of on `Repository`. +* 1. Since v5.7, `persist` and `flush` etc. on `Repository` (shortcuts to methods on `EntityManager`) were marked as *deprecated*, and [planned to remove them in v6](https://github.com/mikro-orm/mikro-orm/discussions/3989). Please use those APIs on `EntityManger` directly instead of on `Repository`. +* 2. v6 has been completely [deprecated](https://mikro-orm.io/docs/upgrading-v5-to-v6#removed-methods-from-entityrepository) the above interface ::: @@ -298,6 +341,66 @@ export class MainConfiguration { +### Logging + +Midway's logger can be added to mikro through configuration to record SQL and other information. + +```typescript +// src/config/config.default.ts +exporg default { + midwayLogger: { + clients: { + mikroLogger: { + // ... + } + } + }, + mikro: { + dataSource: { + default: { + entities: [Author, Book, BookTag, Publisher, BaseEntity], + // ... + logger: 'mikroLogger', + } + }, + } +} +``` + +By default mikro comes with colors and also writes them to files, which can be turned off through configuration. + +```typescript +// src/config/config.default.ts +exporg default { + midwayLogger: { + clients: { + mikroLogger: { + transports: { + console: { + autoColors: false, + }, + file: { + fileLogName: 'mikro.log', + }, + }, + } + } + }, + mikro: { + dataSource: { + default: { + entities: [Author, Book, BookTag, Publisher, BaseEntity], + // ... + logger: 'mikroLogger', + colors: false, + } + }, + } +} +``` + + + ## Frequently Asked Questions From 780c41f729e49686e9832c806412bb23f124bcb7 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 13 Jan 2024 00:16:12 +0800 Subject: [PATCH 07/11] docs: add swagger doc (#3547) * docs: add swagger ui render doc * chore: update * docs: add blog --- packages/cache-manager/src/configuration.ts | 14 +- .../cache-manager/src/decorator/cacheKey.ts | 8 +- packages/cache-manager/src/index.ts | 5 +- .../src/service/user.service.ts | 10 + packages/cache-manager/test/index.test.ts | 13 + packages/core/src/error/framework.ts | 8 +- packages/swagger/src/config/config.default.ts | 9 +- packages/swagger/src/ui/render.ts | 39 +- packages/swagger/test/index.test.ts | 2 +- site/blog/2024-01-13-release-3.14.md | 152 +++++ site/docs/extensions/caching.md | 546 ++++++++++++++++++ site/docs/extensions/static_file.md | 17 + site/docs/extensions/swagger.md | 115 +++- site/docs/extensions/upload.md | 56 ++ site/docs/service_factory.md | 66 +++ .../current/extensions/caching.md | 542 +++++++++++++++++ .../current/extensions/static_file.md | 17 + .../current/extensions/swagger.md | 107 ++++ .../current/extensions/upload.md | 53 ++ .../current/service_factory.md | 66 +++ 20 files changed, 1828 insertions(+), 17 deletions(-) create mode 100644 site/blog/2024-01-13-release-3.14.md create mode 100644 site/docs/extensions/caching.md create mode 100644 site/i18n/en/docusaurus-plugin-content-docs/current/extensions/caching.md diff --git a/packages/cache-manager/src/configuration.ts b/packages/cache-manager/src/configuration.ts index 4da15e2b7b48..c0265add3f3c 100644 --- a/packages/cache-manager/src/configuration.ts +++ b/packages/cache-manager/src/configuration.ts @@ -1,5 +1,6 @@ import { Configuration, + getProviderUUId, ILifeCycle, IMidwayContainer, Inject, @@ -10,6 +11,10 @@ import { import { CACHE_DECORATOR_KEY } from './decorator/cacheKey'; import { CachingFactory } from './factory'; +export function getClassMethodDefaultCacheKey(target, methodName: string) { + return target.name + '-' + getProviderUUId(target) + '-' + methodName; +} + @Configuration({ namespace: 'cacheManager', importConfigs: [ @@ -32,7 +37,14 @@ export class CacheConfiguration implements ILifeCycle { // register @Caching decorator implementation this.decoratorService.registerMethodHandler( CACHE_DECORATOR_KEY, - ({ metadata }) => { + ({ target, propertyName, metadata }) => { + if (!metadata.cacheKey) { + metadata.cacheKey = getClassMethodDefaultCacheKey( + target, + propertyName + ); + } + return { around: async (joinPoint: JoinPoint) => { const cachingInstance = this.cacheService.get( diff --git a/packages/cache-manager/src/decorator/cacheKey.ts b/packages/cache-manager/src/decorator/cacheKey.ts index 6878c3ff5062..257988b3526c 100644 --- a/packages/cache-manager/src/decorator/cacheKey.ts +++ b/packages/cache-manager/src/decorator/cacheKey.ts @@ -11,12 +11,16 @@ export type CachingDecoratorKeyOptions = export function Caching( cacheInstanceName: string, - cacheKey: CachingDecoratorKeyOptions, + cacheKeyOrTTL?: CachingDecoratorKeyOptions | number, ttl?: number ) { + if (typeof cacheKeyOrTTL === 'number') { + ttl = cacheKeyOrTTL; + cacheKeyOrTTL = undefined; + } return createCustomMethodDecorator(CACHE_DECORATOR_KEY, { cacheInstanceName, - cacheKey, + cacheKey: cacheKeyOrTTL, ttl, }); } diff --git a/packages/cache-manager/src/index.ts b/packages/cache-manager/src/index.ts index 828bf2582284..c7f7d42a61a2 100644 --- a/packages/cache-manager/src/index.ts +++ b/packages/cache-manager/src/index.ts @@ -1,4 +1,7 @@ -export { CacheConfiguration as Configuration } from './configuration'; +export { + CacheConfiguration as Configuration, + getClassMethodDefaultCacheKey, +} from './configuration'; export * from './interface'; export * from './factory'; export * from './decorator/cacheKey'; diff --git a/packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts b/packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts index fc6f497969e2..6f57938c7514 100644 --- a/packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts +++ b/packages/cache-manager/test/fixtures/cache-decorator/src/service/user.service.ts @@ -4,6 +4,16 @@ import { Caching } from '../../../../../src'; @Provide() export class UserService { + @Caching('default') + async getDefaultUser(name: string){ + return 'hello ' + name; + } + + @Caching('default', 100) + async getDefaultUserWithTTL(name: string){ + return 'hello ttl ' + name; + } + @Caching('default', 'abc', 10) async getUser(name: string){ return name; diff --git a/packages/cache-manager/test/index.test.ts b/packages/cache-manager/test/index.test.ts index 0bb78e75c48b..9ff377ae3bfb 100644 --- a/packages/cache-manager/test/index.test.ts +++ b/packages/cache-manager/test/index.test.ts @@ -39,6 +39,19 @@ describe(`index.test.ts`, ()=>{ const appCtx = app.getApplicationContext(); const userService: any = await appCtx.getAsync('userService'); + // cache with auto generate cache key + expect((await userService.getDefaultUser('harry'))).toEqual('hello harry'); + expect((await userService.getDefaultUser('harry1'))).toEqual('hello harry'); + expect((await userService.getDefaultUser('harry2'))).toEqual('hello harry'); + + // cache with auto generate cache key and ttl + await sleep(20); + expect((await userService.getDefaultUserWithTTL('harry'))).toEqual('hello ttl harry'); + expect((await userService.getDefaultUserWithTTL('harry1'))).toEqual('hello ttl harry'); + await sleep(100); + expect((await userService.getDefaultUserWithTTL('harry2'))).toEqual('hello ttl harry2'); + + // custom cache key expect((await userService.getUser('harry'))).toEqual('harry'); expect((await userService.getUser('harry1'))).toEqual('harry'); expect((await userService.getUser('harry2'))).toEqual('harry'); diff --git a/packages/core/src/error/framework.ts b/packages/core/src/error/framework.ts index 7605d4006f91..286566f3ff4c 100644 --- a/packages/core/src/error/framework.ts +++ b/packages/core/src/error/framework.ts @@ -221,9 +221,13 @@ export class MidwayMainFrameworkMissingError extends MidwayError { } export class MidwayInvalidConfigPropertyError extends MidwayError { - constructor(propertyName: string) { + constructor(propertyName: string, allowTypes?: string[]) { super( - `Invalid config property "${propertyName}", please check your configuration.`, + `Invalid config property "${propertyName}", ${ + allowTypes + ? `only ${allowTypes.join(',')} can be set` + : 'please check your configuration' + }.`, FrameworkErrorEnum.INVALID_CONFIG_PROPERTY ); } diff --git a/packages/swagger/src/config/config.default.ts b/packages/swagger/src/config/config.default.ts index 436612c9a464..02e1f6c845d3 100644 --- a/packages/swagger/src/config/config.default.ts +++ b/packages/swagger/src/config/config.default.ts @@ -1,10 +1,15 @@ import { SwaggerOptions } from '../interfaces'; -import { renderSwaggerUIRemote } from '../ui/render'; +import { renderSwaggerUIRemote, renderSwaggerUIDist } from '../ui/render'; +import { safeRequire } from '@midwayjs/core'; +const { getAbsoluteFSPath } = safeRequire('swagger-ui-dist'); + export const swagger: SwaggerOptions = { title: 'My Project', description: 'This is a swagger-ui for midwayjs project', version: '1.0.0', swaggerPath: '/swagger-ui', - swaggerUIRender: renderSwaggerUIRemote, + swaggerUIRender: getAbsoluteFSPath + ? renderSwaggerUIDist + : renderSwaggerUIRemote, swaggerUIRenderOptions: {}, }; diff --git a/packages/swagger/src/ui/render.ts b/packages/swagger/src/ui/render.ts index 243d86372e9c..5f4f97659a91 100644 --- a/packages/swagger/src/ui/render.ts +++ b/packages/swagger/src/ui/render.ts @@ -1,18 +1,39 @@ -import { MidwayCommonError, safeRequire } from '@midwayjs/core'; +import { + MidwayCommonError, + MidwayInvalidConfigPropertyError, + safeRequire, +} from '@midwayjs/core'; import type { SwaggerOptions } from '../interfaces'; import { readFileSync } from 'fs'; -import { extname, join } from 'path'; +import { extname, join, isAbsolute } from 'path'; import { SwaggerExplorer } from '../swaggerExplorer'; export function renderSwaggerUIDist( swaggerConfig: SwaggerOptions, - swaggerExplorer: SwaggerExplorer + swaggerExplorer: SwaggerExplorer, + swaggerRenderOptions?: { + customInitializer?: Buffer | string; + } ) { const { getAbsoluteFSPath } = safeRequire('swagger-ui-dist'); if (!getAbsoluteFSPath) { throw new MidwayCommonError('swagger-ui-dist is not installed'); } + swaggerRenderOptions = swaggerRenderOptions || {}; + if (swaggerRenderOptions.customInitializer) { + if (isAbsolute(swaggerRenderOptions.customInitializer as string)) { + swaggerRenderOptions.customInitializer = readFileSync( + swaggerRenderOptions.customInitializer as string + ); + } else { + throw new MidwayInvalidConfigPropertyError( + 'swagger.swaggerRenderOptions.customInitializer', + ['Buffer', 'String'] + ); + } + } + function replaceInfo(content: string): string { let str = `location.href.replace('${swaggerConfig.swaggerPath}/index.html', '${swaggerConfig.swaggerPath}/index.json'),\n validatorUrl: null,`; if (swaggerConfig.displayOptions) { @@ -48,6 +69,14 @@ export function renderSwaggerUIDist( let content: Buffer | string = readFileSync( join(swaggerUiAssetPath, lastName) ); + + if ( + lastName === 'swagger-initializer.js' && + swaggerRenderOptions?.customInitializer + ) { + return { ext: 'js', content: swaggerRenderOptions.customInitializer }; + } + if (lastName === 'index.html' || lastName === 'swagger-initializer.js') { content = content.toString('utf8'); content = replaceInfo(content); @@ -75,7 +104,9 @@ export function renderJSON( export function renderSwaggerUIRemote( swaggerConfig: SwaggerOptions, swaggerExplorer: SwaggerExplorer, - swaggerRenderOptions: any + swaggerRenderOptions?: { + indexPagePath?: string; + } ) { const indexPagePath = swaggerRenderOptions?.indexPagePath || join(__dirname, '../../index.html'); diff --git a/packages/swagger/test/index.test.ts b/packages/swagger/test/index.test.ts index 79681c75e48d..17df5eb62287 100644 --- a/packages/swagger/test/index.test.ts +++ b/packages/swagger/test/index.test.ts @@ -98,7 +98,7 @@ describe('/test/index.test.ts', () => { const result1 = await createHttpRequest(app).get('/swagger-ui/index.html'); expect(result1.type).toEqual('text/html'); - expect(result1.text).toMatch(/window/); + expect(result1.text).toMatch(/html/); await close(app); }); diff --git a/site/blog/2024-01-13-release-3.14.md b/site/blog/2024-01-13-release-3.14.md new file mode 100644 index 000000000000..22ae066770d7 --- /dev/null +++ b/site/blog/2024-01-13-release-3.14.md @@ -0,0 +1,152 @@ +--- +slug: release/3.14.0 +title: Release 3.14.0 +authors: [harry] +tags: [release] + + +--- + +2024 新年快乐。 + +升级请参考 [如何更新 Midway](/docs/how_to_update_midway) 中描述,请不要单独升级某个组件包。 + +本次 3.14 版本,重写了 Cache 组件,同时也带来了一些新的特性,我们将一一介绍。 + + + +## 全新的 CacheManager 组件 + +这一版本,Midway 将底层依赖的 `cache-manager` 模块升级到了 v5 版本,由于存在 Breaking Change,启用了一个新的 `@midwayjs/cache-manager` 组件,原有 `@midwayjs/cache` 组件不再更新。 + +在新组件中,提供了装饰器 `@Caching` 用于快速缓存函数结果。 + +比如: + +```typescript +@Caching('default', 100) +async invokeData(name: string) { + return name; +} +``` + +那么,在多次调用时就会缓存返回值,如果超时,则会返回新的值。 + +```typescript +await invokeData('harry'); // => harry +await invokeData('harry1'); // => harry +await invokeData('harry2'); // => harry +await sleep(100); +await invokeData('harry3'); // => harry3 +``` + +这在一些性能敏感的场景会非常有用。 + +此外,`@Caching` 装饰器还支持多级缓存,如果一个缓存实例配置了多个 Store,那么它将自动根据缓存的顺序进行处理。 + +最重要的一点,组件通过新设计器的 `createStore` 方法支持复用 Redis 组件的配置了。 + +```typescript +import { createStore } from '@midwayjs/cache-manager-redis'; + +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: createStore('default'), + // ... + }, + }, + }, + redis: { + clients: { + default: { + port: 6379, + host: '127.0.0.1', + } + } + } +} +``` + +这下不再需要配置 Redis 多遍了。 + +更多功能可以接着查看 [文档](/docs/extensions/caching)。 + + + +## Swagger 组件的全新渲染方式 + +由于之前的版本无法传递 `swagger-ui` 参数,这个版本设计了一组新的 UI 渲染方式,尽可能的帮助开发者自定义 UI。 + +现在,通过 `renderSwaggerUIDist` ,`renderJSON` 和 `renderSwaggerUIRemote` 方法,你可以选择自己喜欢的方式进行渲染。 + +```typescript +// src/config/config.default.ts +import { renderSwaggerUIRemote } from '@midwayjs/swagger'; + +export default { + // ... + swagger: { + swaggerUIRender: renderSwaggerUIRemote, + swaggerUIRenderOptions: { + // ... + } + }, +} +``` + +只要网络环境允许,即使不再引用 `swagger-ui-dist` 包,也可以通过 CDN 资源加载 UI,进一步减轻服务端压力。 + +也可以仅输出 `Swagger JSON` 内容,不提供 UI,这都可以根据业务随心配置。 + + + +## 服务工厂实例支持优先级 + +这一版本可以针对创建出的实例设置不同的实例优先级。 + +比如: + +```typescript +// config.default.ts +import { DEFAULT_PRIORITY } from '@midwayjs/core'; + +export default { + redis: { + clients: { + instance1: { + // ... + }, + instance2: { + // ... + } + }, + priority: { + instance1: DEFAULT_PRIORITY.L1, + instance2: DEFAULT_PRIORITY.L3, + } + } +} +``` + +实例优先级不影响实例本身的功能,仅对实现了优先级判断的外部逻辑生效。 + +比如在健康检查时可以根据优先级进行忽略,低优先级的实例等价于弱依赖,会跳过健康检查。 + +这一功能目前仅有 `HealthService` 在使用,理论上别的逻辑也可能会用到,我们期待更多的可能性。 + + + +## 更多的变化 + +* [mikro-orm](https://github.com/mikro-orm/mikro-orm/releases) 于 2024.01.08 日发布了 v6 版本,现在 `@midwayjs/mikro` 组件也可以配合 v6 版本使用了 +* `@midwayjs/redis` 增加了基于实例优先级的健康检查逻辑 +* 服务工厂增加了 `getClients()` 和 `getClientKeys()` 方法,用于快速迭代实例 +* 修复了 swagger 组件 ApiOperation 相关的一些问题 +* mwtsc 工具修复了 windows 下开发的问题 + + + +此外,还有一大批依赖进行了更新,更多可以参考我们的 [ChangeLog](https://midwayjs.org/changelog/v3.14.0)) diff --git a/site/docs/extensions/caching.md b/site/docs/extensions/caching.md new file mode 100644 index 000000000000..5c8ea2a37d15 --- /dev/null +++ b/site/docs/extensions/caching.md @@ -0,0 +1,546 @@ +# 缓存 + +缓存是一个伟大而简单的技术,有助于提高你的应用程序的性能。本组件提供了缓存相关的能力,你可以将数据缓存到不同的数据源,也可以针对不同场景建立多级缓存,提高数据访问速度。 + +:::tip + +Midway 提供基于 [cache-manager v5](https://github.com/node-cache-manager/node-cache-manager) 模块重新封装了缓存组件,原有的缓存模块基于 v3 开发不再迭代,如需查看老文档,请访问 [这里](/docs/cache)。 + +::: + +相关信息: + +| 描述 | | +| ----------------- | ---- | +| 可用于标准项目 | ✅ | +| 可用于 Serverless | ✅ | +| 可用于一体化 | ✅ | +| 包含独立主框架 | ❌ | +| 包含独立日志 | ❌ | + + + +## 安装 + +首先安装相关的组件模块。 + +```bash +$ npm i @midwayjs/cache-manager@3 --save +``` + +或者在 `package.json` 中增加如下依赖后,重新安装。 + +```json +{ + "dependencies": { + "@midwayjs/cache-manager": "^3.0.0", + // ... + }, +} +``` + + + +## 启用组件 + + +首先,引入组件,在 `configuration.ts` 中导入: + +```typescript +import { Configuration } from '@midwayjs/core'; +import * as cacheManager from '@midwayjs/cache-manager'; +import { join } from 'path' + +@Configuration({ + imports: [ + // ... + cacheManager, + ], + importConfigs: [ + join(__dirname, 'config') + ] +}) +export class MainConfiguration { +} +``` + + + +## 使用缓存 + + + +### 1、配置缓存 + +在使用前,你需要配置缓存所在的位置,比如内置的内存缓存,或者是引入 Redis 缓存,每个缓存对应了一个缓存的 Store。 + +下面的示例代码,配置了一个名为 `default` 的内存缓存。 + +```typescript +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + }, + }, + } +} +``` + +最常用的场景下,缓存会包含两个参数,配置 `max` 修改缓存的数量,配置 `ttl` 修改缓存的过期时间,单位毫秒。 + +```typescript + // src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + options: { + max: 100, + ttl: 10, + }, + }, + }, + } +} +``` + +:::tip + +内存缓存使用的淘汰算法是 LRU。 + +::: + +### 2、使用缓存 + +可以通过服务工厂的装饰器获取到实例,可以通过简单的 `get` 和 `set` 方法获取和保存缓存。 + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + + @InjectClient(CachingFactory, 'default') + cache: MidwayCache; + + async invoke(name: string, value: string) { + // 设置缓存 + await this.cache.set(name, value); + // 获取缓存 + const data = await this.cache.get(name); + // ... + } +} + +``` + +动态设置 `ttl` 过期时间。 + +```typescript +await this.cache.set('key', 'value', 1000); +``` + +若要禁用缓存过期,可以将 `ttl` 配置属性设置为 0。 + +```typescript +await this.cache.set('key', 'value', 0); +``` + +删除单个缓存。 + +```typescript +await this.cache.del('key'); +``` + +清理整个缓存,可以使用 `reset` 方法。 + +```typescript +await this.cacheManager.reset(); +``` + +:::danger + +注意,清理整个缓存非常危险,如果使用了 Redis 作为缓存 store,将清空整个 Redis 数据。 + +::: + +除了装饰器之外,也可以通过 API 获取缓存实例。 + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + + @Inject() + cachingFactory: CachingFactory; + + async invoke() { + const caching = await this.cachingFactory.get('default'); + // ... + } +} +``` + + + +### 3、配置多个缓存 + +和其他组件一样,组件支持配置多个缓存实例。 + +```typescript +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + }, + otherCaching: { + store: 'memory', + } + }, + } +} +``` + +可以注入不同的缓存实例。 + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + + @InjectClient(CachingFactory, 'default') + cache: MidwayCache; + + @InjectClient(CachingFactory, 'otherCaching') + customCaching: MidwayCache; + +} + +``` + + + +### 4、配置不同 Store + +组件基于 [cache-manager](https://github.com/node-cache-manager/node-cache-manager) 可以配置不同的缓存 Store,比如最常见的可以配置 Redis Store。 + +为了减少重复配置,可以使用 `@midwayjs/cache-manager-redis` 模块,复用 Redis 组件的配置。 + +```bash +$ npm i @midwayjs/cache-manager-redis --save +``` + +假如项目已经配置了一个 `Redis` 。 + +```typescript +import { createStore } from '@midwayjs/cache-manager-redis'; + +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: createStore('default'), + options: { + ttl: 10, + } + }, + }, + }, + redis: { + clients: { + default: { + port: 6379, + host: '127.0.0.1', + } + } + } +} +``` + +`createStore` 方法可以传递一个已经配置的 redis 实例名,可以和 redis 组件复用实例。 + + + +### 5、配置三方 Store + +除了 Redis 之外,用户也可以自行选择 Cache-Manager 的 Store,列表可以参考 [这里](https://github.com/node-cache-manager/node-cache-manager?tab=readme-ov-file#store-engines)。 + +下面是一个配置 [node-cache-manager-ioredis-yet](https://github.com/node-cache-manager/node-cache-manager-ioredis-yet) 的例子。 + +```typescript +// src/config/config.default.ts +import { redisStore } from 'cache-manager-ioredis-yet'; + +export default { + cacheManager: { + clients: { + default: { + store: redisStore, + options: { + port: 6379, + host: 'localhost', + ttl: 10, + }, + }, + }, + } +} +``` + + + +### 6、多级缓存 + +[cache-manager](https://github.com/node-cache-manager/node-cache-manager) 支持将多个缓存 Store 聚合到一起,实现多级缓存。 + +比如我可以创建一个多级缓存将多个缓存 Store 合并到一起。 + +```typescript +// src/config/config.default.ts +import { createStore } from '@midwayjs/cache-manager-redis'; +export default { + cacheManager: { + clients: { + memoryCaching: { + store: 'memory', + }, + redisCaching: { + store: createStore('default'), + options: { + ttl: 10, + } + }, + multiCaching: { + store: [ + 'memoryCaching', + 'redisCaching' + ], + options: { + ttl: 100, + }, + }, + }, + }, + redis: { + clients: { + default: { + port: 6379, + host: '127.0.0.1', + } + } + } +} +``` + +这样 `multiCaching` 这个缓存实例就包含了两级缓存,缓存的优先级从上到下,在查找时,会先查找 `memoryCaching` ,如果内存缓存不存在 key,则继续查找 `redisCaching`。 + + + +### 7、使用多级缓存 + +和普通缓存类似,多级缓存除了 `set`、`get`、`del`方法外,还增加了 `mset` 、`mget`、`mdel` 方法。 + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayMultiCache } from '@midwayjs/cache-manager'; + +const userId2 = 456; +const key2 = 'user_' + userId; +const ttl = 5; + +@Provide() +export class UserService { + + @InjectClient(CachingFactory, 'multiCaching') + multiCache: MidwayMultiCache; + + async invoke() { + // 设置到所有级别的缓存 + await this.multiCache.set('foo2', 'bar2', ttl); + + // 从最高优先级的缓存 Store 中获取 key + console.log(await this.multiCache.get('foo2')); + // >> "bar2" + + // 调用每一个 Store 的 del 方法进行删除 + await this.multiCache.del('foo2'); + + // 在所有缓存中设置多个 key,可以多个键值对 + await this.multiCache.mset( + [ + ['foo', 'bar'], + ['foo2', 'bar2'], + ], + ttl + ); + + // mget() 从最高优先级的缓存中获取值 + // 如果第一个缓存 Store 中不包含所有的 key, + // 继续在下一个缓存 Store 中查找没有找到的 key。 + // 这是递归地完成的,直到: + // - 所有的 key 都已经查找到值 + // - 所有的缓存 Store 都被查找过 + console.log(await this.multiCache.mget('key', 'key2')); + // >> ['bar', 'bar2'] + + // 调用每一个 Store 的 mdel 方法进行删除 + await this.multiCache.mdel('foo', 'foo2'); + } +} + +``` + +### 8、自动刷新 + +不管是普通缓存还是多级缓存,都支持后台刷新功能,只需要配置 `refreshThreshold` 的时间,单位为毫秒。 + +```typescript +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + refreshThreshold: 3 * 1000, + }, + }, + } +} +``` + +如果设置了 `refreshthreshold`,每次从缓存获取值之后,会检查`ttl` 的值,如果剩余的 `ttl` 小于 `refreshthreshold` ,则系统将异步更新缓存,同时系统会返回旧值,直到 `ttl` 过期。 + +:::tip + +* 在多级缓存的情况下,根据优先级顺序找到第一个包含 key 的 Store。 + +* 如果阈值较低且执行的函数比较慢,key 可能会过期,有可能会遇到并发更新的情况。 + +* 后台刷新机制目前只支持单个 key。 + +* 如果没有为 key 设置 `ttl`,则不会触发刷新机制。对于 redis,`ttl` 默认设置为-1。 + +::: + + + +## 自动缓存 + +### 使用装饰器缓存方法 + +可以通过 `@Caching` 装饰器缓存方法的结果,比如缓存 http 响应或者服务调用的结果。 + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + @Caching('default') + async getUser(name: string) { + return name; + } +} + +``` + +当第一次调用 `getUser` 方法时,会正常执行逻辑,返回结果,装饰器会将结果缓存起来,第二次执行时,如果缓存未失效,则会从缓存中直接返回。 + +### 指定缓存的 ttl + +也可以单独设置 `ttl` 。 + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + @Caching('default', 100) + async getUser(name: string) { + return name; + } +} +``` + +### 手动指定缓存 key + +如果对自动生成的 key 不满意,可以手动指定缓存的 key。 + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + @Caching('default', 'customKey', 100) + async getUser(name: string) { + return name; + } +} +``` + +### 带逻辑的缓存 + +如果你希望根据一些特定逻辑进行缓存,比如特定参数,或者特定的 Header,可以传递一个工具函数进行逻辑判断。 + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +function cacheBy({methodArgs, ctx, target}) { + if (methodArgs[0] === 'harry' || methodArgs[0] === 'mike') { + return 'cache1'; + } +} + +@Provide() +export class UserService { + @Caching('default', cacheBy, 100) + async getUser(name: string) { + return 'hello ' + name; + } +} +``` + +上面的示例中,`cacheBy` 方法自定义了缓存的逻辑,当方法入参值为 `harry` 或者 `mike` 时,将返回缓存的 `key` ,而其他参数时则跳过缓存。 + +这个时候执行的结果为: + +```typescript +await userService.getUser('harry')); // hello harry +await userService.getUser('mike')); // hello harry +await userService.getUser('lucy')); // hello lucy +``` + +`@Caching` 装饰器可以在第二个参数中传递一个方法,这个方法的入参 options 为: + +* `methodArgs` 当前调用方法的实际参数 +* `ctx` 如果是请求作用域,则是当前调用的上下文对象,如果是单例,则该对象为空对象 +* `target` 当前调用的实例 + +方法的返回值为字符串或者布尔值,当返回字符串时,表示以该 key 将方法的结果缓存,当返回 `undefined` 或者 `null` 时,表示跳过缓存。 + +通过这些参数判断,我们可以实现非常灵活的自定义缓存逻辑。 + + + +## 常见问题 + + + +### 1、多进程下内存缓存 set 和 get 无法得到相同值 + +这是正常现象,每个进程的数据是独立的,仅保存在当前进程中。如需跨进程缓存,请使用 Redis 这类分布式缓存系统。 diff --git a/site/docs/extensions/static_file.md b/site/docs/extensions/static_file.md index 81d169c4e9a1..c6b148fa6600 100644 --- a/site/docs/extensions/static_file.md +++ b/site/docs/extensions/static_file.md @@ -207,3 +207,20 @@ export default { }, } ``` + + + +### 3、egg(@midwayjs/web)下不生效的情况 + +由于 egg 自带了静态托管插件,如果开启了 static 插件,会和此组件冲突。 + +如需使用本组件,请务必关闭 egg 插件。 + +```typescript +// src/config/plugin.ts +import { EggPlugin } from 'egg'; +export default { + // ... + static: false, +} as EggPlugin; +``` diff --git a/site/docs/extensions/swagger.md b/site/docs/extensions/swagger.md index a17975ae3efe..23856a32d93e 100644 --- a/site/docs/extensions/swagger.md +++ b/site/docs/extensions/swagger.md @@ -20,7 +20,7 @@ $ npm install @midwayjs/swagger@3 --save $ npm install swagger-ui-dist --save-dev ``` -如果想要在服务器上输出 Swagger API 页面,则需要将 swagger-ui-dist 安装到依赖中。 +如果想要在服务器上输出 Swagger API 页面,则需要将 `swagger-ui-dist` 安装到依赖中。 ```bash $ npm install swagger-ui-dist --save @@ -65,7 +65,7 @@ export class MainConfiguration { } ``` -可以配置启用的环境,比如下面的代码指的是“只在 local 环境下启用”。 +可以配置启用的环境,比如下面的代码指的是 **只在 local 环境下启用**。 ```typescript import { Configuration } from '@midwayjs/core'; @@ -677,7 +677,9 @@ findOne(@Param('id') id: string, @Query('test') test: any): ViewCat { -## 高级用法 + + +## 更多配置 ### 路由标签 Swagger 会对 paths 分标签,如果 Controller 未定义任何标签,则会默认归组到 default 下。可以通过 ```@ApiTags([...])``` 来自定义 Controller 标签。 @@ -900,7 +902,7 @@ export class HelloController {} -## 参数配置 +### 完整参数配置 Swagger 组件提供了和 [OpenAPI](https://swagger.io/specification/) 一致的参数配置能力,可以通过自定义配置来实现。 @@ -1056,6 +1058,111 @@ export interface AuthOptions extends Omit { +## UI 渲染 + +### 从 Swagger-ui-dist 渲染 + +默认情况下,如果安装了 `swagger-ui-dist` 包,组件会默认会调用 `renderSwaggerUIDist` 渲染 swagger ui,如果需要传递 swagger-ui 的 options,可以 通过 `swaggerUIRenderOptions` 选项。 + +```typescript +// src/config/config.default.ts +import { renderSwaggerUIDist } from '@midwayjs/swagger'; + +export default { + // ... + swagger: { + swaggerUIRender: renderSwaggerUIDist, + swaggerUIRenderOptions: { + // ... + } + }, +} +``` + +如果希望调整 UI 的配置,可以使用自定义文件的方式替换默认的 `swagger-initializer.js`。 + +```typescript +// src/config/config.default.ts +import { AppInfo } from '@midwayjs/core'; +import { renderSwaggerUIDist } from '@midwayjs/swagger'; +import { join } from 'path'; + +export default (appInfo: AppInfo) { + return { + // ... + swagger: { + swaggerUIRender: renderSwaggerUIDist, + swaggerUIRenderOptions: { + customInitializer: join(appInfo.appDir, 'resource/swagger-initializer.js'), + } + }, + } +} +``` + +自定义的 `swagger-initializer.js` 内容大致如下: + +```javascript +window.onload = function() { + window.ui = SwaggerUIBundle({ + url: "/index.json", + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout", + persistAuthorization: true, + }); +}; + +``` + +其中的 url 指向当前的 swagger json,可以自行修改,完整的 `swagger-ui` 配置请参考 [这里](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md)。 + +### 从 unpkg 等 CDN 地址渲染 + +如果未安装 `swagger-ui-dist` 包,会自动使用 `renderSwaggerUIRemote` 方法进行渲染,默认由 `unpkg.com` 提供 cdn 资源。 + +```typescript +// src/config/config.default.ts +import { renderSwaggerUIRemote } from '@midwayjs/swagger'; + +export default { + // ... + swagger: { + swaggerUIRender: renderSwaggerUIRemote, + swaggerUIRenderOptions: { + // ... + } + }, +} +``` + + + +### 仅提供 Swagger JSON + +如果仅希望提供 Swagger JSON,可以配置 `renderJSON` 仅渲染 JSON ,无需引入 `swagger-ui-dist` 包。 + +```typescript +// src/config/config.default.ts +import { renderJSON } from '@midwayjs/swagger'; + +export default { + // ... + swagger: { + swaggerUIRender: renderJSON, + }, +} +``` + + + ## 常见问题 ### `@Get` 等路由注解中的 `summary` 或者 `description` 不生效 diff --git a/site/docs/extensions/upload.md b/site/docs/extensions/upload.md index fbd23d981227..2dbd45f65181 100644 --- a/site/docs/extensions/upload.md +++ b/site/docs/extensions/upload.md @@ -230,6 +230,36 @@ faas 场景实现方式视平台而定,如果平台不支持流式请求/响 另外,midway 上传组件,为了避免部分 `恶意用户`,通过某些技术手段来`伪造`一些可以被截断的扩展名,所以会对获取到的扩展名的二进制数据进行过滤,仅支持 `0x2e`(即英文点 `.`)、`0x30-0x39`(即数字 `0-9`)、`0x61-0x7a`(即小写字母 `a-z`) 范围内的字符作为扩展名,其他字符将会被自动忽略。 +从 v3.14.0 开始,你可以传递一个函数,可以根据不同的条件动态返回白名单。 + +```typescript +// src/config/config.default.ts +import { uploadWhiteList } from '@midwayjs/upload'; +import { tmpdir } from 'os'; +import { join } from 'path'; + +export default { + // ... + upload: { + whitelist: (ctx) => { + if (ctx.path === '/') { + return [ + '.jpg', + '.jpeg', + ]; + } else { + return [ + '.jpg', + ] + }; + }, + // ... + }, +} +``` + + + ### MIME 类型检查 @@ -289,6 +319,32 @@ MIME 类型校验规则仅适用于使用 文件上传模式 `mode=file`,同 ::: +从 v3.14.0 开始,你可以传递一个函数,可以根据不同的条件动态返回 MIME 规则。 + +```typescript +// src/config/config.default.ts +import { tmpdir } from 'os'; +import { join } from 'path'; + +export default { + // ... + upload: { + mimeTypeWhiteList: (ctx) => { + if (ctx.path === '/') { + return { + '.jpg': 'image/jpeg', + }; + } else { + return { + '.jpeg': ['image/jpeg', 'image/png'], + } + }; + } + }, +} + +``` + ### 配置 match 或 ignore diff --git a/site/docs/service_factory.md b/site/docs/service_factory.md index 28f77fb03e46..ab1893208945 100644 --- a/site/docs/service_factory.md +++ b/site/docs/service_factory.md @@ -445,3 +445,69 @@ export class UserService { } ``` + + +## 实例优先级 + +从 v3.14.0 开始,服务工厂的实例可以增加一个优先级属性,在不同的场景,会根据优先级做一些不同处理。 + +实例的优先级有 `L1`,`L2`, `L3`三个等级,分别对应高,中,低三个层级。 + +定义如下: + +```typescript +export const DEFAULT_PRIORITY = { + L1: 'High', + L2: 'Medium', + L3: 'Low', +}; +``` + +通过配置,我们可以指定不同实例的优先级。 + +```typescript +// config.default.ts +import { DEFAULT_PRIORITY } from '@midwayjs/core'; + +export default { + httpClient: { + clients: { + default: { + baseUrl: '' + }, + default2: { + baseUrl: '' + } + }, + priority: { + default: DEFAULT_PRIORITY.L1, + default2: DEFAULT_PRIORITY.L2, + } + } +} +``` + +如果不做设置, 默认情况下优先级为中等,即 `DEFAULT_PRIORITY.L2`。 + +为了更好的判断优先级,`ServiceFactory` 基类中会增加一些方法。 + +```typescript +@Provide() +@Scope(ScopeEnum.Singleton) +export class HTTPClientService implements HTTPClient { + @Inject() + private serviceFactory: HTTPClientServiceFactory; + + @Init() + async init() { + // 获取优先级 + this.serviceFactory.getClientPriority('default'); // DEFAULT_PRIORITY.L2 + + // 判断优先级 + this.serviceFactory.isHighPriority('default'); + this.serviceFactory.isMediumPriority('default'); + this.serviceFactory.isLowPriority('default'); + } +} +``` + diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/caching.md b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/caching.md new file mode 100644 index 000000000000..ebd2a0a78987 --- /dev/null +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/caching.md @@ -0,0 +1,542 @@ +# Caching + +Caching is a great and simple technique that helps improve the performance of your application. This component provides cache-related capabilities. You can cache data to different data sources, and you can also create multi-level caches for different scenarios to improve data access speed. + +:::tip + +Midway provides a module based on [cache-manager v5](https://github.com/node-cache-manager/node-cache-manager) that re-encapsulates the cache component. The original cache module is developed based on v3 and is no longer iterated, such as To view old documentation, please visit [here](/docs/cache). + +::: + +Related Information: + +| Description | | +| ------------------------------- | ---- | +| Available for standard projects | ✅ | +| Serverless available | ✅ | +| Available for integration | ✅ | +| Contains independent main frame | ❌ | +| Contains standalone logs | ❌ | + + + +## Install + +First install the relevant component modules. + +```bash +$ npm i @midwayjs/cache-manager@3 --save +``` + +Or add the following dependencies in `package.json` and reinstall. + +```json +{ + "dependencies": { + "@midwayjs/cache-manager": "^3.0.0", + // ... + }, +} +``` + + + +## Enable component + + +First, introduce the component and import it in `configuration.ts`: + +```typescript +import { Configuration } from '@midwayjs/core'; +import * as cacheManager from '@midwayjs/cache-manager'; +import { join } from 'path' + +@Configuration({ + imports: [ + // ... + cacheManager, + ], + importConfigs: [ + join(__dirname, 'config') + ] +}) +export class MainConfiguration { +} +``` + +## Use cache + + + +### 1. Configure cache + +Before use, you need to configure the location of the cache, such as the built-in memory cache, or introduce the Redis cache. Each cache corresponds to a cache Store. + +The following example code configures an in-memory cache named `default`. + +```typescript +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + }, + }, + } +} +``` + +In the most commonly used scenario, the cache will contain two parameters. Configure `max` to modify the number of caches, and configure `ttl` to modify the expiration time of the cache, in milliseconds. + +```typescript + // src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + options: { + max: 100, + ttl: 10, + }, + }, + }, + } +} +``` + +:::tip + +The eviction algorithm used by the memory cache is LRU. + +::: + +### 2. Use cache + +Instances can be obtained through the service factory decorator, and caches can be obtained and saved through simple `get` and `set` methods. + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + + @InjectClient(CachingFactory, 'default') + cache: MidwayCache; + + async invoke(name: string, value: string) { + // Set up cache + await this.cache.set(name, value); + // Get cache + const data = await this.cache.get(name); + // ... + } +} + +``` + +Dynamically set `ttl` expiration time. + +```typescript +await this.cache.set('key', 'value', 1000); +``` + +To disable cache expiration, you can set the `ttl` configuration property to 0. + +```typescript +await this.cache.set('key', 'value', 0); +``` + +Delete a single cache. + +```typescript +await this.cache.del('key'); +``` + +To clear the entire cache, use the `reset` method. + +```typescript +await this.cacheManager.reset(); +``` + +:::danger + +Note that clearing the entire cache is very dangerous. If Redis is used as the cache store, the entire Redis data will be cleared. + +::: + +In addition to decorators, cache instances can also be obtained through the API. + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + + @Inject() + cachingFactory: CachingFactory; + + async invoke() { + const caching = await this.cachingFactory.get('default'); + // ... + } +} +``` + + + +### 3. Configure multiple caches + +Like other components, the component supports configuring multiple cache instances. + +```typescript +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + }, + otherCaching: { + store: 'memory', + } + }, + } +} +``` + +Different cache instances can be injected. + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + + @InjectClient(CachingFactory, 'default') + cache: MidwayCache; + + @InjectClient(CachingFactory, 'otherCaching') + customCaching: MidwayCache; + +} + +``` + + + +### 4. Configure different Stores + +The component is based on [cache-manager](https://github.com/node-cache-manager/node-cache-manager) and can be configured with different cache stores. For example, the most common one can be configured with Redis Store. + +In order to reduce repeated configuration, you can use the `@midwayjs/cache-manager-redis` module to reuse the configuration of the Redis component. + +```bash +$ npm i @midwayjs/cache-manager-redis --save +``` + +If the project has been configured with a `Redis`. + +```typescript +import { createStore } from '@midwayjs/cache-manager-redis'; + +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: createStore('default'), + options: { + ttl: 10, + } + }, + }, + }, + redis: { + clients: { + default: { + port: 6379, + host: '127.0.0.1', + } + } + } +} +``` + +The `createStore` method can pass an already configured redis instance name, and the instance can be reused with the redis component. + + + +### 5. Configure third-party Store + +In addition to Redis, users can also choose the Store of Cache-Manager. The list can be found [here](https://github.com/node-cache-manager/node-cache-manager?tab=readme-ov-file #store-engines). + +Below is an example of configuring [node-cache-manager-ioredis-yet](https://github.com/node-cache-manager/node-cache-manager-ioredis-yet). + +```typescript +// src/config/config.default.ts +import { redisStore } from 'cache-manager-ioredis-yet'; + +export default { + cacheManager: { + clients: { + default: { + store: redisStore, + options: { + port: 6379, + host: 'localhost', + ttl: 10, + }, + }, + }, + } +} +``` + + + +### 6. Multi-level cache + +[cache-manager](https://github.com/node-cache-manager/node-cache-manager) supports aggregating multiple cache stores to achieve multi-level caching. + +For example, I can create a multi-level cache to merge multiple cache stores together. + +```typescript +// src/config/config.default.ts +import { createStore } from '@midwayjs/cache-manager-redis'; +export default { + cacheManager: { + clients: { + memoryCaching: { + store: 'memory', + }, + redisCaching: { + store: createStore('default'), + options: { + ttl: 10, + } + }, + multiCaching: { + store: [ + 'memoryCaching', + 'redisCaching' + ], + options: { + ttl: 100, + }, + }, + }, + }, + redis: { + clients: { + default: { + port: 6379, + host: '127.0.0.1', + } + } + } +} +``` + +In this way, the cache instance `multiCaching` contains two levels of cache. The cache priority is from top to bottom. When searching, it will first search `memoryCaching`. If the key does not exist in the memory cache, it will continue to search `redisCaching`. + + + +### 7. Use multi-level cache + +Similar to ordinary cache, multi-level cache also adds `mset`, `mget` and `mdel` methods in addition to `set`, `get` and `del` methods. + +```typescript +import { InjectClient, Provide } from '@midwayjs/core'; +import { CachingFactory, MidwayMultiCache } from '@midwayjs/cache-manager'; + +const userId2 = 456; +const key2 = 'user_' + userId; +const ttl = 5; + +@Provide() +export class UserService { + + @InjectClient(CachingFactory, 'multiCaching') + multiCache: MidwayMultiCache; + + async invoke() { + // Set to all levels of caching + await this.multiCache.set('foo2', 'bar2', ttl); + + // Get the key from the highest priority cache Store + console.log(await this.multiCache.get('foo2')); + // >> "bar2" + + // Call the del method of each Store to delete + await this.multiCache.del('foo2'); + + // Set multiple keys in all caches, including multiple key-value pairs + await this.multiCache.mset( + [ + ['foo', 'bar'], + ['foo2', 'bar2'], + ], + ttl + ); + + // mget() fetches from highest priority cache. + // If the first cache does not return all the keys, + // the next cache is fetched with the keys that were not found. + // This is done recursively until either: + // - all have been found + // - all caches has been fetched + console.log(await this.multiCache.mget('key', 'key2')); + // >> ['bar', 'bar2'] + + // Call the mdel method of each Store to delete + await this.multiCache.mdel('foo', 'foo2'); + } +} + +``` + +### 8. Automatic refresh + +Whether it is a normal cache or a multi-level cache, the background refresh function is supported. You only need to configure the `refreshThreshold` time, in milliseconds. + +```typescript +// src/config/config.default.ts +export default { + cacheManager: { + clients: { + default: { + store: 'memory', + refreshThreshold: 3 * 1000, + }, + }, + } +} +``` + +If `refreshthreshold` is set, each time the value is obtained from the cache, the value of `ttl` will be checked. If the remaining `ttl` is less than `refreshthreshold`, the system will update the cache asynchronously and the system will return the old value until` ttl` Expired. + +:::tip + +* In case of multi-level cache, the store that will be checked for refresh is the one where the key will be found first (highest priority). + +* If the threshold is low and the worker function is slow, the key may expire and you may encounter a racing condition with updating values. +* The background refresh mechanism currently does not support providing multiple keys. +* If no `ttl` is set for the key, the refresh mechanism will not be triggered. For redis, the `ttl` is set to -1 by default. + +::: + + + +## Automatic caching + +### Use decorator cache methods + +You can cache the results of methods through the `@Caching` decorator, such as caching the results of http responses or service calls. + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + @Caching('default') + async getUser(name: string) { + return name; + } +} + +``` + +When the `getUser` method is called for the first time, the logic will be executed normally and the result will be returned. The decorator will cache the result. When it is executed for the second time, if the cache is not invalidated, it will be returned directly from the cache. + +### Specify cached ttl + +`ttl` can also be set individually. + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + @Caching('default', 100) + async getUser(name: string) { + return name; + } +} +``` + +### Manually specify cache key + +If you are not satisfied with the automatically generated key, you can manually specify the cached key. + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +@Provide() +export class UserService { + @Caching('default', 'customKey', 100) + async getUser(name: string) { + return name; + } +} +``` + +### Cache with logic + +If you want to cache based on some specific logic, such as specific parameters or specific headers, you can pass a tool function for logical judgment. + +```typescript +import { Provide } from '@midwayjs/core'; +import { Caching } from '@midwayjs/cache-manager'; + +function cacheBy({methodArgs, ctx, target}) { + if (methodArgs[0] === 'harry' || methodArgs[0] === 'mike') { + return 'cache1'; + } +} + +@Provide() +export class UserService { + @Caching('default', cacheBy, 100) + async getUser(name: string) { + return 'hello ' + name; + } +} +``` + +In the above example, the `cacheBy` method customizes the caching logic. When the method input parameter value is `harry` or `mike`, the cached `key` will be returned, while for other parameters, the cache will be skipped. + +The result of execution at this time is: + +```typescript +await userService.getUser('harry')); // hello harry +await userService.getUser('mike')); // hello harry +await userService.getUser('lucy')); // hello lucy +``` + +The `@Caching` decorator can pass a method in the second parameter. The input parameter options of this method are: + +* `methodArgs` The actual parameters of the currently called method +* `ctx` If it is a request scope, it is the context object of the current call. If it is a singleton, the object is an empty object. +* `target` The currently called instance + +The return value of the method is a string or a Boolean value. When a string is returned, it means that the result of the method is cached with the key. When `undefined` or `null` is returned, it means that the cache is skipped. + +By judging these parameters, we can implement very flexible custom caching logic. + + + +## Common problem + + + +### 1. Memory cache set and get cannot obtain the same value under multi-process + +This is a normal phenomenon, the data of each process is independent and is only saved in the current process. If you need cross-process caching, use a distributed cache system like Redis. \ No newline at end of file diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/static_file.md b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/static_file.md index ef3f3b0f2ea4..88f2ffe6f0a0 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/static_file.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/static_file.md @@ -205,3 +205,20 @@ export default { }, } ```` + + + +### 3. When egg (@midwayjs/web) does not take effect + +Since egg comes with a static hosting plug-in, if the static plug-in is enabled, it will conflict with this component. + +If you want to use this component, be sure to close the egg plug-in. + +```typescript +// src/config/plugin.ts +import { EggPlugin } from 'egg'; +export default { + // ... + static: false, +} as EggPlugin; +``` diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/swagger.md b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/swagger.md index bd1131a57679..82762478ed0c 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/swagger.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/swagger.md @@ -1053,6 +1053,113 @@ All decorators of the component refer to the design of [@nestjs/swagger](https:/ | ```@ApiParam``` | Method | | ```@ApiExtraModel``` | Controller/Model | + + +## UI rendering + +### Rendering from Swagger-ui-dist + +By default, if the `swagger-ui-dist` package is installed, the component will call `renderSwaggerUIDist` to render swagger ui by default. If you need to pass the options of swagger-ui, you can pass the `swaggerUIRenderOptions` option. + +```typescript +// src/config/config.default.ts +import { renderSwaggerUIDist } from '@midwayjs/swagger'; + +export default { + // ... + swagger: { + swaggerUIRender: renderSwaggerUIDist, + swaggerUIRenderOptions: { + // ... + } + }, +} +``` + +If you want to adjust the UI configuration, you can replace the default `swagger-initializer.js` with a custom file. + +```typescript +// src/config/config.default.ts +import { AppInfo } from '@midwayjs/core'; +import { renderSwaggerUIDist } from '@midwayjs/swagger'; +import { join } from 'path'; + +export default (appInfo: AppInfo) { + return { + // ... + swagger: { + swaggerUIRender: renderSwaggerUIDist, + swaggerUIRenderOptions: { + customInitializer: join(appInfo.appDir, 'resource/swagger-initializer.js'), + } + }, + } +} +``` + +The content of the customized `swagger-initializer.js` is roughly as follows: + +```javascript +window.onload = function() { + window.ui = SwaggerUIBundle({ + url: "/index.json", + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout", + persistAuthorization: true, + }); +}; + +``` + +The url points to the current swagger json and can be modified by yourself. For the complete `swagger-ui` configuration, please refer to [here](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage /configuration.md). + +### Rendering from CDN addresses such as unpkg + +If the `swagger-ui-dist` package is not installed, the `renderSwaggerUIRemote` method is automatically used for rendering, and the cdn resource is provided by `unpkg.com` by default. + +```typescript +// src/config/config.default.ts +import { renderSwaggerUIRemote } from '@midwayjs/swagger'; + +export default { + // ... + swagger: { + swaggerUIRender: renderSwaggerUIRemote, + swaggerUIRenderOptions: { + // ... + } + }, +} +``` + + + +### Only Swagger JSON is provided + +If you only want to provide Swagger JSON, you can configure `renderJSON` to only render JSON without introducing the `swagger-ui-dist` package. + +```typescript +// src/config/config.default.ts +import { renderJSON } from '@midwayjs/swagger'; + +export default { + // ... + swagger: { + swaggerUIRender: renderJSON, + }, +} +``` + + + ## Frequently Asked Questions ### `summary` or `description` in route annotations such as `@Get` do not take effect diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/upload.md b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/upload.md index 6b4e019b32e4..d368cac545d3 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/upload.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/extensions/upload.md @@ -224,6 +224,34 @@ The default suffix whitelist can be obtained through the `uploadWhiteList` expor In addition, midway upload component, in order to avoid some `malicious users`, uses some technical means to `forge` some extensions that can be truncated, so it will filter the binary data of the obtained extensions, and only support `0x2e` (that is, the English dot `.`), `0x30-0x39` (that is, the number `0-9`), `0x61-0x7a` (that is, the lowercase letters `a-z`) are used as extensions, and other characters will be Automatically ignored. +Starting with v3.14.0, you can pass a function that can dynamically return a whitelist based on different conditions. + +```typescript +// src/config/config.default.ts +import { uploadWhiteList } from '@midwayjs/upload'; +import { tmpdir } from 'os'; +import { join } from 'path'; + +export default { + // ... + upload: { + whitelist: (ctx) => { + if (ctx.path === '/') { + return [ + '.jpg', + '.jpeg', + ]; + } else { + return [ + '.jpg', + ] + }; + }, + // ... + }, +} +``` + ### MIME type checking @@ -284,6 +312,31 @@ However, we still recommend that you set the `mimeTypeWhiteList` parameter if po ::: +Starting with v3.14.0, you can pass a function that dynamically returns MIME rules based on different conditions. + +```typescript +// src/config/config.default.ts +import { tmpdir } from 'os'; +import { join } from 'path'; + +export default { + // ... + upload: { + mimeTypeWhiteList: (ctx) => { + if (ctx.path === '/') { + return { + '.jpg': 'image/jpeg', + }; + } else { + return { + '.jpeg': ['image/jpeg', 'image/png'], + } + }; + } + }, +} +``` + ### Configure match or ignore diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/service_factory.md b/site/i18n/en/docusaurus-plugin-content-docs/current/service_factory.md index af9d49f8c512..36bd709c90f4 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/service_factory.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/service_factory.md @@ -442,3 +442,69 @@ export class UserService { } } ``` + + + +## Instance priority + +Starting from v3.14.0, service factory instances can add a priority attribute. In different scenarios, some different processing will be done based on the priority. + +There are three levels of priority for instances: `L1`, `L2`, and `L3`, which correspond to high, medium, and low levels respectively. + +The definition is as follows: + +```typescript +export const DEFAULT_PRIORITY = { + L1: 'High', + L2: 'Medium', + L3: 'Low', +}; +``` + +Through configuration, we can specify the priority of different instances. + +```typescript +//config.default.ts +import { DEFAULT_PRIORITY } from '@midwayjs/core'; + +export default { + httpClient: { + clients: { + default: { + baseUrl: '' + }, + default2: { + baseUrl: '' + } + }, + priority: { + default: DEFAULT_PRIORITY.L1, + default2: DEFAULT_PRIORITY.L2, + } + } +} +``` + +If no setting is made, the default priority is medium, that is, `DEFAULT_PRIORITY.L2`. + +In order to better judge the priority, some methods will be added to the `ServiceFactory` base class. + +```typescript +@Provide() +@Scope(ScopeEnum.Singleton) +export class HTTPClientService implements HTTPClient { + @Inject() + private serviceFactory: HTTPClientServiceFactory; + + @Init() + async init() { + // Get priority + this.serviceFactory.getClientPriority('default'); // DEFAULT_PRIORITY.L2 + + // Determine priority + this.serviceFactory.isHighPriority('default'); + this.serviceFactory.isMediumPriority('default'); + this.serviceFactory.isLowPriority('default'); + } +} +``` From f2de8490b872d3a4f50c485bd9087239e77e19bc Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 13 Jan 2024 00:42:03 +0800 Subject: [PATCH 08/11] test: add more time --- packages/cache-manager/test/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cache-manager/test/index.test.ts b/packages/cache-manager/test/index.test.ts index 9ff377ae3bfb..89b0c8767048 100644 --- a/packages/cache-manager/test/index.test.ts +++ b/packages/cache-manager/test/index.test.ts @@ -48,7 +48,7 @@ describe(`index.test.ts`, ()=>{ await sleep(20); expect((await userService.getDefaultUserWithTTL('harry'))).toEqual('hello ttl harry'); expect((await userService.getDefaultUserWithTTL('harry1'))).toEqual('hello ttl harry'); - await sleep(100); + await sleep(105); expect((await userService.getDefaultUserWithTTL('harry2'))).toEqual('hello ttl harry2'); // custom cache key From e4b6e599db5cafa68e4177051e4359d066b46ffc Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 13 Jan 2024 11:56:12 +0800 Subject: [PATCH 09/11] test: fix --- packages/core/test/util/retry.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/test/util/retry.test.ts b/packages/core/test/util/retry.test.ts index c587ea54220e..0d3aa7a97481 100644 --- a/packages/core/test/util/retry.test.ts +++ b/packages/core/test/util/retry.test.ts @@ -91,7 +91,8 @@ describe('test/util/retry.test.ts', function () { const result = await fn(); expect(result).toEqual('ok'); - expect(Date.now() - start).toBeGreaterThanOrEqual(2000); + // js 稍微有点精度问题,所以这里取 1999 + expect(Date.now() - start).toBeGreaterThanOrEqual(1999); }); }); From f717be0b45344e119be69800df7935052d607202 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 13 Jan 2024 18:05:51 +0800 Subject: [PATCH 10/11] docs: update sidebar --- site/blog/2024-01-13-release-3.14.md | 3 +- site/docs/contributing.md | 8 +- site/docs/serverless/aliyun_faas.md | 4 +- site/docs/serverless/aws_lambda.md | 82 +++++++++++ site/docs/sidebars.json | 129 +++++++----------- site/docs/tool/create_midway.md | 2 +- site/docs/tool/mwts.md | 10 +- .../current/contributing.md | 16 ++- .../current/serverless/aliyun_faas.md | 2 +- .../current/tool/create_midway.md | 2 +- .../current/tool/mwts.md | 2 +- site/lib/navbar.js | 6 - .../version-1.0.0-sidebars.json | 22 ++- .../version-2.0.0-sidebars.json | 76 +++++------ 14 files changed, 207 insertions(+), 157 deletions(-) create mode 100644 site/docs/serverless/aws_lambda.md diff --git a/site/blog/2024-01-13-release-3.14.md b/site/blog/2024-01-13-release-3.14.md index 22ae066770d7..632f80be1dc2 100644 --- a/site/blog/2024-01-13-release-3.14.md +++ b/site/blog/2024-01-13-release-3.14.md @@ -146,7 +146,8 @@ export default { * 服务工厂增加了 `getClients()` 和 `getClientKeys()` 方法,用于快速迭代实例 * 修复了 swagger 组件 ApiOperation 相关的一些问题 * mwtsc 工具修复了 windows 下开发的问题 +* 文档的进一步精简,“其他” 菜单合并到了 “使用文档” 菜单中 -此外,还有一大批依赖进行了更新,更多可以参考我们的 [ChangeLog](https://midwayjs.org/changelog/v3.14.0)) +此外,还有一大批依赖进行了更新,更多可以参考我们的 [ChangeLog](https://midwayjs.org/changelog/v3.14.0)。 diff --git a/site/docs/contributing.md b/site/docs/contributing.md index ec03d99a274b..ce5fa6a8874f 100644 --- a/site/docs/contributing.md +++ b/site/docs/contributing.md @@ -4,12 +4,12 @@ Midway 是一款开源框架,欢迎大家为社区贡献力量,本文介绍 -## 报告问题,提交一个 issue +## 报告问题 如果你在开发过程中遇到了一些问题,你无法解决需要想开发者问询的,我们强烈建议: - 1、先在文档中查找相关的问题 -- 2、如果查找后无法解决,可以提交一个 [issue](https://github.com/midwayjs/midway/issues/new)。 +- 2、如果查找后无法解决,可以提交一个 [Q&A](https://github.com/midwayjs/midway/discussions/new/choose)。 @@ -43,6 +43,10 @@ Midway 是一款开源框架,欢迎大家为社区贡献力量,本文介绍 ```bash # 创建新分支 $ git checkout -b branch-name +# 安装依赖 +$ npm i +# 构建项目 +$ npm run build # 开发并执行测试 $ npm test diff --git a/site/docs/serverless/aliyun_faas.md b/site/docs/serverless/aliyun_faas.md index 3527541e793d..8e6481041542 100644 --- a/site/docs/serverless/aliyun_faas.md +++ b/site/docs/serverless/aliyun_faas.md @@ -1,7 +1,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# 阿里云 FC +# 部署到阿里云函数计算 阿里云 Serverless 是国内最早提供 Serverless 计算服务的团队之一, 依托于阿里云强大的云基础设施服务能力,不断实现技术突破。目前,淘宝、支付宝、钉钉、高德等已经将 Serverless 应用于生产业务,云上的 Serverless 产品在南瓜电影、网易云音乐、爱奇艺体育、莉莉丝等数万家企业成功落地。 @@ -712,6 +712,8 @@ Bootstrap.configure({ * [Egg 修改端口](/docs/extensions/egg) * [Express 修改端口](/docs/extensions/express) + + ### 3、平台部署配置 * 1、选择运行环境,比如 `Node.js 18` diff --git a/site/docs/serverless/aws_lambda.md b/site/docs/serverless/aws_lambda.md new file mode 100644 index 000000000000..92bfd6b1c1ca --- /dev/null +++ b/site/docs/serverless/aws_lambda.md @@ -0,0 +1,82 @@ +# 部署到 AWS Lambda + +AWS Lambda是Amazon Web Services (AWS)提供的无服务器计算服务。它允许您在无需预配或管理服务器的情况下运行代码。您可以为几乎任何类型的应用程序或后端服务运行代码,全部无需管理。 + +下面我们将介绍如何将 Midway 标准应用部署到 AWS Lambda。 + + + +### 1、创建项目 + +需要创建 Midway koa/express/express 项目。 + +初始化项目请参考 [创建第一个应用](/docs/quickstart),下面以 koa 应用为例。 + + + +### 2、调整端口 + +为了避免影响本地开发,我们仅在入口 `bootstrap.js` 处增加端口,比如 `8080`。 + +```typescript +const { Bootstrap } = require('@midwayjs/bootstrap'); + +// 显式以组件方式引入用户代码 +Bootstrap.configure({ + globalConfig: { + koa: { + port: 8080, + } + } +}).run() +``` + +不同的框架修改端口请参考: + +* [koa 修改端口](/docs/extensions/koa) +* [Egg 修改端口](/docs/extensions/egg) +* [Express 修改端口](/docs/extensions/express) + + + +### 3、安装和配置 AWS 工具 + +- [安装 AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) +- [配置AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) +- [安装 AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) + + + +### 4、编写 template.yaml + +```yaml +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Resources: + EasySchoolBackendFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./dist.zip + Handler: dist/index/index.handler + Runtime: nodejs14.x + Timeout: 900 + PackageType: Zip + Events: + ApiEvent: + Type: Api + Properties: + Path: /{any+} + Method: ANY + +``` + + + +### 4、构建和部署 + +```bash +$ cd sam +$ sam build # builds sam +$ sam local start-api # start local api +``` + diff --git a/site/docs/sidebars.json b/site/docs/sidebars.json index 77ddc161bfaa..09d46a72e719 100644 --- a/site/docs/sidebars.json +++ b/site/docs/sidebars.json @@ -87,36 +87,13 @@ "collapsible": true, "items": [ [ - { - "type": "doc", - "id": "serverless/serverless_intro", - "label": "介绍" - }, - { - "type": "doc", - "id": "serverless/serverless_v2_upgrade_serverless_v3" - }, - { - "type": "category", - "label": "基础", - "collapsed": false, - "collapsible": false, - "items": [ - "serverless/serverless_dev", - "serverless/serverless_testing", - "serverless/serverless_context", - "serverless/serverless_error" - ] - }, - { - "type": "category", - "label": "平台支持", - "collapsed": false, - "collapsible": false, - "items": [ - "serverless/aliyun_faas" - ] - } + "serverless/serverless_intro", + "serverless/serverless_v2_upgrade_serverless_v3", + "serverless/serverless_dev", + "serverless/serverless_testing", + "serverless/serverless_context", + "serverless/serverless_error", + "serverless/aliyun_faas" ] ] }, @@ -171,6 +148,47 @@ ] } ] + }, + { + "type": "category", + "label": "开发工具", + "collapsed": true, + "collapsible": true, + "items": [ + "tool/create_midway", + "tool/mwtsc", + "tool/mwts", + "tool/luckyeye", + "tool/egg-ts-helper", + "tool/cli" + ] + }, + { + "type": "category", + "label": "常见问题", + "collapsed": true, + "collapsible": true, + "items": [ + "faq/framework_problem", + "faq/git_problem", + "faq/npm_problem", + "faq/ts_problem", + "faq/alias_path", + "how_to_install_nodejs", + "ops/ecs_start_err", + "midway_slow_problem" + ] + }, + { + "type": "category", + "label": "其他", + "collapsed": true, + "collapsible": true, + "items": [ + "release_schedule", + "contributing", + "awesome_midway" + ] } ], "component": [ @@ -272,56 +290,5 @@ "extensions/prometheus" ] } - ], - "other": [ - { - "type": "category", - "label": "工具", - "collapsed": false, - "collapsible": false, - "items": [ - "tool/create_midway", - "tool/mwtsc", - "tool/cli", - "tool/mwts", - "tool/luckyeye", - "tool/egg-ts-helper" - ] - }, - { - "type": "category", - "label": "运维", - "collapsed": false, - "collapsible": false, - "items": [ - "ops/ecs_start_err" - ] - }, - { - "type": "category", - "label": "常见问题", - "collapsed": false, - "collapsible": false, - "items": [ - "faq/framework_problem", - "faq/git_problem", - "faq/npm_problem", - "faq/ts_problem", - "faq/alias_path", - "how_to_install_nodejs", - "midway_slow_problem" - ] - }, - { - "type": "category", - "label": "其他", - "collapsed": false, - "collapsible": false, - "items": [ - "release_schedule", - "contributing", - "awesome_midway" - ] - } ] } diff --git a/site/docs/tool/create_midway.md b/site/docs/tool/create_midway.md index aa4a5b706097..d69c09744705 100644 --- a/site/docs/tool/create_midway.md +++ b/site/docs/tool/create_midway.md @@ -1,4 +1,4 @@ -# 脚手架工具 +# 脚手架 Midway 编写了 `create-midway` 包,通过 npx 命令,可以方便的使用 `npm init midway` 命令创建脚手架。 diff --git a/site/docs/tool/mwts.md b/site/docs/tool/mwts.md index 1088b331ba86..ea8a67e23526 100644 --- a/site/docs/tool/mwts.md +++ b/site/docs/tool/mwts.md @@ -1,4 +1,4 @@ -# Lint 工具、规则和格式化 +# Lint 和格式化 Midway 的框架和业务代码都是由 TypeScript 编写的,默认 Midway 提供了一套默认的 lint、编辑器以及格式化规则,用于更方便的进行开发和测试。 @@ -13,10 +13,10 @@ Midway 的代码风格库叫 [mwts](https://github.com/midwayjs/mwts),源自 为了使用 mwts,我们需要把它添加到开发依赖中。 ```json - "devDependencies": { - "mwts": "^1.0.5", - "typescript": "^4.0.0" - }, +"devDependencies": { + "mwts": "^1.0.5", + "typescript": "^4.0.0" +}, ``` ## ESLint 配置 diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/contributing.md b/site/i18n/en/docusaurus-plugin-content-docs/current/contributing.md index 081056417436..63fc89cf4240 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/contributing.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/contributing.md @@ -4,12 +4,12 @@ Midway is an open source framework that welcomes everyone to contribute to the c -## Report problems and submit an issue +## Report problems If you encounter some problems in the development process and you cannot solve the problems you need to ask developers, we strongly recommend: - 1. Find relevant problems in the document first -- 2. If you cannot solve it after searching, you can submit an [issue](https://github.com/midwayjs/midway/issues/new). +- 2. If you cannot solve it after searching, you can submit an [Q&A](https://github.com/midwayjs/midway/discussions/new/choose). @@ -42,14 +42,18 @@ If you find that the framework has some problems to be modified, you can submit ```bash # Create a new branch -$git checkout -b branch-name +$ git checkout -b branch-name +# install dependencies +$ npm i +# build code +$ npm run build # Develop and execute tests $ npm test -$git add . # git add -u to delete files -$git commit -m "fix(role): role.use must xxx" -$git push origin branch-name +$ git add . # git add -u to delete files +$ git commit -m "fix(role): role.use must xxx" +$ git push origin branch-name ``` 3. Create a Pull Request and choose to merge your own project branch into the main branch of the target midwayjs/midway. diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/serverless/aliyun_faas.md b/site/i18n/en/docusaurus-plugin-content-docs/current/serverless/aliyun_faas.md index 1cb579ee9e0d..9e1b53abe481 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/serverless/aliyun_faas.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/serverless/aliyun_faas.md @@ -1,7 +1,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Aliyun FC +# Deploy to Alibaba Cloud Function Compute Alibaba Cloud Serverless is one of the first teams in China to provide serverless computing services. It relies on Alibaba Cloud's powerful cloud infrastructure service capabilities to continuously achieve technological breakthroughs. At present, Taobao, Alipay, DingTalk, AutoNavi, etc. have applied Serverless to production business. Serverless products on the cloud have been successfully implemented in tens of thousands of companies such as Pumpkin Movie, NetEase Cloud Music, iQiyi Sports, and Lilith. diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/tool/create_midway.md b/site/i18n/en/docusaurus-plugin-content-docs/current/tool/create_midway.md index ba8ae2c34a54..9452e124f103 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/tool/create_midway.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/tool/create_midway.md @@ -1,4 +1,4 @@ -# Scaffoldingtools +# Scaffolding Midway has written the `create-midway` package. Through the npx command, you can easily use the `npm init midway` command to create scaffolding. diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/tool/mwts.md b/site/i18n/en/docusaurus-plugin-content-docs/current/tool/mwts.md index 0d7e3a9a11eb..fb5ebf6b5f98 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/tool/mwts.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/tool/mwts.md @@ -1,4 +1,4 @@ -# Lint tools, rules and formatting +# Lint tools and formatting Midway's framework and business code are written by TypeScript. The default Midway provides a set of default lint, editor and formatting rules for more convenient development and testing. diff --git a/site/lib/navbar.js b/site/lib/navbar.js index 2a2e2b161f0f..98e4a9c1f0d5 100644 --- a/site/lib/navbar.js +++ b/site/lib/navbar.js @@ -11,12 +11,6 @@ module.exports = [ "label": "组件", "position": "left" }, - { - "type": "doc", - "docId": "tool/cli", - "label": "其他", - "position": "left" - }, { to: 'api', label: 'API', diff --git a/site/versioned_sidebars/version-1.0.0-sidebars.json b/site/versioned_sidebars/version-1.0.0-sidebars.json index 3db0352c1e40..340698848170 100644 --- a/site/versioned_sidebars/version-1.0.0-sidebars.json +++ b/site/versioned_sidebars/version-1.0.0-sidebars.json @@ -36,6 +36,15 @@ "test_more" ] }, + { + "type": "category", + "label": "工具", + "collapsed": false, + "collapsible": false, + "items": [ + "tool/cli" + ] + }, { "type": "category", "label": "常见问题", @@ -66,16 +75,5 @@ "id": "hooks/hooks_intro", "label": "介绍" } - ], - "other": [ - { - "type": "category", - "label": "工具", - "collapsed": false, - "collapsible": false, - "items": [ - "tool/cli" - ] - } ] -} \ No newline at end of file +} diff --git a/site/versioned_sidebars/version-2.0.0-sidebars.json b/site/versioned_sidebars/version-2.0.0-sidebars.json index 509c4926eba7..008557c26952 100644 --- a/site/versioned_sidebars/version-2.0.0-sidebars.json +++ b/site/versioned_sidebars/version-2.0.0-sidebars.json @@ -424,6 +424,43 @@ ] } ] + }, + { + "type": "category", + "label": "工具", + "collapsed": false, + "collapsible": false, + "items": [ + "tool/cli", + "tool/mwts", + "tool/luckyeye", + "tool/typeorm_generator", + "tool/egg-ts-helper" + ] + }, + { + "type": "category", + "label": "常见问题", + "collapsed": false, + "collapsible": false, + "items": [ + "framework_problem", + "git_problem", + "npm_problem", + "ts_problem", + "how_to_install_nodejs", + "how_to_update_midway", + "midway_slow_problem" + ] + }, + { + "type": "category", + "label": "其他", + "collapsed": false, + "collapsible": false, + "items": [ + "release_schedule" + ] } ], "component": [ @@ -556,44 +593,5 @@ } ] } - ], - "other": [ - { - "type": "category", - "label": "工具", - "collapsed": false, - "collapsible": false, - "items": [ - "tool/cli", - "tool/mwts", - "tool/luckyeye", - "tool/typeorm_generator", - "tool/egg-ts-helper" - ] - }, - { - "type": "category", - "label": "常见问题", - "collapsed": false, - "collapsible": false, - "items": [ - "framework_problem", - "git_problem", - "npm_problem", - "ts_problem", - "how_to_install_nodejs", - "how_to_update_midway", - "midway_slow_problem" - ] - }, - { - "type": "category", - "label": "其他", - "collapsed": false, - "collapsible": false, - "items": [ - "release_schedule" - ] - } ] } From 984d85e58a9eb75b094f536b3aba19b281bc9daf Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 13 Jan 2024 18:29:29 +0800 Subject: [PATCH 11/11] docs: update sidebar --- site/docs/legacy/mongodb.md | 8 +- site/docs/legacy/orm.md | 6 +- site/docs/logger.md | 645 +++++----- site/docs/{legacy/logger.md => logger_v3.md} | 643 +++++----- site/docs/sidebars.json | 23 +- .../current/legacy/logger.md | 869 -------------- .../current/legacy/mongodb.md | 8 +- .../current/legacy/orm.md | 5 +- .../current/logger.md | 1035 ++++++++--------- .../current/logger_v3.md | 976 ++++++++++++++++ 10 files changed, 2121 insertions(+), 2097 deletions(-) rename site/docs/{legacy/logger.md => logger_v3.md} (60%) delete mode 100644 site/i18n/en/docusaurus-plugin-content-docs/current/legacy/logger.md create mode 100644 site/i18n/en/docusaurus-plugin-content-docs/current/logger_v3.md diff --git a/site/docs/legacy/mongodb.md b/site/docs/legacy/mongodb.md index 6ab71c9ba6a4..8b844178301c 100644 --- a/site/docs/legacy/mongodb.md +++ b/site/docs/legacy/mongodb.md @@ -1,5 +1,9 @@ # MongoDB +:::tip +本文档从 v3.4.0 版本起废弃。 +::: + 在这一章节中,我们选择 [Typegoose](https://github.com/typegoose/typegoose) 作为基础的 MongoDB ORM 库。就如同他描述的那样 " Define Mongoose models using TypeScript classes",和 TypeScript 结合的很不错。 简单的来说,Typegoose 使用 TypeScript 编写 Mongoose 模型的 “包装器”,它的大部分能力还是由 [mongoose](https://www.npmjs.com/package/mongoose) 库来提供的。 @@ -15,10 +19,6 @@ | 可用于一体化 | ✅ | -:::tip -本文档从 v3.4.0 版本起废弃。 -::: - ## Mongoose 版本依赖 diff --git a/site/docs/legacy/orm.md b/site/docs/legacy/orm.md index a5a50a8c951f..2b4c01e90951 100644 --- a/site/docs/legacy/orm.md +++ b/site/docs/legacy/orm.md @@ -1,11 +1,12 @@ # TypeORM -[TypeORM](https://github.com/typeorm/typeorm) 是 `node.js` 现有社区最成熟的对象关系映射器(`ORM` )。Midway 和 TypeORM 搭配,使开发更简单。 - :::tip 本文档从 v3.4.0 版本起废弃。 ::: +[TypeORM](https://github.com/typeorm/typeorm) 是 `node.js` 现有社区最成熟的对象关系映射器(`ORM` )。Midway 和 TypeORM 搭配,使开发更简单。 + + 相关信息: | 描述 | | @@ -16,7 +17,6 @@ - ## 安装组件 diff --git a/site/docs/logger.md b/site/docs/logger.md index f11540f456c1..b3954dbf5ee0 100644 --- a/site/docs/logger.md +++ b/site/docs/logger.md @@ -1,45 +1,21 @@ -# 日志 - -Midway 为不同场景提供了一套统一的日志接入方式。通过 `@midwayjs/logger` 包导出的方法,可以方便的接入不同场景的日志系统。 - -实现的功能有: - -- 日志分级 -- 按大小和时间自动切割 -- 自定义输出格式 -- 统一错误日志 +# 日志(v2) :::tip -当前版本为 3.0 的日志 SDK 文档,如需 2.0 版本,请查看 [这个文档](/docs/legacy/logger)。 +本文档为 `@midwayjs/logger` v2.0 版本的文档。 ::: +Midway 为不同场景提供了一套统一的日志接入方式。通过 `@midwayjs/logger` 包导出的方法,可以方便的接入不同场景的日志系统。 +Midway 的日志系统基于社区的 [winston](https://github.com/winstonjs/winston),是现在社区非常受欢迎的日志库。 -## 从 2.0 升级到 3.0 - -从 midway v3.13.0 开始,支持使用 3.0 版本的 `@midwayjs/logger`。 - -将 `package.json` 中的依赖版本升级,注意是 `dependencies` 依赖。 - -```diff -{ - "dependencies": { -- "@midwayjs/logger": "2.0.0", -+ "@midwayjs/logger": "^3.0.0" - } -} -``` - -如果在配置中没有了 midwayLogger 的类型提示,你需要在 `src/interface.ts` 中加入日志库的引用。 - -```diff -// src/interface.ts -+ import type {} from '@midwayjs/logger'; -``` +实现的功能有: -在大部分场景下,两个版本是兼容的,但是由于是大版本升级,肯定会有一定的差异性,完整的 Breaking Change 变化,请查看 [变更文档](https://github.com/midwayjs/logger/blob/main/BREAKING-3.md)。 +- 日志分级 +- 按大小和时间自动切割 +- 自定义输出格式 +- 统一错误日志 @@ -49,7 +25,7 @@ Midway 会在日志根目录创建一些默认的文件。 - `midway-core.log` 框架、组件打印信息的日志,对应 `coreLogger` 。 -- `midway-app.log` 应用打印信息的日志,对应 `appLogger`,在 `@midawyjs/web` 中,该文件是 `midway-web.log` +- `midway-app.log` 应用打印信息的日志,对应 `appLogger` - `common-error.log` 所有错误的日志(所有 Midway 创建出来的日志,都会将错误重复打印一份到该文件中) 本地开发和服务器部署时的 **日志路径** 和 **日志等级** 不同,具体请参考 [配置日志根目录](#配置日志根目录) 和 [框架的默认等级](#框架的默认等级)。 @@ -63,8 +39,8 @@ Midway 默认在框架提供了三种不同的日志,对应三种不同的行 | 日志 | 释义 | 描述 | 常见使用 | | ----------------------------------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | coreLogger | 框架,组件层面的日志 | 默认会输出控制台日志和文本日志 `midway-core.log` ,并且默认会将错误日志发送到 `common-error.log` 。 | 框架和组件的错误,一般会打印到其中。 | -| appLogger | 业务层面的日志 | 默认会输出控制台日志和文本日志 `midway-app.log` ,并且默认会将错误日志发送到 `common-error.log` ,在 `@midawyjs/web` 中,该文件是 `midway-web.log`。 | 业务使用的日志,一般业务日志会打印到其中。 | -| 上下文日志(复用 appLogger 的配置) | 请求链路的日志 | 默认使用 `appLogger` 进行输出,除了会将错误日志发送到 `common-error.log` 之外,还增加了上下文信息。 | 不同的协议有不同的请求日志格式,比如 HTTP 下就会输出路由信息。 | +| appLogger | 业务层面的日志 | 默认会输出控制台日志和文本日志 `midway-app.log` ,并且默认会将错误日志发送到 `common-error.log` 。 | 业务使用的日志,一般业务日志会打印到其中。 | +| 上下文日志(复用 appLogger 的配置) | 请求链路的日志 | 默认使用 `appLogger` 进行输出,除了会将错误日志发送到 `common-error.log` 之外,还增加了上下文信息。 | 修改日志输出的标记(Label),不同的框架有不同的请求标记,比如 HTTP 下就会输出路由信息。 | @@ -98,6 +74,7 @@ ctx.logger.error(new Error('custom error')); 输出结果: + ```text 2021-07-22 14:50:59,388 INFO 7739 [-/::ffff:127.0.0.1/-/0ms GET /api/get_user] hello world ``` @@ -164,7 +141,6 @@ export class MainConfiguration implements ILifeCycle { 在组件或者框架层面的研发中,我们会使用 coreLogger 来记录日志。 ```typescript - @Configuration() export class MainConfiguration implements ILifeCycle { @@ -185,33 +161,83 @@ export class MainConfiguration implements ILifeCycle { + + ## 输出方法和格式 -Midway 的日志对象提供 `error()` , `warn()` , `info()` , `debug()`,`write()` 五种方法。 +Midway 的日志对象继承与 winston 的日志对象,一般情况下,只提供 `error()` , `warn()` , `info()` , `debug` 四种方法。 示例如下。 + ```typescript logger.debug('debug info'); logger.info('启动耗时 %d ms', Date.now() - start); logger.warn('warning!'); logger.error(new Error('my error')); -logger.write('abcdef'); ``` -:::tip -`write` 方法用于输出用户的原始格式日志。 +### 默认的输出行为 + + +在大部分的普通类型下,日志库都能工作的很好。 + + +比如: + +```typescript +logger.info('hello world'); // 输出字符串 +logger.info(123); // 输出数字 +logger.info(['b', 'c']); // 输出数组 +logger.info(new Set([2, 3, 4])); // 输出 Set +logger.info(new Map([['key1', 'value1'], ['key2', 'value2']])); // 输出 Map +``` + +> Midway 针对 winston 无法输出的 `Array` , `Set` , `Map` 类型,做了特殊定制,使其也能够正常的输出。 + + +不过需要注意的是,日志对象在一般情况下,只能传入一个参数,它的第二个参数有其他作用。 + +```typescript +logger.info('plain error message', 321); // 会忽略 321 +``` + + +### 错误输出 + +针对错误对象,Midway 也对 winston 做了定制,使其能够方便的和普通文本结合到一起输出。 + +```typescript +// 输出错误对象 +logger.error(new Error('error instance')); + +// 输出自定义的错误对象 +const error = new Error('named error instance'); +error.name = 'NamedError'; +logger.error(error); + +// 文本在前,加上 error 实例 +logger.info('text before error', new Error('error instance after text')); +``` + +:::caution +注意,错误对象只能放在最后,且有且只有一个,其后面的所有参数都会被忽略。 ::: + +### 格式化内容 + 基于 `util.format` 的格式化方式。 + ```typescript logger.info('%s %d', 'aaa', 222); ``` + 常用的有 @@ -223,56 +249,100 @@ logger.info('%s %d', 'aaa', 222); +### 输出自定义对象或者复杂类型 + + +基于性能考虑,Midway(winston)大部分时间只会输出基本类型,所以当输出的参数为高级对象时,**需要用户手动转换为需要打印的字符串**。 + + +如下示例,将不会得到希望的结果。 + +```typescript +const obj = {a: 1}; +logger.info(obj); // 默认情况下,输出 [object Object] +``` + +需要手动输出希望打印的内容。 + +```typescript +const obj = {a: 1}; +logger.info(JSON.stringify(obj)); // 可以输出格式化文本 +logger.info(obj.a); // 直接输出属性值 +logger.info('%j', a); // 直接占位符输出整个 json +``` + + + +### 纯输出内容 + + +特殊场景下,我们需要单纯的输出内容,不希望输出时间戳,label 等和格式相关的信息。这种需求我们可以使用 `write` 方法。 + +`write` 方法是个非常底层的方法,并且不管什么级别的日志,它都会写入到文件中。 + + +虽然 `write` 方法在每个 logger 上都有,但是我们只在 `IMidwayLogger` 定义中提供它,我们希望你能明确的知道自己希望调用它。 + +```typescript +(logger as IMidwayLogger).write('hello world'); // 文件中只会有 hello world +``` + + + ## 日志类型定义 -大部分情况下,用户应该使用 `@midwayjs/core` 中最简单的 `ILogger` 定义。 +默认的情况,用户应该使用最简单的 `ILogger` 定义。 + ```typescript -import { Provide, Logger, ILogger } from '@midwayjs/core'; +import { Provide, Logger } from '@midwayjs/core'; +import { ILogger } from '@midwayjs/logger'; @Provide() export class UserService { @Inject() - logger: ILogger; + logger: ILogger; // 获取上下文日志 async getUser() { this.logger.info('hello user'); } + } ``` + `ILogger` 定义只提供最简单的 `debug` , `info` , `warn` 以及 `error` 方法。 -在某些场景下,我们需要更为复杂的定义,这个时候需要使用 `@midwayjs/logger` 提供的 `ILogger` 定义。 +在某些场景下,我们需要更为复杂的定义,比如修改日志属性或者动态调节,这个时候需要使用更为复杂的 `IMidwayLogger` 定义。 ```typescript import { Provide, Logger } from '@midwayjs/core'; -import { ILogger } from '@midwayjs/logger'; +import { IMidwayLogger } from '@midwayjs/logger'; @Provide() export class UserService { @Inject() - logger: ILogger; + logger: IMidwayLogger; // 获取上下文日志 async getUser() { - // ... + this.logger.disableConsole(); // 禁止控制台输出 + this.logger.info('hello user'); // 这句话在控制台看不到 + this.logger.enableConsole(); // 开启控制台输出 + this.logger.info('hello user'); // 这句话在控制台可以看到 } } ``` -`ILogger` 的定义可以参考 interface 中的描述,或者查看 [代码](https://github.com/midwayjs/logger/blob/main/src/interface.ts)。 - - -## 日志配置 +`IMidwayLogger` 的定义可以参考 interface 中的描述,或者查看 [代码](https://github.com/midwayjs/logger/blob/main/src/interface.ts)。 -### 基本配置结构 +## 日志基本配置 我们可以在配置文件中配置日志的各种行为。 @@ -301,111 +371,61 @@ export default { 如上所述,`clients` 配置段中的每个对象都是一个独立的日志配置项,其配置会和 `default` 段落合并后创建 logger 实例。 - - -### 默认 Transport - -在 Midway 中,默认启用了 `console`,`file`,`error` 三个 Transport,更多信息可以通过配置进行修改。 +如果你发现没有定义,请将 `@midawyjs/logger` 在 `src/interface.ts` 中显式声明一次。 ```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; - -export default { - midwayLogger: { - default: { - transports: { - console: { - // console transport 配置 - }, - file: { - // file transport 配置 - }, - error: { - // error transport 配置 - }, - } - }, - // ... - }, -} as MidwayConfig; +// ... +import type {} from '@midwayjs/logger'; ``` -如果不需要某个 transport,可以设置为 `false`。 -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; -export default { - midwayLogger: { - default: { - transports: { - console: false, - } - }, - // ... - }, -} as MidwayConfig; -``` +## 配置日志等级 -### 配置日志等级 -在 Midway 中,一般情况下,我们只会使用 `error` , `warn` , `info` , `debug` 这四种等级。 +winston 的日志等级分为下面几类,日志等级依次降低(数字越大,等级越低): -日志等级表示当前可输出日志的最低等级。比如当你的日志 level 设置为 `warn` 时,仅 `warn` 以及更高的 `error` 等级的日志能被输出。 +```typescript +const levels = { + none: 0, + error: 1, + trace: 2, + warn: 3, + info: 4, + verbose: 5, + debug: 6, + silly: 7, + all: 8, +} +``` +在 Midway 中,为了简化,一般情况下,我们只会使用 `error` , `warn` , `info` , `debug` 这四种等级。 -在 Midway 中,有着自己的默认日志等级。 +日志等级表示当前可输出日志的最低等级。比如当你的日志 level 设置为 `warn` 时,仅 `warn` 以及更高的 `error` 等级的日志能被输出。 +在 Midway 中,针对不同的输出行为,可以配置不同的日志等级。 -- 在开发环境下(local,test,unittest),文本和控制台日志等级统一为 `info` 。 -- 在服务器环境,为减少日志数量,`coreLogger` 日志等级为 `warn` ,而其他日志为 `info`。 +- `level` 写入文本的日志等级 +- `consoleLevel` 控制台输出的日志等级 -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; -export default { - midwayLogger: { - default: { - level: 'info', - }, - // ... - }, -} as MidwayConfig; -``` +### 框架的默认等级 -logger 的 level 和 Transport 的 level 可以分开设置,Tranport 的 level 优先级高于 logger 的 level。 +在 Midway 中,有着自己的默认日志等级。 -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; -export default { - midwayLogger: { - default: { - // logger 的 level - level: 'info', - transports: { - file: { - // file transport 的 level - level: 'warn' - } - } - }, - // ... - }, -} as MidwayConfig; -``` +- 在开发环境下(local,test,unittest),文本和控制台日志等级统一为 `info` 。 +- 在服务器环境(除开发环境外),为减少日志数量,`coreLogger` 日志等级为 `warn` ,而其他日志为 `info`。 + +### 调整日志等级 -我们也可以调整特定的 logger 的日志等级,比如: +一般情况下,我们不建议调整全局默认的日志等级,而是调整特定的 logger 的日志等级,比如: 调整 `coreLogger` 或者 `appLogger` 。 @@ -418,10 +438,12 @@ export default { clients: { coreLogger: { level: 'warn', + consoleLevel: 'warn' // ... }, appLogger: { level: 'warn', + consoleLevel: 'warn' // ... } } @@ -439,11 +461,7 @@ export default { midwayLogger: { default: { level: 'info', - transports: { - console: { - level: 'warn' - } - } + consoleLevel: 'warn' }, // ... }, @@ -452,7 +470,7 @@ export default { -### 配置日志根目录 +## 配置日志根目录 默认情况下,Midway 会在本地开发和服务器部署时输出日志到 **日志根目录**。 @@ -460,7 +478,7 @@ export default { - 本地的日志根目录为 `${app.appDir}/logs/项目名` 目录下 - 服务器的日志根目录为用户目录 `${process.env.HOME}/logs/项目名` (Linux/Mac)以及 `${process.env.USERPROFILE}/logs/项目名` (Windows)下,例如 `/home/admin/logs/example-app`。 -我们可以配置日志所在的根目录,注意,要将所有 Transport 的路径都修改。 +我们可以配置日志所在的根目录。 ```typescript // src/config/config.default.ts @@ -469,14 +487,7 @@ import { MidwayConfig } from '@midwayjs/core'; export default { midwayLogger: { default: { - transports: { - file: { - dir: '/home/admin/logs', - }, - error: { - dir: '/home/admin/logs', - }, - } + dir: '/home/admin/logs', }, // ... }, @@ -485,7 +496,7 @@ export default { -### 配置日志切割(轮转) +## 配置日志切割(轮转) 默认行为下,同一个日志对象 **会生成两个文件**。 @@ -508,14 +519,7 @@ export default { export default { midwayLogger: { default: { - transports: { - file: { - maxSize: '100m', - }, - error: { - maxSize: '100m', - }, - } + maxSize: '100m', }, // ... }, @@ -524,9 +528,9 @@ export default { -### 配置日志清理 +## 配置日志清理 -默认情况下,日志会存在 7 天。 +默认情况下,日志会存在 31 天。 可以通过配置调整该行为,比如改为保存 3 天。 @@ -534,43 +538,25 @@ export default { export default { midwayLogger: { default: { - transports: { - file: { - maxFiles: '3d', - }, - error: { - maxFiles: '3d', - }, - } + maxFiles: '3d', }, // ... }, } as MidwayConfig; ``` -也可以配置数字,表示最多保留日志文件的个数。 -```typescript -export default { - midwayLogger: { - default: { - transports: { - file: { - maxFiles: '3', - }, - error: { - maxFiles: '3d', - }, - } - }, - // ... - }, -} as MidwayConfig; -``` -### 配置自定义日志 + +## 高级配置 + +如果用户不满足于默认的日志对象,也可以自行创建和修改。 + + + +### 增加自定义日志 可以如下配置: @@ -624,19 +610,17 @@ export default { info 对象的默认属性如下: -| **属性名** | **描述** | **示例** | -| ----------- | ------------------------------------------------ | ----------------------- | -| timestamp | 时间戳,默认为 `'YYYY-MM-DD HH:mm:ss,SSS` 格式。 | 2020-12-30 07:50:10,453 | -| level | 小写的日志等级 | info | -| LEVEL | 大写的日志等级 | INFO | -| pid | 当前进程 pid | 3847 | -| message | util.format 的结果 | | -| args | 原始的用户入参 | [ 'a', 'b', 'c' ] | -| ctx | 使用 ContextLogger 时关联的上下文对象 | | -| originError | 原始错误对象,遍历参数后获取,性能较差 | 错误实例本身 | -| originArgs | 同 args,仅做兼容老版本使用 | | - - +| **属性名** | **描述** | **示例** | +| ----------- | ------------------------------------------------ | ------------------------------------------------------------ | +| timestamp | 时间戳,默认为 `'YYYY-MM-DD HH:mm:ss,SSS` 格式。 | 2020-12-30 07:50:10,453 | +| level | 小写的日志等级 | info | +| LEVEL | 大写的日志等级 | INFO | +| pid | 当前进程 pid | 3847 | +| labelText | 标签的聚合文本 | [abcde] | +| message | 普通消息 + 错误消息 + 错误堆栈的组合 | 1、普通文本,如 `123456` , `hello world`
2、错误文本(错误名+堆栈)Error: another test error at Object.anonymous (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18)
3、普通文本+错误文本 hello world Error: another test error at Object.anonymous (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18) | +| stack | 错误堆栈 | | +| originError | 原始错误对象 | 错误实例本身 | +| originArgs | 原始的用户入参 | [ 'a', 'b', 'c' ] | @@ -653,6 +637,7 @@ const contextLogger = customLogger.createContextLogger(ctx); ```typescript import { Provide } from '@midwayjs/core'; +import { IMidwayLogger } from '@midwayjs/logger'; import { Context } from '@midwayjs/koa'; @Provide() @@ -713,77 +698,64 @@ ctx.getLogger('customLogger').info('hello world'); -### 配置延迟初始化 +### 日志默认 Transport -可以使用 `lazyLoad` 配置让日志延迟初始化。 +每个日志包含几个默认的 Transport。 -比如: +| 名称 | 默认行为 | 描述 | +| ----------------- | -------- | ------------------------------ | +| Console Transport | 开启 | 用于输出到控制台 | +| File Transport | 开启 | 用于输出到文本文件 | +| Error Transport | 开启 | 用于将错误输出到特定的错误日志 | +| JSON Transport | 关闭 | 用于输出 JSON 格式的文本 | + +可以通过配置进行修改。 + +**示例:只开启控制台输出** ```typescript export default { midwayLogger: { clients: { - customLoggerA: { - // .. - }, - customLoggerB: { - lazyLoad: true, - }, + abcLogger: { + enableFile: false, + enableError: false, + // ... + } } // ... }, } as MidwayConfig; ``` -`customLoggerA` 会在框架启动时立即初始化,而 `customLoggerB` 会在业务实际第一次使用 `getLogger` 或者 `@Logger` 注入时才被初始化。 - -这个功能非常适合动态化创建日志,但是配置却希望合并到一起的场景。 - - - -### 配置关联日志 - -日志对象可以配置一个关联的日志对象名。 - -比如: +**示例:关闭控制台输出** ```typescript export default { midwayLogger: { clients: { - customLoggerA: { - aliasName: 'customLoggerB', + abcLogger: { + enableConsole: false, // ... - }, + } } // ... }, } as MidwayConfig; ``` -当使用 API 获取时,不同的名字将取到同样的日志对象。 - -```typescript -app.getLogger('customLoggerA') => customLoggerA -app.getLogger('customLoggerB') => customLoggerA -``` - - - -### 配置控制台输出颜色 - -控制台输出时,在命令行支持颜色输出的情况下,针对不同的的日志等级会输出不同的颜色,如果不支持颜色,则不会显示。 - -你可以通过配置直接关闭颜色输出。 +**示例:开启文本和 JSON 同步输出,关闭错误输出** ```typescript export default { midwayLogger: { - default: { - transports: { - console: { - autoColors: false, - } + clients: { + abcLogger: { + enableConsole: false, + enableFile: true, + enableError: false, + enableJSON: true, + // ... } } // ... @@ -793,183 +765,130 @@ export default { - - -## 自定义 Transport +### 自定义 Transport 框架提供了扩展 Transport 的功能,比如,你可以写一个 Transport 来做日志的中转,上传到别的日志库等能力。 - - -### 继承现有 Transport - -如果是写入到新的文件,可以通过使用 `FileTransport` 来实现。 +比如下面的示例,我们就将日志中转到另一个本地文件中。 ```typescript -import { FileTransport, isEnableLevel, LoggerLevel, LogMeta } from '@midwayjs/logger'; - -// Transport 的配置 -interface CustomOptions { - // ... -} +import { EmptyTransport } from '@midwayjs/logger'; -class CustomTransport extends FileTransport { - log(level: LoggerLevel | false, meta: LogMeta, ...args) { - // 判断 level 是否满足当前 Transport - if (!isEnableLevel(level, this.options.level)) { - return; - } - - // 使用内置的格式化方法格式化消息 - let buf = this.format(level, meta, args) as string; - // 加上换行符 - buf += this.options.eol; - - // 写入自己想写的日志 - if (this.options.bufferWrite) { - this.bufSize += buf.length; - this.buf.push(buf); - if (this.buf.length > this.options.bufferMaxLength) { - this.flush(); - } - } else { - // 没启用缓存,则直接写入 - this.logStream.write(buf); +class CustomTransport extends EmptyTransport { + log(info, callback) { + const levelLowerCase = info.level; + if (levelLowerCase === 'error' || levelLowerCase === 'warn') { + writeFileSync(join(logsDir, 'test.log'), info.message); } + callback(); } } ``` -在使用前,需要注册到日志库中。 +我们可以初始化,加到 logger 中,也可以单独对 Transport 设置 level。 ```typescript -import { TransportManager } from '@midwayjs/logger'; +const customTransport = new CustomTransport({ + level: 'warn', +}); -TransportManager.set('custom', CustomTransport); -``` - -之后就可以在配置中使用这个 Transport 了。 - -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; - -export default { - midwayLogger: { - default: { - transports: { - custom: { - dir: 'xxxx', - fileLogName: 'xxx', - // ... - } - } - } - }, -} as MidwayConfig; +logger.add(customTransport); ``` 这样,原有的 logger 打印日志时,会自动执行该 Transport。 +所有的 Transport 是附加在原有的 logger 实例之上(非 context logger),如需 ctx 数据,可以从 info 获取,注意判空。 -### 完全自定义 Transport - -除了写入文件之外,也可以将日志投递到远端服务,比如下面的示例,将日志中转到另一个服务。 - -注意,Transport 是一个可异步执行的操作,但是 logger 本身不会等待 Transport 执行返回。 - ```typescript -import { Transport, ITransport, LoggerLevel, LogMeta } from '@midwayjs/logger'; - - -// Transport 的配置 -interface CustomOptions { - // ... -} - -class CustomTransport extends Transport implements ITransport { - log(level: LoggerLevel | false, meta: LogMeta, ...args) { - // 使用内置的格式化方法格式化消息 - let msg = this.format(level, meta, args) as string; - - // 异步写入日志库 - remoteSdk.send(msg).catch(err => { - // 记录下错误或者忽略 - console.error(err); - }); +class CustomTransport extends EmptyTransport { + log(info, callback) { + if (info.ctx) { + // ... + } else { + // ... + } + callback(); } } ``` - -## 动态 API - -通过 `getLogger` 方法动态获取日志对象。 +我们也可以使用依赖注入的方式来定义 Transport。 ```typescript -// 获取 coreLogger -const coreLogger = app.getLogger('coreLogger'); -// 获取默认的 contextLogger -const contextLogger = ctx.getLogger(); -// 获取特定 logger 创建出来的 contextLogger,等价于 customALogger.createContextLogger(ctx) -const customAContextLogger = ctx.getLogger('customA'); -``` - -框架内置的 `MidwayLoggerService` 也拥有上述的 API。 - -```typescript -import { MidwayLoggerService } from '@midwayjs/core'; -import { Context } from '@midwayjs/koa'; +import { EmptyTransport, IMidwayLogger } from '@midwayjs/logger'; +import { MidwayLoggerService, Provide, Scope, ScopeEnum } from '@midwayjs/core'; @Provide() +@Scope(ScopeEnum) +export class CustomTransport extends EmptyTransport { + log(info, callback) { + // ... + callback(); + } +} + +// src/configuration.ts +@Configuration(/*...*/) export class MainConfiguration { - + @Inject() loggerService: MidwayLoggerService; - + @Inject() - ctx: Context; - - async getUser() { - // get custom logger - const customLogger = this.loggerService.getLogger('customLogger'); - - // 创建 context logger - const customContextLogger = this.loggerService.createContextLogger(this.ctx, customLogger); + customTransport: CustomTransport; + + async onReady() { + const appLogger = this.loggerService.getLogger('customLogger') as IMidwayLogger; + appLogger.add(this.customTransport); } } ``` -## 常见问题 +### 延迟初始化 +可以使用 `lazyLoad` 配置让日志延迟初始化。 +比如: -### 1、服务器环境日志不输出 +```typescript +export default { + midwayLogger: { + clients: { + customLoggerA: { + level: 'DEBUG', + }, + customLoggerB: { + lazyLoad: true, + }, + } + // ... + }, +} as MidwayConfig; +``` -我们不推荐在服务器环境打印太多的日志,只打印必须的内容,过多的日志输出影响性能,也影响快速定位问题。 +`customLoggerA` 会在框架启动时立即初始化,而 `customLoggerB` 会在业务实际第一次使用 `getLogger` 或者 `@Logger` 注入时才被初始化。 -如需调整日志等级,请查看 ”配置日志等级“ 部分。 +这个功能非常适合动态化创建日志,但是配置却希望合并到一起的场景。 -### 2、服务器没有控制台日志 +## 常见问题 -一般来说,服务器控制台日志(console)是关闭的,只会输出到文件中,如有特殊需求,可以单独调整。 +### 1、服务器环境日志不输出 -### 3、部分 Docker 环境启动失败 +服务器环境,默认日志等级为 warn,即 `logger.warn` 才会打印输出,请查看 ”日志等级“ 部分。 -检查日志写入的目录当前应用启动的用户是否有权限。 +我们不推荐在服务器环境打印太多的日志,只打印必须的内容,过多的日志输出影响性能,也影响快速定位问题。 -### 4、如果有老的配置如何转换 +### 2、服务器没有控制台日志 -新版本日志库已经兼容老配置,一般情况下无需额外处理,老配置和新配置在合并时有优先级关系,请查看 [变更文档](https://github.com/midwayjs/logger/blob/main/BREAKING-3.md)。 +一般来说,服务器控制台日志(console)是关闭的,只会输出到文件中,如有特殊需求,可以单独调整。 -为了减少排查问题,在使用新版本日志库时请尽可能使用新配置格式。 diff --git a/site/docs/legacy/logger.md b/site/docs/logger_v3.md similarity index 60% rename from site/docs/legacy/logger.md rename to site/docs/logger_v3.md index 7de974032d07..f11540f456c1 100644 --- a/site/docs/legacy/logger.md +++ b/site/docs/logger_v3.md @@ -1,15 +1,7 @@ # 日志 -:::tip - -本文档为 `@midwayjs/logger` v2.0 版本的文档。 - -::: - Midway 为不同场景提供了一套统一的日志接入方式。通过 `@midwayjs/logger` 包导出的方法,可以方便的接入不同场景的日志系统。 -Midway 的日志系统基于社区的 [winston](https://github.com/winstonjs/winston),是现在社区非常受欢迎的日志库。 - 实现的功能有: - 日志分级 @@ -17,6 +9,38 @@ Midway 的日志系统基于社区的 [winston](https://github.com/winstonjs/win - 自定义输出格式 - 统一错误日志 +:::tip + +当前版本为 3.0 的日志 SDK 文档,如需 2.0 版本,请查看 [这个文档](/docs/legacy/logger)。 + +::: + + + +## 从 2.0 升级到 3.0 + +从 midway v3.13.0 开始,支持使用 3.0 版本的 `@midwayjs/logger`。 + +将 `package.json` 中的依赖版本升级,注意是 `dependencies` 依赖。 + +```diff +{ + "dependencies": { +- "@midwayjs/logger": "2.0.0", ++ "@midwayjs/logger": "^3.0.0" + } +} +``` + +如果在配置中没有了 midwayLogger 的类型提示,你需要在 `src/interface.ts` 中加入日志库的引用。 + +```diff +// src/interface.ts ++ import type {} from '@midwayjs/logger'; +``` + +在大部分场景下,两个版本是兼容的,但是由于是大版本升级,肯定会有一定的差异性,完整的 Breaking Change 变化,请查看 [变更文档](https://github.com/midwayjs/logger/blob/main/BREAKING-3.md)。 + ## 日志路径和文件 @@ -25,7 +49,7 @@ Midway 会在日志根目录创建一些默认的文件。 - `midway-core.log` 框架、组件打印信息的日志,对应 `coreLogger` 。 -- `midway-app.log` 应用打印信息的日志,对应 `appLogger` +- `midway-app.log` 应用打印信息的日志,对应 `appLogger`,在 `@midawyjs/web` 中,该文件是 `midway-web.log` - `common-error.log` 所有错误的日志(所有 Midway 创建出来的日志,都会将错误重复打印一份到该文件中) 本地开发和服务器部署时的 **日志路径** 和 **日志等级** 不同,具体请参考 [配置日志根目录](#配置日志根目录) 和 [框架的默认等级](#框架的默认等级)。 @@ -39,8 +63,8 @@ Midway 默认在框架提供了三种不同的日志,对应三种不同的行 | 日志 | 释义 | 描述 | 常见使用 | | ----------------------------------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | coreLogger | 框架,组件层面的日志 | 默认会输出控制台日志和文本日志 `midway-core.log` ,并且默认会将错误日志发送到 `common-error.log` 。 | 框架和组件的错误,一般会打印到其中。 | -| appLogger | 业务层面的日志 | 默认会输出控制台日志和文本日志 `midway-app.log` ,并且默认会将错误日志发送到 `common-error.log` 。 | 业务使用的日志,一般业务日志会打印到其中。 | -| 上下文日志(复用 appLogger 的配置) | 请求链路的日志 | 默认使用 `appLogger` 进行输出,除了会将错误日志发送到 `common-error.log` 之外,还增加了上下文信息。 | 修改日志输出的标记(Label),不同的框架有不同的请求标记,比如 HTTP 下就会输出路由信息。 | +| appLogger | 业务层面的日志 | 默认会输出控制台日志和文本日志 `midway-app.log` ,并且默认会将错误日志发送到 `common-error.log` ,在 `@midawyjs/web` 中,该文件是 `midway-web.log`。 | 业务使用的日志,一般业务日志会打印到其中。 | +| 上下文日志(复用 appLogger 的配置) | 请求链路的日志 | 默认使用 `appLogger` 进行输出,除了会将错误日志发送到 `common-error.log` 之外,还增加了上下文信息。 | 不同的协议有不同的请求日志格式,比如 HTTP 下就会输出路由信息。 | @@ -74,7 +98,6 @@ ctx.logger.error(new Error('custom error')); 输出结果: - ```text 2021-07-22 14:50:59,388 INFO 7739 [-/::ffff:127.0.0.1/-/0ms GET /api/get_user] hello world ``` @@ -141,6 +164,7 @@ export class MainConfiguration implements ILifeCycle { 在组件或者框架层面的研发中,我们会使用 coreLogger 来记录日志。 ```typescript + @Configuration() export class MainConfiguration implements ILifeCycle { @@ -161,83 +185,33 @@ export class MainConfiguration implements ILifeCycle { - - ## 输出方法和格式 -Midway 的日志对象继承与 winston 的日志对象,一般情况下,只提供 `error()` , `warn()` , `info()` , `debug` 四种方法。 +Midway 的日志对象提供 `error()` , `warn()` , `info()` , `debug()`,`write()` 五种方法。 示例如下。 - ```typescript logger.debug('debug info'); logger.info('启动耗时 %d ms', Date.now() - start); logger.warn('warning!'); logger.error(new Error('my error')); +logger.write('abcdef'); ``` +:::tip -### 默认的输出行为 - - -在大部分的普通类型下,日志库都能工作的很好。 - - -比如: - -```typescript -logger.info('hello world'); // 输出字符串 -logger.info(123); // 输出数字 -logger.info(['b', 'c']); // 输出数组 -logger.info(new Set([2, 3, 4])); // 输出 Set -logger.info(new Map([['key1', 'value1'], ['key2', 'value2']])); // 输出 Map -``` - -> Midway 针对 winston 无法输出的 `Array` , `Set` , `Map` 类型,做了特殊定制,使其也能够正常的输出。 - - -不过需要注意的是,日志对象在一般情况下,只能传入一个参数,它的第二个参数有其他作用。 - -```typescript -logger.info('plain error message', 321); // 会忽略 321 -``` - - -### 错误输出 - - -针对错误对象,Midway 也对 winston 做了定制,使其能够方便的和普通文本结合到一起输出。 - -```typescript -// 输出错误对象 -logger.error(new Error('error instance')); - -// 输出自定义的错误对象 -const error = new Error('named error instance'); -error.name = 'NamedError'; -logger.error(error); +`write` 方法用于输出用户的原始格式日志。 -// 文本在前,加上 error 实例 -logger.info('text before error', new Error('error instance after text')); -``` - -:::caution -注意,错误对象只能放在最后,且有且只有一个,其后面的所有参数都会被忽略。 ::: - -### 格式化内容 - 基于 `util.format` 的格式化方式。 - ```typescript logger.info('%s %d', 'aaa', 222); ``` - 常用的有 @@ -249,100 +223,56 @@ logger.info('%s %d', 'aaa', 222); -### 输出自定义对象或者复杂类型 - - -基于性能考虑,Midway(winston)大部分时间只会输出基本类型,所以当输出的参数为高级对象时,**需要用户手动转换为需要打印的字符串**。 - - -如下示例,将不会得到希望的结果。 - -```typescript -const obj = {a: 1}; -logger.info(obj); // 默认情况下,输出 [object Object] -``` - -需要手动输出希望打印的内容。 - -```typescript -const obj = {a: 1}; -logger.info(JSON.stringify(obj)); // 可以输出格式化文本 -logger.info(obj.a); // 直接输出属性值 -logger.info('%j', a); // 直接占位符输出整个 json -``` - - - -### 纯输出内容 - - -特殊场景下,我们需要单纯的输出内容,不希望输出时间戳,label 等和格式相关的信息。这种需求我们可以使用 `write` 方法。 - -`write` 方法是个非常底层的方法,并且不管什么级别的日志,它都会写入到文件中。 - - -虽然 `write` 方法在每个 logger 上都有,但是我们只在 `IMidwayLogger` 定义中提供它,我们希望你能明确的知道自己希望调用它。 - -```typescript -(logger as IMidwayLogger).write('hello world'); // 文件中只会有 hello world -``` - - - ## 日志类型定义 -默认的情况,用户应该使用最简单的 `ILogger` 定义。 - +大部分情况下,用户应该使用 `@midwayjs/core` 中最简单的 `ILogger` 定义。 ```typescript -import { Provide, Logger } from '@midwayjs/core'; -import { ILogger } from '@midwayjs/logger'; +import { Provide, Logger, ILogger } from '@midwayjs/core'; @Provide() export class UserService { @Inject() - logger: ILogger; // 获取上下文日志 + logger: ILogger; async getUser() { this.logger.info('hello user'); } - } ``` - `ILogger` 定义只提供最简单的 `debug` , `info` , `warn` 以及 `error` 方法。 -在某些场景下,我们需要更为复杂的定义,比如修改日志属性或者动态调节,这个时候需要使用更为复杂的 `IMidwayLogger` 定义。 +在某些场景下,我们需要更为复杂的定义,这个时候需要使用 `@midwayjs/logger` 提供的 `ILogger` 定义。 ```typescript import { Provide, Logger } from '@midwayjs/core'; -import { IMidwayLogger } from '@midwayjs/logger'; +import { ILogger } from '@midwayjs/logger'; @Provide() export class UserService { @Inject() - logger: IMidwayLogger; // 获取上下文日志 + logger: ILogger; async getUser() { - this.logger.disableConsole(); // 禁止控制台输出 - this.logger.info('hello user'); // 这句话在控制台看不到 - this.logger.enableConsole(); // 开启控制台输出 - this.logger.info('hello user'); // 这句话在控制台可以看到 + // ... } } ``` +`ILogger` 的定义可以参考 interface 中的描述,或者查看 [代码](https://github.com/midwayjs/logger/blob/main/src/interface.ts)。 + + -`IMidwayLogger` 的定义可以参考 interface 中的描述,或者查看 [代码](https://github.com/midwayjs/logger/blob/main/src/interface.ts)。 +## 日志配置 -## 日志基本配置 +### 基本配置结构 我们可以在配置文件中配置日志的各种行为。 @@ -371,61 +301,111 @@ export default { 如上所述,`clients` 配置段中的每个对象都是一个独立的日志配置项,其配置会和 `default` 段落合并后创建 logger 实例。 -如果你发现没有定义,请将 `@midawyjs/logger` 在 `src/interface.ts` 中显式声明一次。 - -```typescript -// ... -import type {} from '@midwayjs/logger'; -``` +### 默认 Transport +在 Midway 中,默认启用了 `console`,`file`,`error` 三个 Transport,更多信息可以通过配置进行修改。 +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; -## 配置日志等级 - +export default { + midwayLogger: { + default: { + transports: { + console: { + // console transport 配置 + }, + file: { + // file transport 配置 + }, + error: { + // error transport 配置 + }, + } + }, + // ... + }, +} as MidwayConfig; +``` -winston 的日志等级分为下面几类,日志等级依次降低(数字越大,等级越低): +如果不需要某个 transport,可以设置为 `false`。 ```typescript -const levels = { - none: 0, - error: 1, - trace: 2, - warn: 3, - info: 4, - verbose: 5, - debug: 6, - silly: 7, - all: 8, -} +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + transports: { + console: false, + } + }, + // ... + }, +} as MidwayConfig; ``` -在 Midway 中,为了简化,一般情况下,我们只会使用 `error` , `warn` , `info` , `debug` 这四种等级。 + + +### 配置日志等级 + +在 Midway 中,一般情况下,我们只会使用 `error` , `warn` , `info` , `debug` 这四种等级。 日志等级表示当前可输出日志的最低等级。比如当你的日志 level 设置为 `warn` 时,仅 `warn` 以及更高的 `error` 等级的日志能被输出。 -在 Midway 中,针对不同的输出行为,可以配置不同的日志等级。 -- `level` 写入文本的日志等级 -- `consoleLevel` 控制台输出的日志等级 +在 Midway 中,有着自己的默认日志等级。 +- 在开发环境下(local,test,unittest),文本和控制台日志等级统一为 `info` 。 +- 在服务器环境,为减少日志数量,`coreLogger` 日志等级为 `warn` ,而其他日志为 `info`。 -### 框架的默认等级 +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; +export default { + midwayLogger: { + default: { + level: 'info', + }, + // ... + }, +} as MidwayConfig; +``` -在 Midway 中,有着自己的默认日志等级。 -- 在开发环境下(local,test,unittest),文本和控制台日志等级统一为 `info` 。 -- 在服务器环境(除开发环境外),为减少日志数量,`coreLogger` 日志等级为 `warn` ,而其他日志为 `info`。 +logger 的 level 和 Transport 的 level 可以分开设置,Tranport 的 level 优先级高于 logger 的 level。 +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + // logger 的 level + level: 'info', + transports: { + file: { + // file transport 的 level + level: 'warn' + } + } + }, + // ... + }, +} as MidwayConfig; +``` -### 调整日志等级 -一般情况下,我们不建议调整全局默认的日志等级,而是调整特定的 logger 的日志等级,比如: +我们也可以调整特定的 logger 的日志等级,比如: 调整 `coreLogger` 或者 `appLogger` 。 @@ -438,12 +418,10 @@ export default { clients: { coreLogger: { level: 'warn', - consoleLevel: 'warn' // ... }, appLogger: { level: 'warn', - consoleLevel: 'warn' // ... } } @@ -461,7 +439,11 @@ export default { midwayLogger: { default: { level: 'info', - consoleLevel: 'warn' + transports: { + console: { + level: 'warn' + } + } }, // ... }, @@ -470,7 +452,7 @@ export default { -## 配置日志根目录 +### 配置日志根目录 默认情况下,Midway 会在本地开发和服务器部署时输出日志到 **日志根目录**。 @@ -478,7 +460,7 @@ export default { - 本地的日志根目录为 `${app.appDir}/logs/项目名` 目录下 - 服务器的日志根目录为用户目录 `${process.env.HOME}/logs/项目名` (Linux/Mac)以及 `${process.env.USERPROFILE}/logs/项目名` (Windows)下,例如 `/home/admin/logs/example-app`。 -我们可以配置日志所在的根目录。 +我们可以配置日志所在的根目录,注意,要将所有 Transport 的路径都修改。 ```typescript // src/config/config.default.ts @@ -487,7 +469,14 @@ import { MidwayConfig } from '@midwayjs/core'; export default { midwayLogger: { default: { - dir: '/home/admin/logs', + transports: { + file: { + dir: '/home/admin/logs', + }, + error: { + dir: '/home/admin/logs', + }, + } }, // ... }, @@ -496,7 +485,7 @@ export default { -## 配置日志切割(轮转) +### 配置日志切割(轮转) 默认行为下,同一个日志对象 **会生成两个文件**。 @@ -519,7 +508,14 @@ export default { export default { midwayLogger: { default: { - maxSize: '100m', + transports: { + file: { + maxSize: '100m', + }, + error: { + maxSize: '100m', + }, + } }, // ... }, @@ -528,9 +524,9 @@ export default { -## 配置日志清理 +### 配置日志清理 -默认情况下,日志会存在 31 天。 +默认情况下,日志会存在 7 天。 可以通过配置调整该行为,比如改为保存 3 天。 @@ -538,25 +534,43 @@ export default { export default { midwayLogger: { default: { - maxFiles: '3d', + transports: { + file: { + maxFiles: '3d', + }, + error: { + maxFiles: '3d', + }, + } }, // ... }, } as MidwayConfig; ``` +也可以配置数字,表示最多保留日志文件的个数。 +```typescript +export default { + midwayLogger: { + default: { + transports: { + file: { + maxFiles: '3', + }, + error: { + maxFiles: '3d', + }, + } + }, + // ... + }, +} as MidwayConfig; +``` - -## 高级配置 - -如果用户不满足于默认的日志对象,也可以自行创建和修改。 - - - -### 增加自定义日志 +### 配置自定义日志 可以如下配置: @@ -610,17 +624,19 @@ export default { info 对象的默认属性如下: -| **属性名** | **描述** | **示例** | -| ----------- | ------------------------------------------------ | ------------------------------------------------------------ | -| timestamp | 时间戳,默认为 `'YYYY-MM-DD HH:mm:ss,SSS` 格式。 | 2020-12-30 07:50:10,453 | -| level | 小写的日志等级 | info | -| LEVEL | 大写的日志等级 | INFO | -| pid | 当前进程 pid | 3847 | -| labelText | 标签的聚合文本 | [abcde] | -| message | 普通消息 + 错误消息 + 错误堆栈的组合 | 1、普通文本,如 `123456` , `hello world`
2、错误文本(错误名+堆栈)Error: another test error at Object.anonymous (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18)
3、普通文本+错误文本 hello world Error: another test error at Object.anonymous (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18) | -| stack | 错误堆栈 | | -| originError | 原始错误对象 | 错误实例本身 | -| originArgs | 原始的用户入参 | [ 'a', 'b', 'c' ] | +| **属性名** | **描述** | **示例** | +| ----------- | ------------------------------------------------ | ----------------------- | +| timestamp | 时间戳,默认为 `'YYYY-MM-DD HH:mm:ss,SSS` 格式。 | 2020-12-30 07:50:10,453 | +| level | 小写的日志等级 | info | +| LEVEL | 大写的日志等级 | INFO | +| pid | 当前进程 pid | 3847 | +| message | util.format 的结果 | | +| args | 原始的用户入参 | [ 'a', 'b', 'c' ] | +| ctx | 使用 ContextLogger 时关联的上下文对象 | | +| originError | 原始错误对象,遍历参数后获取,性能较差 | 错误实例本身 | +| originArgs | 同 args,仅做兼容老版本使用 | | + + @@ -637,7 +653,6 @@ const contextLogger = customLogger.createContextLogger(ctx); ```typescript import { Provide } from '@midwayjs/core'; -import { IMidwayLogger } from '@midwayjs/logger'; import { Context } from '@midwayjs/koa'; @Provide() @@ -698,64 +713,77 @@ ctx.getLogger('customLogger').info('hello world'); -### 日志默认 Transport - -每个日志包含几个默认的 Transport。 - -| 名称 | 默认行为 | 描述 | -| ----------------- | -------- | ------------------------------ | -| Console Transport | 开启 | 用于输出到控制台 | -| File Transport | 开启 | 用于输出到文本文件 | -| Error Transport | 开启 | 用于将错误输出到特定的错误日志 | -| JSON Transport | 关闭 | 用于输出 JSON 格式的文本 | +### 配置延迟初始化 -可以通过配置进行修改。 +可以使用 `lazyLoad` 配置让日志延迟初始化。 -**示例:只开启控制台输出** +比如: ```typescript export default { midwayLogger: { clients: { - abcLogger: { - enableFile: false, - enableError: false, - // ... - } + customLoggerA: { + // .. + }, + customLoggerB: { + lazyLoad: true, + }, } // ... }, } as MidwayConfig; ``` -**示例:关闭控制台输出** +`customLoggerA` 会在框架启动时立即初始化,而 `customLoggerB` 会在业务实际第一次使用 `getLogger` 或者 `@Logger` 注入时才被初始化。 + +这个功能非常适合动态化创建日志,但是配置却希望合并到一起的场景。 + + + +### 配置关联日志 + +日志对象可以配置一个关联的日志对象名。 + +比如: ```typescript export default { midwayLogger: { clients: { - abcLogger: { - enableConsole: false, + customLoggerA: { + aliasName: 'customLoggerB', // ... - } + }, } // ... }, } as MidwayConfig; ``` -**示例:开启文本和 JSON 同步输出,关闭错误输出** +当使用 API 获取时,不同的名字将取到同样的日志对象。 + +```typescript +app.getLogger('customLoggerA') => customLoggerA +app.getLogger('customLoggerB') => customLoggerA +``` + + + +### 配置控制台输出颜色 + +控制台输出时,在命令行支持颜色输出的情况下,针对不同的的日志等级会输出不同的颜色,如果不支持颜色,则不会显示。 + +你可以通过配置直接关闭颜色输出。 ```typescript export default { midwayLogger: { - clients: { - abcLogger: { - enableConsole: false, - enableFile: true, - enableError: false, - enableJSON: true, - // ... + default: { + transports: { + console: { + autoColors: false, + } } } // ... @@ -765,130 +793,183 @@ export default { -### 自定义 Transport + + +## 自定义 Transport 框架提供了扩展 Transport 的功能,比如,你可以写一个 Transport 来做日志的中转,上传到别的日志库等能力。 -比如下面的示例,我们就将日志中转到另一个本地文件中。 + + +### 继承现有 Transport + +如果是写入到新的文件,可以通过使用 `FileTransport` 来实现。 ```typescript -import { EmptyTransport } from '@midwayjs/logger'; +import { FileTransport, isEnableLevel, LoggerLevel, LogMeta } from '@midwayjs/logger'; + +// Transport 的配置 +interface CustomOptions { + // ... +} -class CustomTransport extends EmptyTransport { - log(info, callback) { - const levelLowerCase = info.level; - if (levelLowerCase === 'error' || levelLowerCase === 'warn') { - writeFileSync(join(logsDir, 'test.log'), info.message); +class CustomTransport extends FileTransport { + log(level: LoggerLevel | false, meta: LogMeta, ...args) { + // 判断 level 是否满足当前 Transport + if (!isEnableLevel(level, this.options.level)) { + return; + } + + // 使用内置的格式化方法格式化消息 + let buf = this.format(level, meta, args) as string; + // 加上换行符 + buf += this.options.eol; + + // 写入自己想写的日志 + if (this.options.bufferWrite) { + this.bufSize += buf.length; + this.buf.push(buf); + if (this.buf.length > this.options.bufferMaxLength) { + this.flush(); + } + } else { + // 没启用缓存,则直接写入 + this.logStream.write(buf); } - callback(); } } ``` -我们可以初始化,加到 logger 中,也可以单独对 Transport 设置 level。 +在使用前,需要注册到日志库中。 ```typescript -const customTransport = new CustomTransport({ - level: 'warn', -}); +import { TransportManager } from '@midwayjs/logger'; -logger.add(customTransport); +TransportManager.set('custom', CustomTransport); +``` + +之后就可以在配置中使用这个 Transport 了。 + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + transports: { + custom: { + dir: 'xxxx', + fileLogName: 'xxx', + // ... + } + } + } + }, +} as MidwayConfig; ``` 这样,原有的 logger 打印日志时,会自动执行该 Transport。 -所有的 Transport 是附加在原有的 logger 实例之上(非 context logger),如需 ctx 数据,可以从 info 获取,注意判空。 +### 完全自定义 Transport + +除了写入文件之外,也可以将日志投递到远端服务,比如下面的示例,将日志中转到另一个服务。 + +注意,Transport 是一个可异步执行的操作,但是 logger 本身不会等待 Transport 执行返回。 + ```typescript -class CustomTransport extends EmptyTransport { - log(info, callback) { - if (info.ctx) { - // ... - } else { - // ... - } - callback(); +import { Transport, ITransport, LoggerLevel, LogMeta } from '@midwayjs/logger'; + + +// Transport 的配置 +interface CustomOptions { + // ... +} + +class CustomTransport extends Transport implements ITransport { + log(level: LoggerLevel | false, meta: LogMeta, ...args) { + // 使用内置的格式化方法格式化消息 + let msg = this.format(level, meta, args) as string; + + // 异步写入日志库 + remoteSdk.send(msg).catch(err => { + // 记录下错误或者忽略 + console.error(err); + }); } } ``` -我们也可以使用依赖注入的方式来定义 Transport。 + +## 动态 API + +通过 `getLogger` 方法动态获取日志对象。 ```typescript -import { EmptyTransport, IMidwayLogger } from '@midwayjs/logger'; -import { MidwayLoggerService, Provide, Scope, ScopeEnum } from '@midwayjs/core'; +// 获取 coreLogger +const coreLogger = app.getLogger('coreLogger'); +// 获取默认的 contextLogger +const contextLogger = ctx.getLogger(); +// 获取特定 logger 创建出来的 contextLogger,等价于 customALogger.createContextLogger(ctx) +const customAContextLogger = ctx.getLogger('customA'); +``` -@Provide() -@Scope(ScopeEnum) -export class CustomTransport extends EmptyTransport { - log(info, callback) { - // ... - callback(); - } -} +框架内置的 `MidwayLoggerService` 也拥有上述的 API。 -// src/configuration.ts -@Configuration(/*...*/) -export class MainConfiguration { +```typescript +import { MidwayLoggerService } from '@midwayjs/core'; +import { Context } from '@midwayjs/koa'; +@Provide() +export class MainConfiguration { + @Inject() loggerService: MidwayLoggerService; - + @Inject() - customTransport: CustomTransport; - - async onReady() { - const appLogger = this.loggerService.getLogger('customLogger') as IMidwayLogger; - appLogger.add(this.customTransport); + ctx: Context; + + async getUser() { + // get custom logger + const customLogger = this.loggerService.getLogger('customLogger'); + + // 创建 context logger + const customContextLogger = this.loggerService.createContextLogger(this.ctx, customLogger); } } ``` -### 延迟初始化 +## 常见问题 -可以使用 `lazyLoad` 配置让日志延迟初始化。 -比如: -```typescript -export default { - midwayLogger: { - clients: { - customLoggerA: { - level: 'DEBUG', - }, - customLoggerB: { - lazyLoad: true, - }, - } - // ... - }, -} as MidwayConfig; -``` +### 1、服务器环境日志不输出 -`customLoggerA` 会在框架启动时立即初始化,而 `customLoggerB` 会在业务实际第一次使用 `getLogger` 或者 `@Logger` 注入时才被初始化。 +我们不推荐在服务器环境打印太多的日志,只打印必须的内容,过多的日志输出影响性能,也影响快速定位问题。 -这个功能非常适合动态化创建日志,但是配置却希望合并到一起的场景。 +如需调整日志等级,请查看 ”配置日志等级“ 部分。 -## 常见问题 +### 2、服务器没有控制台日志 +一般来说,服务器控制台日志(console)是关闭的,只会输出到文件中,如有特殊需求,可以单独调整。 -### 1、服务器环境日志不输出 -服务器环境,默认日志等级为 warn,即 `logger.warn` 才会打印输出,请查看 ”日志等级“ 部分。 +### 3、部分 Docker 环境启动失败 -我们不推荐在服务器环境打印太多的日志,只打印必须的内容,过多的日志输出影响性能,也影响快速定位问题。 +检查日志写入的目录当前应用启动的用户是否有权限。 -### 2、服务器没有控制台日志 +### 4、如果有老的配置如何转换 -一般来说,服务器控制台日志(console)是关闭的,只会输出到文件中,如有特殊需求,可以单独调整。 +新版本日志库已经兼容老配置,一般情况下无需额外处理,老配置和新配置在合并时有优先级关系,请查看 [变更文档](https://github.com/midwayjs/logger/blob/main/BREAKING-3.md)。 +为了减少排查问题,在使用新版本日志库时请尽可能使用新配置格式。 diff --git a/site/docs/sidebars.json b/site/docs/sidebars.json index 09d46a72e719..4a432573d740 100644 --- a/site/docs/sidebars.json +++ b/site/docs/sidebars.json @@ -43,7 +43,7 @@ "environment", "env_config", "lifecycle", - "logger", + "logger_v3", "cookie_session", "built_in_service", "router_table", @@ -189,6 +189,15 @@ "contributing", "awesome_midway" ] + }, + { + "type": "category", + "label": "历史文档", + "collapsed": true, + "collapsible": true, + "items": [ + "logger" + ] } ], "component": [ @@ -289,6 +298,18 @@ "extensions/alinode", "extensions/prometheus" ] + }, + { + "type": "category", + "label": "历史废弃组件", + "collapsed": false, + "collapsible": false, + "items": [ + "legacy/mongodb", + "legacy/sequelize", + "legacy/orm", + "legacy/task" + ] } ] } diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/logger.md b/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/logger.md deleted file mode 100644 index 44c9e0e65755..000000000000 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/logger.md +++ /dev/null @@ -1,869 +0,0 @@ -# Logger - -:::tip - -This document is for `@midwayjs/logger` v2.0 version. - -::: - -Midway provides a unified log access method for different scenarios. The `@midwayjs/logger` package export method allows you to easily access log systems in different scenarios. - -Midway's log system is based on the [winston](https://github.com/winstonjs/winston) of the community and is now a very popular log library in the community. - -The functions realized are: - -- Log classification -- Automatic cutting by size and time -- Custom output format -- Unified error log - - - -## Loger path and file - -Midway creates some default files in the log root directory. - - -- `midway-core.log` logs of printed information of the framework and components, corresponding to the `coreLogger`. -- `midway-app.log` applies the log of printing information, corresponding to the `appLogger` -- `common-error.log` The log of all errors (all logs created by Midway will repeatedly print errors to this file) - -The **Log Path** and **Log Level** of local development and server deployment are different. For more information, see [Configure Log Root](# Configure the log root directory) and [Default Level](# The default level of the framework). - - - -## Default loger object - -Midway provides three different logs in the framework by default, corresponding to three different behaviors. - -| Log | Interpretation | Description | Common use | -| ----------------------------------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| coreLogger | Framework, component-level logs | By default, the console log and text log `midway-core.log` are output, and the error log is sent to `common-error.log` by default. | Frames and component errors are generally printed into them. | -| appLogger | Logs at the business level | The `midway-app.log` of the console log and text log is output by default, and the error log is sent to `common-error.log` by default. | The log used by the business. Generally, the business log will be printed in it. | -| Context Logger (Configuration of Multiplexing appLogger) | Log of request link | By default, `appLogger` is used for output. In addition to sending error logs to `common-error.log`, context information is added. | Modify the label (Label) of log output. Different frameworks have different request labels. For example, under HTTP, routing information will be output. | - - - -## Use log - -Midway's common log usage method. - -### Context log - -The context log is the log associated with the framework context object (Context). - -You can [obtain the ctx object](./req_res_app) and then use the `ctx.logger` object to print and output logs. - -For example: - -```typescript -ctx.logger.info("hello world"); -ctx.logger.debug('debug info'); -ctx.logger.warn('WARNNING!!!!'); - -// Error log recording will directly record the complete stack information of the error log and output it to the errorLog -// In order to ensure that exceptions can be traced, all exceptions thrown must be of Error type, because only Error type will bring stack information to locate the problem. -ctx.logger.error(new Error('custom error')); -``` - -After execution, we can see the log output in two places: - - -- The console sees the output. -- In the midway-app.log file of the log directory - - -Output result: -```text -2021-07-22 14:50:59,388 INFO 7739 [-/::ffff:127.0.0.1/-/0ms GET /api/get_user] hello world -``` - -In the injection form, you can also use `@Inject() logger` to inject `ctx.logger`, which is equivalent to calling `ctx.logger` directly. - -For example: - -```typescript -import { Get, Inject, Controller, Provide } from '@midwayjs/core'; -import { ILogger } from '@midwayjs/logger'; - -@Controller() -export class HelloController { - - @Inject() - logger: ILogger; - - @Inject() - ctx; - - @Get("/") - async hello() { - // ... - - // this.logger === ctx.logger - } -} -``` - - - -### App Logger - -If we want to do some application-level logging, such as recording some data information during the startup phase, we can do it through App Logger. - -```typescript -import { Configuration, Logger } from '@midwayjs/core'; -import { ILogger } from '@midwayjs/logger'; - -@Configuration() -export class MainConfiguration implements ILifeCycle { - - @Logger() - logger: ILogger; - - async onReady(container: IMidwayContainer): Promise { - this.logger.debug('debug info'); - This.logger.info ('startup takes% d ms', Date.now() - start); - this.logger.warn('warning!'); - - this.logger.error(someErrorObj); - } - -} -``` - -Note that the `@Logger()` decorator is used here. - - - -### CoreLogger - -In research and development at the component or framework level, we will use coreLogger to log. - -```typescript - -@Configuration() -export class MainConfiguration implements ILifeCycle { - - @Logger('coreLogger') - logger: ILogger; - - async onReady(container: IMidwayContainer): Promise { - this.logger.debug('debug info'); - This.logger.info ('startup takes% D MS', Date.now() -Start); - this.logger.warn('warning!'); - - this.logger.error(someErrorObj); - } - -} -``` - - - - - - -## Output method and format - - -The log object of Midway inherits the log object of the winston. In general, only four methods are provided: `error()`, `war ()`, `info()`, and `debug`. - - -An example is as follows. -```typescript -logger.debug('debug info'); -logger.info('startup takes% d ms', Date.now() - start); -logger.warn('warning!'); -logger.error(new Error('my error')); -``` - - -### Default output behavior - - -In most common types, the logstore works well. - - -For example: -```typescript -logger.info('hello world'); // Output string -logger.info(123); //Output Number -logger.info(['B', 'c']); // Output array -logger.info(new Set([2, 3, 4])); // Output Set -logger.info(new Map([['key1', 'value1'], ['key2', 'value2']])); // Output Map -``` -> Midway has specially customized the `Array`, `Set`, and `Map` types that winston cannot output to enable them to output normally. - - -However, it should be noted that under normal circumstances, the log object can only pass in one parameter, and its second parameter has other functions. -```typescript -logger.info('plain error message', 321); // 321 will be ignored -``` - - -### Error output - - -For the wrong object, Midway has also customized the winston so that it can be easily combined with ordinary text for output. -```typescript -// Output error object -logger.error(new Error('error instance')); - -// Output custom error object -const error = new Error('named error instance'); -error.name = 'NamedError'; -logger.error(error); - -// Text before, plus error instance -logger.info('text before error', new Error('error instance after text')); -``` -:::caution -Note that the error object can only be placed at the end, and there is only one, and all parameters after it will be ignored. -::: - - - - -### Format content -The format method based on `util.format`. -```typescript -logger.info('%s %d', 'aaa', 222); -``` -Commonly used are - - -- The `%s` string is occupied. -- `%d` digit occupancy -- `%j` json placeholder - -For more information, see the [util.format](https://nodejs.org/dist/latest-v14.x/docs/api/util.html#util_util_format_format_args) method of Node. js. - - - -### Output custom objects or complex types - - -Based on performance considerations, Midway(winston) only outputs basic types most of the time, so when the output parameter is an advanced object, the user **needs to manually convert it to a string** that needs to be printed. - - -The following example will not get the desired result. -```typescript -const obj = {a: 1}; -logger.info(obj); // By default, output [object Object] -``` -You need to manually output what you want to print. -```typescript -const obj = {a: 1}; -logger.info(JSON.stringify(obj)); // formatted text can be output -logger.info(obj.a); // Direct output attribute value -logger.info('%j', a); // Direct placeholder output entire json -``` - - - -### Pure output content - - -In special scenarios, we need to simply output content, and do not want to output timestamps, labels and other format-related information. For this requirement, we can use the `write` method. - -The `write` method is a very low-level method, and no matter what level of logs are written to the file. - - -Although the `write` method is available on every logger, we only provide it in the `IMidwayLogger` definition, and we hope you can clearly know that you want to call it. -```typescript -(logger as IMidwayLogger).write('hello world'); // There will only be hello world in the file -``` - - - -## Log type definition - - -By default, users should use the simplest `ILogger` definition. -```typescript -import { Provide, Logger } from '@midwayjs/core'; -import { ILogger } from '@midwayjs/logger'; - -@Provide() -export class UserService { - - @Inject() - logger: ILogger; // Get context log - - async getUser() { - this.logger.info('hello user'); - } - -} -``` - - -The `ILogger` definition provides only the simplest `debug`, `info`, `WARN`, and `error` methods. - - -In some scenarios, we need more complex definitions, such as modifying log attributes or dynamically adjusting. At this time, we need to use more complex `IMidwayLogger` definitions. - - -```typescript -import { Provide, Logger } from '@midwayjs/core'; -import { IMidwayLogger } from '@midwayjs/logger'; - -@Provide() -export class UserService { - - @Inject() - logger: IMidwayLogger; // Get context log - - async getUser() { - This. Logger. disableConsole(); // Prohibit console output - this.logger.info('hello user'); // This sentence is not visible in the console - This. Logger. enableConsole(); // Turn on console output - this.logger.info('hello user'); // This sentence can be seen in the console - } - -} -``` -The definition of the `IMidwayLogger` can refer to the description in the interface or view the [code](https://github.com/midwayjs/logger/blob/main/src/interface.ts). - - - -## Basic log configuration - -We can configure various behaviors of the log in the configuration file. - -The log configuration in Midway includes **Global Configuration** and **Single Log Configuration**. The two configurations are merged and overwritten. - -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; - -export default { - midwayLogger: { - default: { - // ... - }, - clients: { - coreLogger: { - // ... - }, - appLogger: { - // ... - } - } - }, -} as MidwayConfig; -``` - -As mentioned above, each object in the `clients` configuration segment is an independent log configuration item, and its configuration will be merged with the `default` segment to create a logger instance. - - - -## Configure log level - - -The winston log levels are divided into the following categories, and the log levels decrease in turn (the larger the number, the lower the level): -```typescript -const levels = { - none: 0 - error: 1 - trace: 2 - warn: 3 - info: 4 - verbose: 5 - debug: 6 - silly: 7 - all: 8 -} -``` -In order to simplify the Midway, we usually use only four levels: `error`, `war`, `info`, and `debug`. - -The log level represents the lowest level that can currently output logs. For example, if your log level is set to `WARN`, only logs of the `WARN` and higher `error` level can be output. - -In Midway, different log levels can be configured for different output behaviors. - -- `Level` Log Level of Text Written -- `consoleLevel` the log level output from the console - - - -### The default level of the framework - - -In Midway, it has its own default log level. - - -- In the development environment (local,test,unittest), the text and console log levels are unified to `info`. -- In the server environment (except the development environment), in order to reduce the number of logs, the log level of `coreLogger` is `warn`, while other logs are `info`. - - - -### Adjust log level - -In general, we do not recommend adjusting the global default log level, but adjust the log level of a specific logger, for example: - -Adjust `coreLogger` or `appLogger`. - -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; - -export default { - midwayLogger: { - clients: { - coreLogger: { - level: 'warn', - consoleLevel: 'warn' - // ... - }, - appLogger: { - level: 'warn', - consoleLevel: 'warn' - // ... - } - } - }, -} as MidwayConfig; -``` - -In special scenarios, you can also temporarily adjust the global log level. - -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; - -export default { - midwayLogger: { - default: { - level: 'info', - consoleLevel: 'warn' - }, - // ... - }, -} as MidwayConfig; -``` - - - -## Configure the log root directory - -By default, Midway outputs logs to the **root directory** during local development and server deployment. - - -- The root directory of the local log is `${app.appDir}/logs/project name`. -- The log root directory of the server is under the user directory `${process.env.HOME}/logs/project_name` (Linux/Mac) and `${process.env.USERPROFILE}/logs/project_name` (Windows), for example `/home/admin/logs/example-app`. - -We can configure the root directory where the log is located. - -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; - -export default { - midwayLogger: { - default: { - dir: '/home/admin/logs', - }, - // ... - }, -} as MidwayConfig; -``` - - - -## Configure log cutting (rotation) - - -By default, the same log object **generates two files**. - -Take `midway-core.log` as an example. When the application is started, a `midway-core with the timestamp of the day is generated. YYYY-MM files in-DD` format and a soft chain file of `midway-core.log` without timestamp. - -> Soft chain will not be generated under windows - - -To facilitate log collection and viewing, the soft chain file always points to the latest log file. - - -At `00:00` in the morning, a new file of the form `midway-core.log.YYYY-MM-DD` is generated at the end of the day's log. - -At the same time, when a single log file exceeds 200M, it will be automatically cut to generate a new log file. - -You can adjust the cutting behavior by configuration. - -```typescript -export default { - midwayLogger: { - default: { - maxSize: '100m', - }, - // ... - }, -} as MidwayConfig; -``` - - - -## Configure log cleanup - -By default, the log will exist for 31 days. - -This behavior can be adjusted by configuration, such as saving for 3 days instead. - -```typescript -} as MidwayConfig;export default { - midwayLogger: { - default: { - maxFiles: '3d', - }, - // ... - }, -} as MidwayConfig; -``` - - - - - - -## Advanced configuration - -If you are not satisfied with the default log object, you can create and modify it yourself. - - - -### Add custom log - -It can be configured as follows: - -```typescript -export default { - midwayLogger: { - clients: { - abcLogger: { - fileLogName: 'abc.log' - // ... - } - } - // ... - }, -} as MidwayConfig; -``` - -You can call `@Logger('abcLogger')` to obtain custom logs. - -For more log options, please refer to the [LoggerOptions description](https://github.com/midwayjs/logger/blob/main/src/interface.ts) in the interface. - - - -### Configure log output format - - -The display format refers to the string structure of a single line of text when the log is output. Midway has customized Winston logs and provided some default objects. - -For each logger object, you can configure an output format. The display format is a method that returns a string structure with the [info object](https://github.com/winstonjs/logform#info-objects) parameter of the Winston. - -```typescript -export default { - midwayLogger: { - clients: { - appLogger: { - format: info => { - return `${info.timestamp} ${info.LEVEL} ${info.pid} ${info.labelText}${info.message}`; - } - // ... - }, - customOtherLogger: { - format: info => { - return 'xxxx'; - } - } - } - // ... - }, -} as MidwayConfig; -``` - -The default properties of the info object are as follows: - -| **Attribute Name** | **Description** | **Example** | -| ----------- | ------------------------------------------------ | ------------------------------------------------------------ | -| timestamp | The timestamp. Default value: `'YYYY-MM-DD HH:mm:ss,SSS`. | 2020-12-30 07:50:10,453 | -| level | Lowercase log level | info | -| LEVEL | Uppercase log level | INFO | -| pid | current process pid | 3847 | -| labelText | Aggregate text for labels | [abcde] | -| message | Combination of normal messages + error messages + error stacks | 1. plain text, such as `123456`, `hello world`
2, error text (error name + stack) error: another test error at object. anonymous (/home/runner/work/midway/packages/logger/test/index.test.ts:224:18)
3, plain text + error text hello world error: another test error at object. anonymous (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18) | -| stack | Error stack | | -| originError | Original error object | The error instance itself | -| originArgs | Original user input parameters | ['a', 'B', 'c'] | - - - -### Get a custom context log - -Context logs are typed based on **raw log objects**. All formats of the original logs are reused. The relationship between them is as follows. - -```typescript -// Pseudocode -const contextLogger = customLogger.createContextLogger(ctx); -``` - -`@Inject` can only inject the default context logs. You can use the `ctx.getLogger` method to obtain the **context logs** corresponding to other **custom logs**. the context log is associated with ctx, and the same key in the same context will obtain the same log object. when ctx is destroyed, the log object will also be recycled. - -```typescript -import { Provide } from '@midwayjs/core'; -import { IMidwayLogger } from '@midwayjs/logger'; -import { Context } from '@midwayjs/koa'; - -@Provide() -export class UserService { - - @Inject() - ctx: Context; - - async getUser() { - // The context log object corresponding to the customLogger is obtained here. - const customLogger = this.ctx.getLogger('customLogger'); - customLogger.info('hello world'); - } - -} -``` - - - - -### Configure the context log output format - -Context logs are typed based on the **original log object**. All formats of the original log are reused. However, you can configure the corresponding context log format of the log object separately. - -There are more ctx objects in the info object of the context log. Let's take the context log of the `customLogger` as an example. - -```typescript -export default { - midwayLogger: { - clients: { - customLogger: { - contextFormat: info => { - const ctx = info.ctx; - return `${info.timestamp} ${info.LEVEL} ${info.pid} [${Date.now() - ctx.startTime}ms ${ctx.method}] ${info.message}`; - } - // ... - } - } - // ... - }, -} as MidwayConfig; -``` - -Then when you use the context log output, it will default to your format. - -```typescript -ctx.getLogger('customLogger').info('hello world'); -// 2021-01-28 11:10:19,334 INFO 9223 [2ms POST] hello world -``` - -Note that because `App Logger` is the default log object for all frameworks, it is relatively special. Some existing frameworks have their context formats configured by default, resulting in invalid configuration in `midwayLogger` fields. - -For this, you need to modify the context log format configuration of a framework separately, please jump to a different framework to view. - -- [Modify the koa context log format](./extensions/koa# Modify Context Log) -- [Modify the context log format of the egg](./extensions/egg# Modify Context Log) -- [Modify express context log format](./extensions/express# Modify Context Log) - - - -### Log default Transport - -Each log contains several default Transport. - -| Name | Default behavior | Description | -| ----------------- | -------- | ------------------------------ | -| Console Transport | Open | For output to console | -| File Transport | Open | For output to a text file | -| Error Transport | Open | Used to output errors to specific error logs | -| JSON Transport | Close | Text used to output JSON format | - -It can be modified through configuration. - -**Example: Only enable console output** - -```typescript -export default { - midwayLogger: { - clients: { - abcLogger: { - enableFile: false - enableError: false - // ... - } - } - // ... - }, -} as MidwayConfig; -``` - -**Example: Disable Console Output** - -```typescript -export default { - midwayLogger: { - clients: { - abcLogger: { - enableConsole: false - // ... - } - } - // ... - }, -} as MidwayConfig; -``` - -**Example: Enable text and JSON synchronization and disable error output** - -```typescript -export default { - midwayLogger: { - clients: { - abcLogger: { - enableConsole: false - enableFile: true - enableError: false - enableJSON: true - // ... - } - } - // ... - }, -} as MidwayConfig; -``` - - - -### Custom Transport - -The framework provides extended Transport functions, for example, you can write a Transport to transfer logs and upload them to other log libraries. - -For example, in the following example, we will transfer the log to another local file. - -```typescript -import { EmptyTransport } from '@midwayjs/logger'; - -class CustomTransport extends EmptyTransport { - log(info, callback) { - const levelLowerCase = info.level; - if (levelLowerCase === 'error' || levelLowerCase === 'warn') { - writeFileSync(join(logsDir, 'test.log'), info.message); - } - callback(); - } -} -``` - -We can initialize, add it to logger, or set level for Transport separately. - -```typescript -const customTransport = new CustomTransport({ - level: 'warn', -}); - -logger.add(customTransport); -``` - -In this way, the original logger will automatically execute the Transport when printing logs. - -All Transport are attached to the original logger instance (not context logger). If ctx data is required, it can be obtained from info. Note that it is empty. - - -```typescript -class CustomTransport extends EmptyTransport { - log(info, callback) { - if (info.ctx) { - // ... - } else { - // ... - } - callback(); - } -} -``` - - -We can also use dependency injection to define Transport. - -```typescript -import { EmptyTransport, IMidwayLogger } from '@midwayjs/logger'; -import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import { MidwayLoggerService } from '@midwayjs/core'; - -@Provide() -@Scope(ScopeEnum) -export class CustomTransport extends EmptyTransport { - log(info, callback) { - // ... - callback(); - } -} - -// src/configuration.ts -@Configuration(/*...*/) -export class MainConfiguration { - - @Inject() - loggerService: MidwayLoggerService; - - @Inject() - customTransport: CustomTransport; - - async onReady() { - const appLogger = this.loggerService.getLogger('customLogger') as IMidwayLogger; - appLogger.add(this.customTransport); - } -} -``` - - - -### Lazy initialization - -The log can be initialized lazily using the `lazyLoad` configuration. - -for example: - -```typescript -export default { - midwayLogger: { - clients: { - customLoggerA: { - level: 'DEBUG', - }, - customLoggerB: { - lazyLoad: true, - }, - } - //... - }, -} as MidwayConfig; -``` - -`customLoggerA` will be initialized immediately when the framework starts, and `customLoggerB` will be initialized when the business actually uses `getLogger` or `@Logger` injection for the first time. - -This feature is very suitable for dynamically creating logs, but configurations want to be merged together. - - - -## Frequently Asked Questions - - - -### 1. The server environment log is not output - -For the server environment, the default log level is warn, that is, `logger.warn` will print out. please check the "log level" section. - -We do not recommend printing too many logs in the server environment, only printing the necessary content, too much log output affects performance, but also affects the rapid positioning problem. - - - -### 2. The server does not have a console log - -Generally speaking, the server console log (console) is closed and will only be output to the file. If there are special requirements, it can be adjusted separately. - diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/mongodb.md b/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/mongodb.md index d0e64e3ed980..da2f3518550d 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/mongodb.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/mongodb.md @@ -1,5 +1,10 @@ # MongoDB + +:::tip +This document is obsolete from v3.4.0. +::: + In this chapter, we choose [Typegoose](https://github.com/typegoose/typegoose) as the MongoDB ORM library based on it. As he described, "Define Mongoose models using TypeScript classes" is very well combined with TypeScript. Simply put, Typegoose using TypeScript "wrappers" to write Mongoose models, most of its capabilities are provided by [mongoose](https://www.npmjs.com/package/mongoose) libraries. @@ -15,9 +20,6 @@ Related information: | Can be used for integration | ✅ | -:::tip -This document is obsolete from v3.4.0. -::: ## Mongoose version dependency diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/orm.md b/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/orm.md index 1d0cb50dc22a..5230969fc10a 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/orm.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/legacy/orm.md @@ -1,11 +1,12 @@ # TypeORM -[TypeORM](https://github.com/typeorm/typeorm) is the most mature object relation mapper (`ORM`) in the existing community of `node.js`. Midway and TypeORM match to make development easier. - :::tip This document is obsolete from v3.4.0. ::: +[TypeORM](https://github.com/typeorm/typeorm) is the most mature object relation mapper (`ORM`) in the existing community of `node.js`. Midway and TypeORM match to make development easier. + + Related information: | Description | | diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/logger.md b/site/i18n/en/docusaurus-plugin-content-docs/current/logger.md index 8f7019a1b1ce..44c9e0e65755 100644 --- a/site/i18n/en/docusaurus-plugin-content-docs/current/logger.md +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/logger.md @@ -1,111 +1,86 @@ # Logger -Midway provides a unified log access method for different scenarios. Through the `@midwayjs/logger` package export method, you can easily access the logging system in different scenarios. - -The functions implemented are: - -- Log classification -- Automatic cutting by size and time -- Custom output format -- Unified error log - :::tip -The current version of the log SDK documentation is 3.0. If you need version 2.0, please check [this document](/docs/legacy/logger). +This document is for `@midwayjs/logger` v2.0 version. ::: +Midway provides a unified log access method for different scenarios. The `@midwayjs/logger` package export method allows you to easily access log systems in different scenarios. +Midway's log system is based on the [winston](https://github.com/winstonjs/winston) of the community and is now a very popular log library in the community. -## Upgrade from 2.0 to 3.0 - -Starting from midway v3.13.0, the 3.0 version of `@midwayjs/logger` is supported. - -Upgrade the dependency versions in `package.json`, pay attention to the `dependencies` dependencies. - -```diff -{ - "dependencies": { -- "@midwayjs/logger": "2.0.0", -+ "@midwayjs/logger": "^3.0.0" - } -} -``` - -If there is no type hint for midwayLogger in the configuration, you need to add a reference to the log library in `src/interface.ts`. - -```diff -// src/interface.ts -+ import type {} from '@midwayjs/logger'; -``` +The functions realized are: -In most scenarios, the two versions are compatible, but since it is a major version upgrade, there will definitely be some differences. For the complete Breaking Change, please view the [Change Document](https://github.com/midwayjs /logger/blob/main/BREAKING-3.md). +- Log classification +- Automatic cutting by size and time +- Custom output format +- Unified error log -## Logger path and file +## Loger path and file -Midway will create some default files in the log root directory. +Midway creates some default files in the log root directory. -- `midway-core.log` is the log of information printed by the framework and components, corresponding to `coreLogger`. -- `midway-app.log` is the log of application printing information, corresponding to `appLogger`. In `@midawyjs/web`, the file is `midway-web.log` -- `common-error.log` All error logs (all logs created by Midway will repeatedly print errors to this file) +- `midway-core.log` logs of printed information of the framework and components, corresponding to the `coreLogger`. +- `midway-app.log` applies the log of printing information, corresponding to the `appLogger` +- `common-error.log` The log of all errors (all logs created by Midway will repeatedly print errors to this file) -The **log path** and **log level** are different between local development and server deployment. For details, please refer to [Configuration log root directory](#Configuration log root directory) and [Framework’s default level](#Framework’s default grade). +The **Log Path** and **Log Level** of local development and server deployment are different. For more information, see [Configure Log Root](# Configure the log root directory) and [Default Level](# The default level of the framework). -##Default log object +## Default loger object Midway provides three different logs in the framework by default, corresponding to three different behaviors. -| Log | Definition | Description | Common Usage | -| ---------------------------------------------- | ------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| coreLogger | Framework, component level logs | Console logs and text logs `midway-core.log` will be output by default, and error logs will be sent to `common-error.log` by default. | Errors in frameworks and components are generally printed to it. | -| appLogger | Business-level logs | Console logs and text logs `midway-app.log` will be output by default, and error logs will be sent to `common-error.log` by default, in `@midawyjs/web`, The file is `midway-web.log`. | Log used by business, generally business logs will be printed into it. | -| Context logger (reuse appLogger configuration) | Request link log | By default, `appLogger` is used for output. In addition to sending the error log to `common-error.log`, context information is also added. | Different protocols have different request log formats. For example, routing information will be output under HTTP. | +| Log | Interpretation | Description | Common use | +| ----------------------------------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| coreLogger | Framework, component-level logs | By default, the console log and text log `midway-core.log` are output, and the error log is sent to `common-error.log` by default. | Frames and component errors are generally printed into them. | +| appLogger | Logs at the business level | The `midway-app.log` of the console log and text log is output by default, and the error log is sent to `common-error.log` by default. | The log used by the business. Generally, the business log will be printed in it. | +| Context Logger (Configuration of Multiplexing appLogger) | Log of request link | By default, `appLogger` is used for output. In addition to sending error logs to `common-error.log`, context information is added. | Modify the label (Label) of log output. Different frameworks have different request labels. For example, under HTTP, routing information will be output. | -## Usage logger +## Use log -Common log usage methods for Midway. +Midway's common log usage method. -### Context logger +### Context log -The context log is a log associated with the framework context object (Context). +The context log is the log associated with the framework context object (Context). -We can use the `ctx.logger` object to print logs after [obtaining the ctx object](./req_res_app). +You can [obtain the ctx object](./req_res_app) and then use the `ctx.logger` object to print and output logs. -for example: +For example: ```typescript ctx.logger.info("hello world"); ctx.logger.debug('debug info'); ctx.logger.warn('WARNNING!!!!'); -// Error logging will directly record the complete stack information of the error log and output it to errorLog. -// In order to ensure that exceptions are traceable, it must be ensured that all thrown exceptions are of type Error, because only type Error will bring stack information and locate the problem. +// Error log recording will directly record the complete stack information of the error log and output it to the errorLog +// In order to ensure that exceptions can be traced, all exceptions thrown must be of Error type, because only Error type will bring stack information to locate the problem. ctx.logger.error(new Error('custom error')); ``` -After execution, we can see log output in two places: +After execution, we can see the log output in two places: - The console sees the output. -- midway-app.log file in the log directory +- In the midway-app.log file of the log directory Output result: - ```text 2021-07-22 14:50:59,388 INFO 7739 [-/::ffff:127.0.0.1/-/0ms GET /api/get_user] hello world ``` -In the form of injection, we can also directly use the form of `@Inject() logger` to inject `ctx.logger`, which is equivalent to directly calling `ctx.logger`. +In the injection form, you can also use `@Inject() logger` to inject `ctx.logger`, which is equivalent to calling `ctx.logger` directly. -for example: +For example: ```typescript import { Get, Inject, Controller, Provide } from '@midwayjs/core'; @@ -114,18 +89,18 @@ import { ILogger } from '@midwayjs/logger'; @Controller() export class HelloController { - @Inject() - logger: ILogger; + @Inject() + logger: ILogger; - @Inject() - ctx; + @Inject() + ctx; - @Get("/") - async hello(){ - // ... + @Get("/") + async hello() { + // ... - // this.logger === ctx.logger - } + // this.logger === ctx.logger + } } ``` @@ -142,16 +117,16 @@ import { ILogger } from '@midwayjs/logger'; @Configuration() export class MainConfiguration implements ILifeCycle { - @Logger() - logger: ILogger; + @Logger() + logger: ILogger; - async onReady(container: IMidwayContainer): Promise { - this.logger.debug('debug info'); - this.logger.info('Startup took %d ms', Date.now() - start); - this.logger.warn('warning!'); + async onReady(container: IMidwayContainer): Promise { + this.logger.debug('debug info'); + This.logger.info ('startup takes% d ms', Date.now() - start); + this.logger.warn('warning!'); - this.logger.error(someErrorObj); - } + this.logger.error(someErrorObj); + } } ``` @@ -162,22 +137,23 @@ Note that the `@Logger()` decorator is used here. ### CoreLogger -In component or framework level development, we will use coreLogger to record logs. +In research and development at the component or framework level, we will use coreLogger to log. ```typescript + @Configuration() export class MainConfiguration implements ILifeCycle { - @Logger('coreLogger') - logger: ILogger; + @Logger('coreLogger') + logger: ILogger; - async onReady(container: IMidwayContainer): Promise { - this.logger.debug('debug info'); - this.logger.info('Startup took %d ms', Date.now() - start); - this.logger.warn('warning!'); + async onReady(container: IMidwayContainer): Promise { + this.logger.debug('debug info'); + This.logger.info ('startup takes% D MS', Date.now() -Start); + this.logger.warn('warning!'); - this.logger.error(someErrorObj); - } + this.logger.error(someErrorObj); + } } ``` @@ -185,73 +161,125 @@ export class MainConfiguration implements ILifeCycle { -## Output method and format -Midway's log object provides five methods: `error()`, `warn()`, `info()`, `debug()`, and `write()`. +## Output method and format + +The log object of Midway inherits the log object of the winston. In general, only four methods are provided: `error()`, `war ()`, `info()`, and `debug`. -Examples are as follows. +An example is as follows. ```typescript logger.debug('debug info'); -logger.info('Startup takes %d ms', Date.now() - start); +logger.info('startup takes% d ms', Date.now() - start); logger.warn('warning!'); logger.error(new Error('my error')); -logger.write('abcdef'); ``` -:::tip -The `write` method is used to output the user's original format log. +### Default output behavior + + +In most common types, the logstore works well. + +For example: +```typescript +logger.info('hello world'); // Output string +logger.info(123); //Output Number +logger.info(['B', 'c']); // Output array +logger.info(new Set([2, 3, 4])); // Output Set +logger.info(new Map([['key1', 'value1'], ['key2', 'value2']])); // Output Map +``` +> Midway has specially customized the `Array`, `Set`, and `Map` types that winston cannot output to enable them to output normally. + + +However, it should be noted that under normal circumstances, the log object can only pass in one parameter, and its second parameter has other functions. +```typescript +logger.info('plain error message', 321); // 321 will be ignored +``` + + +### Error output + + +For the wrong object, Midway has also customized the winston so that it can be easily combined with ordinary text for output. +```typescript +// Output error object +logger.error(new Error('error instance')); + +// Output custom error object +const error = new Error('named error instance'); +error.name = 'NamedError'; +logger.error(error); + +// Text before, plus error instance +logger.info('text before error', new Error('error instance after text')); +``` +:::caution +Note that the error object can only be placed at the end, and there is only one, and all parameters after it will be ignored. ::: -Formatting method based on `util.format`. +### Format content +The format method based on `util.format`. ```typescript logger.info('%s %d', 'aaa', 222); ``` - -Commonly used ones include +Commonly used are -- `%s` string placeholder -- `%d` digital placeholder +- The `%s` string is occupied. +- `%d` digit occupancy - `%j` json placeholder -For more placeholders and details, please refer to the [util.format](https://nodejs.org/dist/latest-v14.x/docs/api/util.html#util_util_format_format_args) method of node.js. +For more information, see the [util.format](https://nodejs.org/dist/latest-v14.x/docs/api/util.html#util_util_format_format_args) method of Node. js. -## Logger type definition +### Output custom objects or complex types -In most cases, users should use the simplest `ILogger` definition in `@midwayjs/core`. +Based on performance considerations, Midway(winston) only outputs basic types most of the time, so when the output parameter is an advanced object, the user **needs to manually convert it to a string** that needs to be printed. + +The following example will not get the desired result. +```typescript +const obj = {a: 1}; +logger.info(obj); // By default, output [object Object] +``` +You need to manually output what you want to print. ```typescript -import { Provide, Logger, ILogger } from '@midwayjs/core'; +const obj = {a: 1}; +logger.info(JSON.stringify(obj)); // formatted text can be output +logger.info(obj.a); // Direct output attribute value +logger.info('%j', a); // Direct placeholder output entire json +``` -@Provide() -export class UserService { - @Inject() - logger: ILogger; - async getUser() { - this.logger.info('hello user'); - } -} +### Pure output content + + +In special scenarios, we need to simply output content, and do not want to output timestamps, labels and other format-related information. For this requirement, we can use the `write` method. + +The `write` method is a very low-level method, and no matter what level of logs are written to the file. + + +Although the `write` method is available on every logger, we only provide it in the `IMidwayLogger` definition, and we hope you can clearly know that you want to call it. +```typescript +(logger as IMidwayLogger).write('hello world'); // There will only be hello world in the file ``` -The `ILogger` definition only provides the simplest `debug`, `info`, `warn` and `error` methods. -In some scenarios, we need more complex definitions. In this case, we need to use the `ILogger` definition provided by `@midwayjs/logger`. +## Log type definition +By default, users should use the simplest `ILogger` definition. ```typescript import { Provide, Logger } from '@midwayjs/core'; import { ILogger } from '@midwayjs/logger'; @@ -259,158 +287,119 @@ import { ILogger } from '@midwayjs/logger'; @Provide() export class UserService { - @Inject() - logger: ILogger; + @Inject() + logger: ILogger; // Get context log - async getUser() { - // ... - } + async getUser() { + this.logger.info('hello user'); + } } ``` -`ILogger`The definition can refer to the description in interface, or view [code](https://github.com/midwayjs/logger/blob/main/src/interface.ts). - +The `ILogger` definition provides only the simplest `debug`, `info`, `WARN`, and `error` methods. -## Logger configuration +In some scenarios, we need more complex definitions, such as modifying log attributes or dynamically adjusting. At this time, we need to use more complex `IMidwayLogger` definitions. -### Basic configuration structure +```typescript +import { Provide, Logger } from '@midwayjs/core'; +import { IMidwayLogger } from '@midwayjs/logger'; -We can configure various log behaviors in the configuration file. +@Provide() +export class UserService { -The log configuration in Midway includes two parts: **global configuration** and **individual log configuration**. The two configurations will be merged and overwritten. + @Inject() + logger: IMidwayLogger; // Get context log -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; + async getUser() { + This. Logger. disableConsole(); // Prohibit console output + this.logger.info('hello user'); // This sentence is not visible in the console + This. Logger. enableConsole(); // Turn on console output + this.logger.info('hello user'); // This sentence can be seen in the console + } -export default { - midwayLogger: { - default: { - // ... - }, - clients: { - coreLogger: { - // ... - }, - appLogger: { - // ... - } - } - }, -} as MidwayConfig; +} ``` +The definition of the `IMidwayLogger` can refer to the description in the interface or view the [code](https://github.com/midwayjs/logger/blob/main/src/interface.ts). -As mentioned above, each object in the `clients` configuration section is an independent log configuration item, and its configuration will be merged with the `default` section to create a logger instance. +## Basic log configuration -### Default Transport +We can configure various behaviors of the log in the configuration file. -In Midway, three Transports `console`, `file`, and `error` are enabled by default. More information can be modified through configuration. +The log configuration in Midway includes **Global Configuration** and **Single Log Configuration**. The two configurations are merged and overwritten. ```typescript // src/config/config.default.ts import { MidwayConfig } from '@midwayjs/core'; export default { - midwayLogger: { - default: { - transports: { - console: { - // console transport configuration - }, - file: { - // file transport configuration - }, - error: { - // error transport configuration - }, - } - }, - // ... - }, + midwayLogger: { + default: { + // ... + }, + clients: { + coreLogger: { + // ... + }, + appLogger: { + // ... + } + } + }, } as MidwayConfig; ``` -If a transport is not required, it can be set to `false`. +As mentioned above, each object in the `clients` configuration segment is an independent log configuration item, and its configuration will be merged with the `default` segment to create a logger instance. -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; - -export default { - midwayLogger: { - default: { - transports: { - console: false, - } - }, - // ... - }, -} as MidwayConfig; -``` +## Configure log level -### Configure log level -In Midway, under normal circumstances, we only use four levels: `error`, `warn`, `info`, and `debug`. - -The log level indicates the lowest level that can currently output logs. For example, when your log level is set to `warn`, only `warn` and higher `error` level logs can be output. +The winston log levels are divided into the following categories, and the log levels decrease in turn (the larger the number, the lower the level): +```typescript +const levels = { + none: 0 + error: 1 + trace: 2 + warn: 3 + info: 4 + verbose: 5 + debug: 6 + silly: 7 + all: 8 +} +``` +In order to simplify the Midway, we usually use only four levels: `error`, `war`, `info`, and `debug`. +The log level represents the lowest level that can currently output logs. For example, if your log level is set to `WARN`, only logs of the `WARN` and higher `error` level can be output. -Midway has its own default log level. +In Midway, different log levels can be configured for different output behaviors. +- `Level` Log Level of Text Written +- `consoleLevel` the log level output from the console -- In the development environment (local, test, unittest), the text and console log levels are unified to `info`. -- In a server environment, in order to reduce the number of logs, the log level of `coreLogger` is `warn`, while other logs are `info`. -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; -export default { - midwayLogger: { - default: { - level: 'info', - }, - // ... - }, -} as MidwayConfig; -``` +### The default level of the framework +In Midway, it has its own default log level. -The level of the logger and the level of the Transport can be set separately. The level of the Transport has a higher priority than the level of the logger. -```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; +- In the development environment (local,test,unittest), the text and console log levels are unified to `info`. +- In the server environment (except the development environment), in order to reduce the number of logs, the log level of `coreLogger` is `warn`, while other logs are `info`. -export default { - midwayLogger: { - default: { - // level of logger - level: 'info', - transports: { - file: { - //level of file transport - level: 'warn' - } - } - }, - // ... - }, -} as MidwayConfig; -``` +### Adjust log level -We can also adjust the log level of a specific logger, such as: +In general, we do not recommend adjusting the global default log level, but adjust the log level of a specific logger, for example: Adjust `coreLogger` or `appLogger`. @@ -419,256 +408,223 @@ Adjust `coreLogger` or `appLogger`. import { MidwayConfig } from '@midwayjs/core'; export default { - midwayLogger: { - clients: { - coreLogger: { - level: 'warn', - // ... - }, - appLogger: { - level: 'warn', - // ... - } - } - }, + midwayLogger: { + clients: { + coreLogger: { + level: 'warn', + consoleLevel: 'warn' + // ... + }, + appLogger: { + level: 'warn', + consoleLevel: 'warn' + // ... + } + } + }, } as MidwayConfig; ``` -In special scenarios, the global log level can also be temporarily adjusted. +In special scenarios, you can also temporarily adjust the global log level. ```typescript // src/config/config.default.ts import { MidwayConfig } from '@midwayjs/core'; export default { - midwayLogger: { - default: { - level: 'info', - transports: { - console: { - level: 'warn' - } - } - }, - // ... - }, + midwayLogger: { + default: { + level: 'info', + consoleLevel: 'warn' + }, + // ... + }, } as MidwayConfig; ``` -### Configure log root directory +## Configure the log root directory -By default, Midway will output logs to the **log root** during local development and server deployment. +By default, Midway outputs logs to the **root directory** during local development and server deployment. -- The local log root directory is under the `${app.appDir}/logs/project name` directory -- The server's log root directory is under the user directory `${process.env.HOME}/logs/project name` (Linux/Mac) and `${process.env.USERPROFILE}/logs/project name` (Windows), For example `/home/admin/logs/example-app`. +- The root directory of the local log is `${app.appDir}/logs/project name`. +- The log root directory of the server is under the user directory `${process.env.HOME}/logs/project_name` (Linux/Mac) and `${process.env.USERPROFILE}/logs/project_name` (Windows), for example `/home/admin/logs/example-app`. -We can configure the root directory where the log is located. Note that all Transport paths must be modified. +We can configure the root directory where the log is located. ```typescript // src/config/config.default.ts import { MidwayConfig } from '@midwayjs/core'; export default { - midwayLogger: { - default: { - transports: { - file: { - dir: '/home/admin/logs', - }, - error: { - dir: '/home/admin/logs', - }, - } - }, - // ... - }, + midwayLogger: { + default: { + dir: '/home/admin/logs', + }, + // ... + }, } as MidwayConfig; ``` -### Configure log cutting (rotation) +## Configure log cutting (rotation) -Under the default behavior, the same log object **will generate two files**. +By default, the same log object **generates two files**. -Taking `midway-core.log` as an example, when the application starts, it will generate a file in the format of `midway-core.YYYY-MM-DD` with a timestamp of the day, and a `midway-core.log` without a timestamp. soft link file. +Take `midway-core.log` as an example. When the application is started, a `midway-core with the timestamp of the day is generated. YYYY-MM files in-DD` format and a soft chain file of `midway-core.log` without timestamp. -> Soft links will not be generated under windows +> Soft chain will not be generated under windows -To facilitate the configuration of log collection and viewing, the soft link file always points to the latest log file. +To facilitate log collection and viewing, the soft chain file always points to the latest log file. -When `00:00` is reached in the morning, a new file will be generated in the form of `midway-core.log.YYYY-MM-DD` ending with the current day's log. +At `00:00` in the morning, a new file of the form `midway-core.log.YYYY-MM-DD` is generated at the end of the day's log. -At the same time, when a single log file exceeds 200M, it will be automatically cut and a new log file will be generated. +At the same time, when a single log file exceeds 200M, it will be automatically cut to generate a new log file. -Cutting by size behavior can be adjusted through configuration. +You can adjust the cutting behavior by configuration. ```typescript export default { - midwayLogger: { - default: { - transports: { - file: { - maxSize: '100m', - }, - error: { - maxSize: '100m', - }, - } - }, - // ... - }, + midwayLogger: { + default: { + maxSize: '100m', + }, + // ... + }, } as MidwayConfig; ``` -### Configure log cleaning +## Configure log cleanup -By default, logs exist for 7 days. +By default, the log will exist for 31 days. -This behavior can be adjusted through configuration, such as saving for 3 days instead. +This behavior can be adjusted by configuration, such as saving for 3 days instead. ```typescript -export default { - midwayLogger: { - default: { - transports: { - file: { - maxFiles: '3d', - }, - error: { - maxFiles: '3d', - }, - } - }, - // ... - }, +} as MidwayConfig;export default { + midwayLogger: { + default: { + maxFiles: '3d', + }, + // ... + }, } as MidwayConfig; ``` -You can also configure a number to indicate the maximum number of log files to retain. -```typescript -export default { - midwayLogger: { - default: { - transports: { - file: { - maxFiles: '3', - }, - error: { - maxFiles: '3d', - }, - } - }, - // ... - }, -} as MidwayConfig; -``` -### Configure custom logs + + + +## Advanced configuration + +If you are not satisfied with the default log object, you can create and modify it yourself. + + + +### Add custom log It can be configured as follows: ```typescript export default { - midwayLogger: { - clients: { - abcLogger: { - fileLogName: 'abc.log' - // ... - } - } - // ... - }, + midwayLogger: { + clients: { + abcLogger: { + fileLogName: 'abc.log' + // ... + } + } + // ... + }, } as MidwayConfig; ``` -Customized logs can be obtained through `@Logger('abcLogger')`. +You can call `@Logger('abcLogger')` to obtain custom logs. -For more logging options, please refer to [LoggerOptions Description](https://github.com/midwayjs/logger/blob/main/src/interface.ts) in interface. +For more log options, please refer to the [LoggerOptions description](https://github.com/midwayjs/logger/blob/main/src/interface.ts) in the interface. ### Configure log output format -The display format refers to the string structure of a single line of text when outputting logs. Midway has customized Winston's log and provides some default objects. +The display format refers to the string structure of a single line of text when the log is output. Midway has customized Winston logs and provided some default objects. -Each logger object can be configured with an output format. The display format is a method that returns a string structure, and the parameter is Winston's [info object](https://github.com/winstonjs/logform#info-objects). +For each logger object, you can configure an output format. The display format is a method that returns a string structure with the [info object](https://github.com/winstonjs/logform#info-objects) parameter of the Winston. ```typescript export default { - midwayLogger: { - clients: { - appLogger: { - format: info => { - return `${info.timestamp} ${info.LEVEL} ${info.pid} ${info.labelText}${info.message}`; - } - // ... - }, - customOtherLogger: { - format: info => { - return 'xxxx'; - } - } - } - // ... - }, + midwayLogger: { + clients: { + appLogger: { + format: info => { + return `${info.timestamp} ${info.LEVEL} ${info.pid} ${info.labelText}${info.message}`; + } + // ... + }, + customOtherLogger: { + format: info => { + return 'xxxx'; + } + } + } + // ... + }, } as MidwayConfig; ``` The default properties of the info object are as follows: -| **Attribute name** | **Description** | **Example** | -| ------------------ | ------------------------------------------------------------ | ----------------------- | -| timestamp | Timestamp, default is `'YYYY-MM-DD HH:mm:ss,SSS` format. | 2020-12-30 07:50:10,453 | -| level | Lowercase log level | info | -| LEVEL | uppercase log level | INFO | -| pid | current process pid | 3847 | -| message | result of util.format | | -| args | Original user input parameters | [ 'a', 'b', 'c' ] | -| ctx | Context object associated when using ContextLogger | | -| originError | Original error object, obtained after traversing parameters, poor performance | error instance itself | -| originArgs | Same as args, only compatible with older versions | | - - +| **Attribute Name** | **Description** | **Example** | +| ----------- | ------------------------------------------------ | ------------------------------------------------------------ | +| timestamp | The timestamp. Default value: `'YYYY-MM-DD HH:mm:ss,SSS`. | 2020-12-30 07:50:10,453 | +| level | Lowercase log level | info | +| LEVEL | Uppercase log level | INFO | +| pid | current process pid | 3847 | +| labelText | Aggregate text for labels | [abcde] | +| message | Combination of normal messages + error messages + error stacks | 1. plain text, such as `123456`, `hello world`
2, error text (error name + stack) error: another test error at object. anonymous (/home/runner/work/midway/packages/logger/test/index.test.ts:224:18)
3, plain text + error text hello world error: another test error at object. anonymous (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18) | +| stack | Error stack | | +| originError | Original error object | The error instance itself | +| originArgs | Original user input parameters | ['a', 'B', 'c'] | -### Get custom context log +### Get a custom context log -Context log is logged based on **original log object** and will reuse all formats of the original log. Their relationship is as follows. +Context logs are typed based on **raw log objects**. All formats of the original logs are reused. The relationship between them is as follows. ```typescript -// pseudocode +// Pseudocode const contextLogger = customLogger.createContextLogger(ctx); ``` -`@Inject` can only inject the default context log. We can obtain the **context log** corresponding to other **custom log** through the `ctx.getLogger` method. The context log is associated with ctx. The same context and the same key will obtain the same log object. When ctx is destroyed, the log object will also be recycled. +`@Inject` can only inject the default context logs. You can use the `ctx.getLogger` method to obtain the **context logs** corresponding to other **custom logs**. the context log is associated with ctx, and the same key in the same context will obtain the same log object. when ctx is destroyed, the log object will also be recycled. ```typescript import { Provide } from '@midwayjs/core'; +import { IMidwayLogger } from '@midwayjs/logger'; import { Context } from '@midwayjs/koa'; @Provide() export class UserService { - @Inject() - ctx: Context; + @Inject() + ctx: Context; - async getUser() { - // What is obtained here is the context log object corresponding to customLogger - const customLogger = this.ctx.getLogger('customLogger'); - customLogger.info('hello world'); - } + async getUser() { + // The context log object corresponding to the customLogger is obtained here. + const customLogger = this.ctx.getLogger('customLogger'); + customLogger.info('hello world'); + } } ``` @@ -676,301 +632,238 @@ export class UserService { -### Configure context log output format +### Configure the context log output format -The context log is based on the **original log object** and will reuse all the formats of the original log, but we can separately configure the corresponding context log format of the log object. +Context logs are typed based on the **original log object**. All formats of the original log are reused. However, you can configure the corresponding context log format of the log object separately. -There is an additional ctx object in the info object of the context log. Let's take modifying the context log of `customLogger` as an example. +There are more ctx objects in the info object of the context log. Let's take the context log of the `customLogger` as an example. ```typescript export default { - midwayLogger: { - clients: { - customLogger: { - contextFormat: info => { - const ctx = info.ctx; - return `${info.timestamp} ${info.LEVEL} ${info.pid} [${Date.now() - ctx.startTime}ms ${ctx.method}] ${info.message}`; - } - // ... - } - } - // ... - }, + midwayLogger: { + clients: { + customLogger: { + contextFormat: info => { + const ctx = info.ctx; + return `${info.timestamp} ${info.LEVEL} ${info.pid} [${Date.now() - ctx.startTime}ms ${ctx.method}] ${info.message}`; + } + // ... + } + } + // ... + }, } as MidwayConfig; ``` -Then when you use context log output, it will become your format by default. +Then when you use the context log output, it will default to your format. ```typescript ctx.getLogger('customLogger').info('hello world'); // 2021-01-28 11:10:19,334 INFO 9223 [2ms POST] hello world ``` -Note that since `App Logger` is the default log object of all frameworks and is quite special, some existing frameworks configure its context format by default, causing the configuration in the `midwayLogger` field to be invalid. +Note that because `App Logger` is the default log object for all frameworks, it is relatively special. Some existing frameworks have their context formats configured by default, resulting in invalid configuration in `midwayLogger` fields. -To do this, you need to modify the context log format configuration of a certain framework separately. Please jump to a different framework to view it. +For this, you need to modify the context log format configuration of a framework separately, please jump to a different framework to view. -- [Modify koa's context log format](./extensions/koa#Modify context log) -- [Modify egg's context log format](./extensions/egg#Modify context log) -- [Modify the context log format of express](./extensions/express#Modify the context log) +- [Modify the koa context log format](./extensions/koa# Modify Context Log) +- [Modify the context log format of the egg](./extensions/egg# Modify Context Log) +- [Modify express context log format](./extensions/express# Modify Context Log) -### Configure delayed initialization +### Log default Transport -The log can be initialized lazily using the `lazyLoad` configuration. +Each log contains several default Transport. -for example: +| Name | Default behavior | Description | +| ----------------- | -------- | ------------------------------ | +| Console Transport | Open | For output to console | +| File Transport | Open | For output to a text file | +| Error Transport | Open | Used to output errors to specific error logs | +| JSON Transport | Close | Text used to output JSON format | + +It can be modified through configuration. + +**Example: Only enable console output** ```typescript export default { - midwayLogger: { - clients: { - customLoggerA: { - // .. - }, - customLoggerB: { - lazyLoad: true, - }, - } - // ... - }, + midwayLogger: { + clients: { + abcLogger: { + enableFile: false + enableError: false + // ... + } + } + // ... + }, } as MidwayConfig; ``` -`customLoggerA` will be initialized immediately when the framework starts, while `customLoggerB` will be initialized when the business actually uses `getLogger` or `@Logger` injection for the first time. - -This function is very suitable for scenarios where logs are dynamically created, but the configurations are expected to be merged together. - - - -### Configure associated logs - -The log object can be configured with an associated log object name. - -for example: +**Example: Disable Console Output** ```typescript export default { - midwayLogger: { - clients: { - customLoggerA: { - aliasName: 'customLoggerB', - // ... - }, - } - // ... - }, + midwayLogger: { + clients: { + abcLogger: { + enableConsole: false + // ... + } + } + // ... + }, } as MidwayConfig; ``` -When using the API to retrieve, the same log object will be retrieved with different names. - -```typescript -app.getLogger('customLoggerA') => customLoggerA -app.getLogger('customLoggerB') => customLoggerA -``` - - - -### Configure console output color - -When outputting to the console, if the command line supports color output, different colors will be output for different log levels. If color is not supported, it will not be displayed. - -You can turn off color output directly through configuration. +**Example: Enable text and JSON synchronization and disable error output** ```typescript export default { - midwayLogger: { - default: { - transports: { - console: { - autoColors: false, - } - } - } - // ... - }, + midwayLogger: { + clients: { + abcLogger: { + enableConsole: false + enableFile: true + enableError: false + enableJSON: true + // ... + } + } + // ... + }, } as MidwayConfig; ``` -## Custom Transport +### Custom Transport -The framework provides the function of extending Transport. For example, you can write a Transport to transfer logs and upload them to other log libraries. +The framework provides extended Transport functions, for example, you can write a Transport to transfer logs and upload them to other log libraries. - - -### Inherit existing Transport - -If writing to a new file, this can be achieved by using `FileTransport`. +For example, in the following example, we will transfer the log to another local file. ```typescript -import { FileTransport, isEnableLevel, LoggerLevel, LogMeta } from '@midwayjs/logger'; +import { EmptyTransport } from '@midwayjs/logger'; -// Transport configuration -interface CustomOptions { - // ... +class CustomTransport extends EmptyTransport { + log(info, callback) { + const levelLowerCase = info.level; + if (levelLowerCase === 'error' || levelLowerCase === 'warn') { + writeFileSync(join(logsDir, 'test.log'), info.message); + } + callback(); + } } - -class CustomTransport extends FileTransport { - log(level: LoggerLevel | false, meta: LogMeta, ...args) { - // Determine whether level satisfies the current Transport - if (!isEnableLevel(level, this.options.level)) { - return; - } - - // Format the message using built-in formatting methods - let buf = this.format(level, meta, args) as string; - //Add newline character - buf += this.options.eol; - - //Write the log you want to write - if (this.options.bufferWrite) { - this.bufSize += buf.length; - this.buf.push(buf); - if (this.buf.length > this.options.bufferMaxLength) { - this.flush(); - } - } else { - // If caching is not enabled, write directly - this.logStream.write(buf); - } - } -} -``` - -Before use, it needs to be registered in the log library. - -```typescript -import { TransportManager } from '@midwayjs/logger'; - -TransportManager.set('custom', CustomTransport); ``` -You can then use this Transport in your configuration. +We can initialize, add it to logger, or set level for Transport separately. ```typescript -// src/config/config.default.ts -import { MidwayConfig } from '@midwayjs/core'; +const customTransport = new CustomTransport({ + level: 'warn', +}); -export default { - midwayLogger: { - default: { - transports: { - custom: { - dir: 'xxxx', - fileLogName: 'xxx', - // ... - } - } - } - }, -} as MidwayConfig; +logger.add(customTransport); ``` In this way, the original logger will automatically execute the Transport when printing logs. +All Transport are attached to the original logger instance (not context logger). If ctx data is required, it can be obtained from info. Note that it is empty. -### Fully customized Transport - -In addition to writing files, logs can also be delivered to remote services. For example, in the following example, the logs are forwarded to another service. - -Note that Transport is an operation that can be executed asynchronously, but the logger itself will not wait for Transport to execute and return. - ```typescript -import { Transport, ITransport, LoggerLevel, LogMeta } from '@midwayjs/logger'; - - -// Transport configuration -interface CustomOptions { - // ... -} - -class CustomTransport extends Transport implements ITransport { - log(level: LoggerLevel | false, meta: LogMeta, ...args) { - // Format the message using built-in formatting methods - let msg = this.format(level, meta, args) as string; - - //Asynchronously write to the log library - remoteSdk.send(msg).catch(err => { - // Log the error or ignore it - console.error(err); - }); - } +class CustomTransport extends EmptyTransport { + log(info, callback) { + if (info.ctx) { + // ... + } else { + // ... + } + callback(); + } } ``` - -## Dynamic API - -Dynamically obtain the log object through the `getLogger` method. - -```typescript -// Get coreLogger -const coreLogger = app.getLogger('coreLogger'); -// Get the default contextLogger -const contextLogger = ctx.getLogger(); -// Get the contextLogger created by a specific logger, equivalent to customALogger.createContextLogger(ctx) -const customAContextLogger = ctx.getLogger('customA'); -``` - -The framework's built-in `MidwayLoggerService` also has the above API. +We can also use dependency injection to define Transport. ```typescript +import { EmptyTransport, IMidwayLogger } from '@midwayjs/logger'; +import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { MidwayLoggerService } from '@midwayjs/core'; -import { Context } from '@midwayjs/koa'; @Provide() +@Scope(ScopeEnum) +export class CustomTransport extends EmptyTransport { + log(info, callback) { + // ... + callback(); + } +} + +// src/configuration.ts +@Configuration(/*...*/) export class MainConfiguration { - - @Inject() - loggerService: MidwayLoggerService; - - @Inject() - ctx: Context; - - async getUser() { - // get custom logger - const customLogger = this.loggerService.getLogger('customLogger'); - - //Create context logger - const customContextLogger = this.loggerService.createContextLogger(this.ctx, customLogger); - } + + @Inject() + loggerService: MidwayLoggerService; + + @Inject() + customTransport: CustomTransport; + + async onReady() { + const appLogger = this.loggerService.getLogger('customLogger') as IMidwayLogger; + appLogger.add(this.customTransport); + } } ``` -## Common Problem +### Lazy initialization +The log can be initialized lazily using the `lazyLoad` configuration. +for example: -### 1. The server environment log is not output +```typescript +export default { + midwayLogger: { + clients: { + customLoggerA: { + level: 'DEBUG', + }, + customLoggerB: { + lazyLoad: true, + }, + } + //... + }, +} as MidwayConfig; +``` -We do not recommend printing too many logs in the server environment. Only print necessary content. Excessive log output affects performance and quickly locates problems. +`customLoggerA` will be initialized immediately when the framework starts, and `customLoggerB` will be initialized when the business actually uses `getLogger` or `@Logger` injection for the first time. -To adjust the log level, see the "Configuring Log Level" section. +This feature is very suitable for dynamically creating logs, but configurations want to be merged together. -### 2. The server has no console log +## Frequently Asked Questions -Generally speaking, the server console log (console) is closed and will only be output to a file. If there are special needs, it can be adjusted individually. +### 1. The server environment log is not output -### 3. Some Docker environments fail to start +For the server environment, the default log level is warn, that is, `logger.warn` will print out. please check the "log level" section. -Check whether the user who started the current application in the directory where the log is written has permissions. +We do not recommend printing too many logs in the server environment, only printing the necessary content, too much log output affects performance, but also affects the rapid positioning problem. -### 4. How to convert if there is an old configuration? +### 2. The server does not have a console log -The new version of the log library is already compatible with the old configuration. Generally, no additional processing is required. There is a priority relationship between the old configuration and the new configuration when merging. Please check the [Change Document](https://github.com/midwayjs/logger/blob/ main/BREAKING-3.md). +Generally speaking, the server console log (console) is closed and will only be output to the file. If there are special requirements, it can be adjusted separately. -In order to reduce troubleshooting problems, please use the new configuration format when using the new version of the log library. \ No newline at end of file diff --git a/site/i18n/en/docusaurus-plugin-content-docs/current/logger_v3.md b/site/i18n/en/docusaurus-plugin-content-docs/current/logger_v3.md new file mode 100644 index 000000000000..8f7019a1b1ce --- /dev/null +++ b/site/i18n/en/docusaurus-plugin-content-docs/current/logger_v3.md @@ -0,0 +1,976 @@ +# Logger + +Midway provides a unified log access method for different scenarios. Through the `@midwayjs/logger` package export method, you can easily access the logging system in different scenarios. + +The functions implemented are: + +- Log classification +- Automatic cutting by size and time +- Custom output format +- Unified error log + +:::tip + +The current version of the log SDK documentation is 3.0. If you need version 2.0, please check [this document](/docs/legacy/logger). + +::: + + + +## Upgrade from 2.0 to 3.0 + +Starting from midway v3.13.0, the 3.0 version of `@midwayjs/logger` is supported. + +Upgrade the dependency versions in `package.json`, pay attention to the `dependencies` dependencies. + +```diff +{ + "dependencies": { +- "@midwayjs/logger": "2.0.0", ++ "@midwayjs/logger": "^3.0.0" + } +} +``` + +If there is no type hint for midwayLogger in the configuration, you need to add a reference to the log library in `src/interface.ts`. + +```diff +// src/interface.ts ++ import type {} from '@midwayjs/logger'; +``` + +In most scenarios, the two versions are compatible, but since it is a major version upgrade, there will definitely be some differences. For the complete Breaking Change, please view the [Change Document](https://github.com/midwayjs /logger/blob/main/BREAKING-3.md). + + + +## Logger path and file + +Midway will create some default files in the log root directory. + + +- `midway-core.log` is the log of information printed by the framework and components, corresponding to `coreLogger`. +- `midway-app.log` is the log of application printing information, corresponding to `appLogger`. In `@midawyjs/web`, the file is `midway-web.log` +- `common-error.log` All error logs (all logs created by Midway will repeatedly print errors to this file) + +The **log path** and **log level** are different between local development and server deployment. For details, please refer to [Configuration log root directory](#Configuration log root directory) and [Framework’s default level](#Framework’s default grade). + + + +##Default log object + +Midway provides three different logs in the framework by default, corresponding to three different behaviors. + +| Log | Definition | Description | Common Usage | +| ---------------------------------------------- | ------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| coreLogger | Framework, component level logs | Console logs and text logs `midway-core.log` will be output by default, and error logs will be sent to `common-error.log` by default. | Errors in frameworks and components are generally printed to it. | +| appLogger | Business-level logs | Console logs and text logs `midway-app.log` will be output by default, and error logs will be sent to `common-error.log` by default, in `@midawyjs/web`, The file is `midway-web.log`. | Log used by business, generally business logs will be printed into it. | +| Context logger (reuse appLogger configuration) | Request link log | By default, `appLogger` is used for output. In addition to sending the error log to `common-error.log`, context information is also added. | Different protocols have different request log formats. For example, routing information will be output under HTTP. | + + + +## Usage logger + +Common log usage methods for Midway. + +### Context logger + +The context log is a log associated with the framework context object (Context). + +We can use the `ctx.logger` object to print logs after [obtaining the ctx object](./req_res_app). + +for example: + +```typescript +ctx.logger.info("hello world"); +ctx.logger.debug('debug info'); +ctx.logger.warn('WARNNING!!!!'); + +// Error logging will directly record the complete stack information of the error log and output it to errorLog. +// In order to ensure that exceptions are traceable, it must be ensured that all thrown exceptions are of type Error, because only type Error will bring stack information and locate the problem. +ctx.logger.error(new Error('custom error')); +``` + +After execution, we can see log output in two places: + + +- The console sees the output. +- midway-app.log file in the log directory + + +Output result: + +```text +2021-07-22 14:50:59,388 INFO 7739 [-/::ffff:127.0.0.1/-/0ms GET /api/get_user] hello world +``` + +In the form of injection, we can also directly use the form of `@Inject() logger` to inject `ctx.logger`, which is equivalent to directly calling `ctx.logger`. + +for example: + +```typescript +import { Get, Inject, Controller, Provide } from '@midwayjs/core'; +import { ILogger } from '@midwayjs/logger'; + +@Controller() +export class HelloController { + + @Inject() + logger: ILogger; + + @Inject() + ctx; + + @Get("/") + async hello(){ + // ... + + // this.logger === ctx.logger + } +} +``` + + + +### App Logger + +If we want to do some application-level logging, such as recording some data information during the startup phase, we can do it through App Logger. + +```typescript +import { Configuration, Logger } from '@midwayjs/core'; +import { ILogger } from '@midwayjs/logger'; + +@Configuration() +export class MainConfiguration implements ILifeCycle { + + @Logger() + logger: ILogger; + + async onReady(container: IMidwayContainer): Promise { + this.logger.debug('debug info'); + this.logger.info('Startup took %d ms', Date.now() - start); + this.logger.warn('warning!'); + + this.logger.error(someErrorObj); + } + +} +``` + +Note that the `@Logger()` decorator is used here. + + + +### CoreLogger + +In component or framework level development, we will use coreLogger to record logs. + +```typescript +@Configuration() +export class MainConfiguration implements ILifeCycle { + + @Logger('coreLogger') + logger: ILogger; + + async onReady(container: IMidwayContainer): Promise { + this.logger.debug('debug info'); + this.logger.info('Startup took %d ms', Date.now() - start); + this.logger.warn('warning!'); + + this.logger.error(someErrorObj); + } + +} +``` + + + + +## Output method and format + + +Midway's log object provides five methods: `error()`, `warn()`, `info()`, `debug()`, and `write()`. + + +Examples are as follows. + +```typescript +logger.debug('debug info'); +logger.info('Startup takes %d ms', Date.now() - start); +logger.warn('warning!'); +logger.error(new Error('my error')); +logger.write('abcdef'); +``` + +:::tip + +The `write` method is used to output the user's original format log. + +::: + + + +Formatting method based on `util.format`. + +```typescript +logger.info('%s %d', 'aaa', 222); +``` + +Commonly used ones include + + +- `%s` string placeholder +- `%d` digital placeholder +- `%j` json placeholder + +For more placeholders and details, please refer to the [util.format](https://nodejs.org/dist/latest-v14.x/docs/api/util.html#util_util_format_format_args) method of node.js. + + + +## Logger type definition + + +In most cases, users should use the simplest `ILogger` definition in `@midwayjs/core`. + +```typescript +import { Provide, Logger, ILogger } from '@midwayjs/core'; + +@Provide() +export class UserService { + + @Inject() + logger: ILogger; + + async getUser() { + this.logger.info('hello user'); + } +} +``` + +The `ILogger` definition only provides the simplest `debug`, `info`, `warn` and `error` methods. + + +In some scenarios, we need more complex definitions. In this case, we need to use the `ILogger` definition provided by `@midwayjs/logger`. + + +```typescript +import { Provide, Logger } from '@midwayjs/core'; +import { ILogger } from '@midwayjs/logger'; + +@Provide() +export class UserService { + + @Inject() + logger: ILogger; + + async getUser() { + // ... + } + +} +``` + +`ILogger`The definition can refer to the description in interface, or view [code](https://github.com/midwayjs/logger/blob/main/src/interface.ts). + + + +## Logger configuration + + + +### Basic configuration structure + +We can configure various log behaviors in the configuration file. + +The log configuration in Midway includes two parts: **global configuration** and **individual log configuration**. The two configurations will be merged and overwritten. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + // ... + }, + clients: { + coreLogger: { + // ... + }, + appLogger: { + // ... + } + } + }, +} as MidwayConfig; +``` + +As mentioned above, each object in the `clients` configuration section is an independent log configuration item, and its configuration will be merged with the `default` section to create a logger instance. + + + +### Default Transport + +In Midway, three Transports `console`, `file`, and `error` are enabled by default. More information can be modified through configuration. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + transports: { + console: { + // console transport configuration + }, + file: { + // file transport configuration + }, + error: { + // error transport configuration + }, + } + }, + // ... + }, +} as MidwayConfig; +``` + +If a transport is not required, it can be set to `false`. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + transports: { + console: false, + } + }, + // ... + }, +} as MidwayConfig; +``` + + + +### Configure log level + +In Midway, under normal circumstances, we only use four levels: `error`, `warn`, `info`, and `debug`. + +The log level indicates the lowest level that can currently output logs. For example, when your log level is set to `warn`, only `warn` and higher `error` level logs can be output. + + +Midway has its own default log level. + + +- In the development environment (local, test, unittest), the text and console log levels are unified to `info`. +- In a server environment, in order to reduce the number of logs, the log level of `coreLogger` is `warn`, while other logs are `info`. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + level: 'info', + }, + // ... + }, +} as MidwayConfig; +``` + + + +The level of the logger and the level of the Transport can be set separately. The level of the Transport has a higher priority than the level of the logger. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + // level of logger + level: 'info', + transports: { + file: { + //level of file transport + level: 'warn' + } + } + }, + // ... + }, +} as MidwayConfig; +``` + + + +We can also adjust the log level of a specific logger, such as: + +Adjust `coreLogger` or `appLogger`. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + clients: { + coreLogger: { + level: 'warn', + // ... + }, + appLogger: { + level: 'warn', + // ... + } + } + }, +} as MidwayConfig; +``` + +In special scenarios, the global log level can also be temporarily adjusted. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + level: 'info', + transports: { + console: { + level: 'warn' + } + } + }, + // ... + }, +} as MidwayConfig; +``` + + + +### Configure log root directory + +By default, Midway will output logs to the **log root** during local development and server deployment. + + +- The local log root directory is under the `${app.appDir}/logs/project name` directory +- The server's log root directory is under the user directory `${process.env.HOME}/logs/project name` (Linux/Mac) and `${process.env.USERPROFILE}/logs/project name` (Windows), For example `/home/admin/logs/example-app`. + +We can configure the root directory where the log is located. Note that all Transport paths must be modified. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + transports: { + file: { + dir: '/home/admin/logs', + }, + error: { + dir: '/home/admin/logs', + }, + } + }, + // ... + }, +} as MidwayConfig; +``` + + + +### Configure log cutting (rotation) + + +Under the default behavior, the same log object **will generate two files**. + +Taking `midway-core.log` as an example, when the application starts, it will generate a file in the format of `midway-core.YYYY-MM-DD` with a timestamp of the day, and a `midway-core.log` without a timestamp. soft link file. + +> Soft links will not be generated under windows + + +To facilitate the configuration of log collection and viewing, the soft link file always points to the latest log file. + + +When `00:00` is reached in the morning, a new file will be generated in the form of `midway-core.log.YYYY-MM-DD` ending with the current day's log. + +At the same time, when a single log file exceeds 200M, it will be automatically cut and a new log file will be generated. + +Cutting by size behavior can be adjusted through configuration. + +```typescript +export default { + midwayLogger: { + default: { + transports: { + file: { + maxSize: '100m', + }, + error: { + maxSize: '100m', + }, + } + }, + // ... + }, +} as MidwayConfig; +``` + + + +### Configure log cleaning + +By default, logs exist for 7 days. + +This behavior can be adjusted through configuration, such as saving for 3 days instead. + +```typescript +export default { + midwayLogger: { + default: { + transports: { + file: { + maxFiles: '3d', + }, + error: { + maxFiles: '3d', + }, + } + }, + // ... + }, +} as MidwayConfig; +``` + +You can also configure a number to indicate the maximum number of log files to retain. + +```typescript +export default { + midwayLogger: { + default: { + transports: { + file: { + maxFiles: '3', + }, + error: { + maxFiles: '3d', + }, + } + }, + // ... + }, +} as MidwayConfig; +``` + +### Configure custom logs + +It can be configured as follows: + +```typescript +export default { + midwayLogger: { + clients: { + abcLogger: { + fileLogName: 'abc.log' + // ... + } + } + // ... + }, +} as MidwayConfig; +``` + +Customized logs can be obtained through `@Logger('abcLogger')`. + +For more logging options, please refer to [LoggerOptions Description](https://github.com/midwayjs/logger/blob/main/src/interface.ts) in interface. + + + +### Configure log output format + + +The display format refers to the string structure of a single line of text when outputting logs. Midway has customized Winston's log and provides some default objects. + +Each logger object can be configured with an output format. The display format is a method that returns a string structure, and the parameter is Winston's [info object](https://github.com/winstonjs/logform#info-objects). + +```typescript +export default { + midwayLogger: { + clients: { + appLogger: { + format: info => { + return `${info.timestamp} ${info.LEVEL} ${info.pid} ${info.labelText}${info.message}`; + } + // ... + }, + customOtherLogger: { + format: info => { + return 'xxxx'; + } + } + } + // ... + }, +} as MidwayConfig; +``` + +The default properties of the info object are as follows: + +| **Attribute name** | **Description** | **Example** | +| ------------------ | ------------------------------------------------------------ | ----------------------- | +| timestamp | Timestamp, default is `'YYYY-MM-DD HH:mm:ss,SSS` format. | 2020-12-30 07:50:10,453 | +| level | Lowercase log level | info | +| LEVEL | uppercase log level | INFO | +| pid | current process pid | 3847 | +| message | result of util.format | | +| args | Original user input parameters | [ 'a', 'b', 'c' ] | +| ctx | Context object associated when using ContextLogger | | +| originError | Original error object, obtained after traversing parameters, poor performance | error instance itself | +| originArgs | Same as args, only compatible with older versions | | + + + + + +### Get custom context log + +Context log is logged based on **original log object** and will reuse all formats of the original log. Their relationship is as follows. + +```typescript +// pseudocode +const contextLogger = customLogger.createContextLogger(ctx); +``` + +`@Inject` can only inject the default context log. We can obtain the **context log** corresponding to other **custom log** through the `ctx.getLogger` method. The context log is associated with ctx. The same context and the same key will obtain the same log object. When ctx is destroyed, the log object will also be recycled. + +```typescript +import { Provide } from '@midwayjs/core'; +import { Context } from '@midwayjs/koa'; + +@Provide() +export class UserService { + + @Inject() + ctx: Context; + + async getUser() { + // What is obtained here is the context log object corresponding to customLogger + const customLogger = this.ctx.getLogger('customLogger'); + customLogger.info('hello world'); + } + +} +``` + + + + +### Configure context log output format + +The context log is based on the **original log object** and will reuse all the formats of the original log, but we can separately configure the corresponding context log format of the log object. + +There is an additional ctx object in the info object of the context log. Let's take modifying the context log of `customLogger` as an example. + +```typescript +export default { + midwayLogger: { + clients: { + customLogger: { + contextFormat: info => { + const ctx = info.ctx; + return `${info.timestamp} ${info.LEVEL} ${info.pid} [${Date.now() - ctx.startTime}ms ${ctx.method}] ${info.message}`; + } + // ... + } + } + // ... + }, +} as MidwayConfig; +``` + +Then when you use context log output, it will become your format by default. + +```typescript +ctx.getLogger('customLogger').info('hello world'); +// 2021-01-28 11:10:19,334 INFO 9223 [2ms POST] hello world +``` + +Note that since `App Logger` is the default log object of all frameworks and is quite special, some existing frameworks configure its context format by default, causing the configuration in the `midwayLogger` field to be invalid. + +To do this, you need to modify the context log format configuration of a certain framework separately. Please jump to a different framework to view it. + +- [Modify koa's context log format](./extensions/koa#Modify context log) +- [Modify egg's context log format](./extensions/egg#Modify context log) +- [Modify the context log format of express](./extensions/express#Modify the context log) + + + +### Configure delayed initialization + +The log can be initialized lazily using the `lazyLoad` configuration. + +for example: + +```typescript +export default { + midwayLogger: { + clients: { + customLoggerA: { + // .. + }, + customLoggerB: { + lazyLoad: true, + }, + } + // ... + }, +} as MidwayConfig; +``` + +`customLoggerA` will be initialized immediately when the framework starts, while `customLoggerB` will be initialized when the business actually uses `getLogger` or `@Logger` injection for the first time. + +This function is very suitable for scenarios where logs are dynamically created, but the configurations are expected to be merged together. + + + +### Configure associated logs + +The log object can be configured with an associated log object name. + +for example: + +```typescript +export default { + midwayLogger: { + clients: { + customLoggerA: { + aliasName: 'customLoggerB', + // ... + }, + } + // ... + }, +} as MidwayConfig; +``` + +When using the API to retrieve, the same log object will be retrieved with different names. + +```typescript +app.getLogger('customLoggerA') => customLoggerA +app.getLogger('customLoggerB') => customLoggerA +``` + + + +### Configure console output color + +When outputting to the console, if the command line supports color output, different colors will be output for different log levels. If color is not supported, it will not be displayed. + +You can turn off color output directly through configuration. + +```typescript +export default { + midwayLogger: { + default: { + transports: { + console: { + autoColors: false, + } + } + } + // ... + }, +} as MidwayConfig; +``` + + + +## Custom Transport + +The framework provides the function of extending Transport. For example, you can write a Transport to transfer logs and upload them to other log libraries. + + + +### Inherit existing Transport + +If writing to a new file, this can be achieved by using `FileTransport`. + +```typescript +import { FileTransport, isEnableLevel, LoggerLevel, LogMeta } from '@midwayjs/logger'; + +// Transport configuration +interface CustomOptions { + // ... +} + +class CustomTransport extends FileTransport { + log(level: LoggerLevel | false, meta: LogMeta, ...args) { + // Determine whether level satisfies the current Transport + if (!isEnableLevel(level, this.options.level)) { + return; + } + + // Format the message using built-in formatting methods + let buf = this.format(level, meta, args) as string; + //Add newline character + buf += this.options.eol; + + //Write the log you want to write + if (this.options.bufferWrite) { + this.bufSize += buf.length; + this.buf.push(buf); + if (this.buf.length > this.options.bufferMaxLength) { + this.flush(); + } + } else { + // If caching is not enabled, write directly + this.logStream.write(buf); + } + } +} +``` + +Before use, it needs to be registered in the log library. + +```typescript +import { TransportManager } from '@midwayjs/logger'; + +TransportManager.set('custom', CustomTransport); +``` + +You can then use this Transport in your configuration. + +```typescript +// src/config/config.default.ts +import { MidwayConfig } from '@midwayjs/core'; + +export default { + midwayLogger: { + default: { + transports: { + custom: { + dir: 'xxxx', + fileLogName: 'xxx', + // ... + } + } + } + }, +} as MidwayConfig; +``` + +In this way, the original logger will automatically execute the Transport when printing logs. + + + +### Fully customized Transport + +In addition to writing files, logs can also be delivered to remote services. For example, in the following example, the logs are forwarded to another service. + +Note that Transport is an operation that can be executed asynchronously, but the logger itself will not wait for Transport to execute and return. + +```typescript +import { Transport, ITransport, LoggerLevel, LogMeta } from '@midwayjs/logger'; + + +// Transport configuration +interface CustomOptions { + // ... +} + +class CustomTransport extends Transport implements ITransport { + log(level: LoggerLevel | false, meta: LogMeta, ...args) { + // Format the message using built-in formatting methods + let msg = this.format(level, meta, args) as string; + + //Asynchronously write to the log library + remoteSdk.send(msg).catch(err => { + // Log the error or ignore it + console.error(err); + }); + } +} +``` + + + +## Dynamic API + +Dynamically obtain the log object through the `getLogger` method. + +```typescript +// Get coreLogger +const coreLogger = app.getLogger('coreLogger'); +// Get the default contextLogger +const contextLogger = ctx.getLogger(); +// Get the contextLogger created by a specific logger, equivalent to customALogger.createContextLogger(ctx) +const customAContextLogger = ctx.getLogger('customA'); +``` + +The framework's built-in `MidwayLoggerService` also has the above API. + +```typescript +import { MidwayLoggerService } from '@midwayjs/core'; +import { Context } from '@midwayjs/koa'; + +@Provide() +export class MainConfiguration { + + @Inject() + loggerService: MidwayLoggerService; + + @Inject() + ctx: Context; + + async getUser() { + // get custom logger + const customLogger = this.loggerService.getLogger('customLogger'); + + //Create context logger + const customContextLogger = this.loggerService.createContextLogger(this.ctx, customLogger); + } +} +``` + + + +## Common Problem + + + +### 1. The server environment log is not output + +We do not recommend printing too many logs in the server environment. Only print necessary content. Excessive log output affects performance and quickly locates problems. + +To adjust the log level, see the "Configuring Log Level" section. + + + +### 2. The server has no console log + +Generally speaking, the server console log (console) is closed and will only be output to a file. If there are special needs, it can be adjusted individually. + + + +### 3. Some Docker environments fail to start + +Check whether the user who started the current application in the directory where the log is written has permissions. + + + +### 4. How to convert if there is an old configuration? + +The new version of the log library is already compatible with the old configuration. Generally, no additional processing is required. There is a priority relationship between the old configuration and the new configuration when merging. Please check the [Change Document](https://github.com/midwayjs/logger/blob/ main/BREAKING-3.md). + +In order to reduce troubleshooting problems, please use the new configuration format when using the new version of the log library. \ No newline at end of file