From b040bd7447b7f368d8bbf371725d0aab147a4ad6 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 27 Apr 2017 16:52:41 +0800 Subject: [PATCH 1/7] feat: BaseContextClass add logger - move BaseContextClass to egg - add BaseContextLogger --- index.js | 10 ++- lib/core/base_context_class.js | 55 ++++++++++++++++ lib/core/base_context_logger.js | 63 +++++++++++++++++++ lib/egg.js | 22 +++++++ .../base-context-class/app/controller/home.js | 22 +++++++ .../apps/base-context-class/app/router.js | 7 +++ .../base-context-class/app/service/home.js | 13 ++++ .../config/config.default.js | 3 + .../apps/base-context-class/package.json | 3 + test/lib/egg.test.js | 41 ++++++++++++ 10 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 lib/core/base_context_class.js create mode 100644 lib/core/base_context_logger.js create mode 100644 test/fixtures/apps/base-context-class/app/controller/home.js create mode 100644 test/fixtures/apps/base-context-class/app/router.js create mode 100644 test/fixtures/apps/base-context-class/app/service/home.js create mode 100644 test/fixtures/apps/base-context-class/config/config.default.js create mode 100644 test/fixtures/apps/base-context-class/package.json diff --git a/index.js b/index.js index b196cc63d1..3492efbbb1 100644 --- a/index.js +++ b/index.js @@ -38,10 +38,16 @@ exports.AgentWorkerLoader = require('./lib/loader').AgentWorkerLoader; * @member {Controller} Egg#Controller * @since 1.1.0 */ -exports.Controller = require('egg-core').BaseContextClass; +exports.Controller = require('./lib/core/base_context_class'); /** * @member {Service} Egg#Service * @since 1.1.0 */ -exports.Service = require('egg-core').BaseContextClass; +exports.Controller = require('./lib/core/base_context_class'); + +/** + * @member {Service} Egg#BaseContextClass + * @since 1.2.0 + */ +exports.BaseContextClass = require('./lib/core/base_context_class'); diff --git a/lib/core/base_context_class.js b/lib/core/base_context_class.js new file mode 100644 index 0000000000..edee6709eb --- /dev/null +++ b/lib/core/base_context_class.js @@ -0,0 +1,55 @@ +'use strict'; + +const BaseContextLogger = require('./base_context_logger'); + +const LOGGER = Symbol('BaseContextClass#logger'); + +/** + * BaseContextClass is a base class that can be extended, + * it's instantiated in context level, + * {@link Helper}, {@link Service} is extending it. + */ +class BaseContextClass { + + /** + * @constructor + * @param {Context} ctx - context instance + * @param {String} pathName - class path name + * @since 1.0.0 + */ + constructor(ctx, pathName) { + /** + * @member {Context} BaseContextClass#ctx + * @since 1.0.0 + */ + this.ctx = ctx; + /** + * @member {Application} BaseContextClass#app + * @since 1.0.0 + */ + this.app = ctx.app; + /** + * @member {Config} BaseContextClass#config + * @since 1.0.0 + */ + this.config = ctx.app.config; + /** + * @member {Service} BaseContextClass#service + * @since 1.0.0 + */ + this.service = ctx.service; + + /** + * @member {Service} BaseContextClass#pathName + * @since 1.0.0 + */ + if (pathName) this.pathName = pathName; + } + + get logger() { + if (!this[LOGGER]) this[LOGGER] = new BaseContextLogger(this.ctx, this.pathName); + return this[LOGGER]; + } +} + +module.exports = BaseContextClass; diff --git a/lib/core/base_context_logger.js b/lib/core/base_context_logger.js new file mode 100644 index 0000000000..f4a8a3b551 --- /dev/null +++ b/lib/core/base_context_logger.js @@ -0,0 +1,63 @@ +'use strict'; + +const CALL = Symbol('BaseContextLogger#call'); + +class BaseContextLogger { + + /** + * @constructor + * @param {Context} ctx - context instance + * @param {String} pathName - class path name + * @since 1.0.0 + */ + constructor(ctx, pathName) { + /** + * @member {Context} BaseContextLogger#ctx + * @since 1.2.0 + */ + this.ctx = ctx; + this.pathName = pathName; + } + + [CALL](method, args) { + // add `[${pathName}]` in log + if (this.pathName && typeof args[0] === 'string') { + args[0] = `[${this.pathName}] ${args[0]}`; + } + this.ctx.logger[method](...args); + } + + /** + * @member {Function} BaseContextLogger#debug + * @since 1.2.0 + */ + debug(...args) { + this[CALL]('debug', args); + } + + /** + * @member {Function} BaseContextLogger#info + * @since 1.2.0 + */ + info(...args) { + this[CALL]('info', args); + } + + /** + * @member {Function} BaseContextLogger#warn + * @since 1.2.0 + */ + warn(...args) { + this[CALL]('warn', args); + } + + /** + * @member {Function} BaseContextLogger#error + * @since 1.2.0 + */ + error(...args) { + this[CALL]('error', args); + } +} + +module.exports = BaseContextLogger; diff --git a/lib/egg.js b/lib/egg.js index a79bf265e9..fb0b413ac0 100644 --- a/lib/egg.js +++ b/lib/egg.js @@ -13,6 +13,7 @@ const createHttpClient = require('./core/httpclient'); const createLoggers = require('./core/logger'); const Singleton = require('./core/singleton'); const utils = require('./core/utils'); +const BaseContextClass = require('./core/base_context_class'); const HTTPCLIENT = Symbol('EggApplication#httpclient'); const LOGGERS = Symbol('EggApplication#loggers'); @@ -103,6 +104,27 @@ class EggApplication extends EggCore { this.messenger.close(); process.removeListener('unhandledRejection', this._unhandledRejectionHandler); }); + + /** + * Retreive base context class + * @member {Controller} BaseContextClass + * @since 1.0.0 + */ + this.BaseContextClass = BaseContextClass; + + /** + * Retreive base controller + * @member {Controller} Controller + * @since 1.0.0 + */ + this.Controller = BaseContextClass; + + /** + * Retreive base service + * @member {Service} Service + * @since 1.0.0 + */ + this.Service = BaseContextClass; } /** diff --git a/test/fixtures/apps/base-context-class/app/controller/home.js b/test/fixtures/apps/base-context-class/app/controller/home.js new file mode 100644 index 0000000000..fe32134b5d --- /dev/null +++ b/test/fixtures/apps/base-context-class/app/controller/home.js @@ -0,0 +1,22 @@ +'use strict'; + +module.exports = app => { + return class HomeController extends app.Controller { + * show() { + yield this.service.home.show(); + this.ctx.body = 'hello'; + this.logger.debug('debug'); + this.logger.info('appname: %s', this.config.name); + this.logger.warn('warn'); + this.logger.error(new Error('some error')); + } + + getPathName() { + this.ctx.body = this.pathName; + } + + getConfig() { + this.ctx.body = this.config.name; + } + }; +} diff --git a/test/fixtures/apps/base-context-class/app/router.js b/test/fixtures/apps/base-context-class/app/router.js new file mode 100644 index 0000000000..77fc6e6492 --- /dev/null +++ b/test/fixtures/apps/base-context-class/app/router.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = app => { + app.get('/', 'home.show'); + app.get('/pathName', 'home.getPathName'); + app.get('/config', 'home.getConfig'); +}; diff --git a/test/fixtures/apps/base-context-class/app/service/home.js b/test/fixtures/apps/base-context-class/app/service/home.js new file mode 100644 index 0000000000..ea9f307283 --- /dev/null +++ b/test/fixtures/apps/base-context-class/app/service/home.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = app => { + return class HomeController extends app.Service { + * show() { + this.ctx.body = 'hello'; + this.logger.debug('debug'); + this.logger.info('appname: %s', this.config.name); + this.logger.warn('warn'); + this.logger.error(new Error('some error')); + } + } +} diff --git a/test/fixtures/apps/base-context-class/config/config.default.js b/test/fixtures/apps/base-context-class/config/config.default.js new file mode 100644 index 0000000000..4a364a6ebc --- /dev/null +++ b/test/fixtures/apps/base-context-class/config/config.default.js @@ -0,0 +1,3 @@ +'use strict'; + +exports.keys = 'test keys'; diff --git a/test/fixtures/apps/base-context-class/package.json b/test/fixtures/apps/base-context-class/package.json new file mode 100644 index 0000000000..13ed4173e6 --- /dev/null +++ b/test/fixtures/apps/base-context-class/package.json @@ -0,0 +1,3 @@ +{ + "name": "base-context-class" +} diff --git a/test/lib/egg.test.js b/test/lib/egg.test.js index e4295cbf71..0b6b3d52c8 100644 --- a/test/lib/egg.test.js +++ b/test/lib/egg.test.js @@ -181,6 +181,47 @@ describe('test/lib/egg.test.js', () => { assert(body.match(/at .+router.js:\d+:\d+\)/)); }); }); + + describe('BaseContextClass', () => { + let app; + before(() => { + app = utils.app('apps/base-context-class'); + return app.ready(); + }); + after(() => app.close()); + + it('should access base context properties success', function* () { + mm(app.config.logger, 'level', 'DEBUG'); + yield request(app.callback()) + .get('/') + .expect('hello') + .expect(200); + + const logPath = path.join(utils.getFilepath('apps/base-context-class'), 'logs/base-context-class/base-context-class-web.log'); + const log = fs.readFileSync(logPath, 'utf8'); + assert(log.match(/INFO .*? \[service\.home\] appname: base-context-class/)); + assert(log.match(/INFO .*? \[controller\.home\] appname: base-context-class/)); + assert(log.match(/WARN .*? \[service\.home\] warn/)); + assert(log.match(/WARN .*? \[controller\.home\] warn/)); + const errorPath = path.join(utils.getFilepath('apps/base-context-class'), 'logs/base-context-class/common-error.log'); + const error = fs.readFileSync(errorPath, 'utf8'); + assert(error.match(/nodejs.Error: some error/)); + }); + + it('should get pathName success', function* () { + yield request(app.callback()) + .get('/pathName') + .expect('controller.home') + .expect(200); + }); + + it('should get config success', function* () { + yield request(app.callback()) + .get('/config') + .expect('base-context-class') + .expect(200); + }); + }); }); function readJson(p) { From 0486689dfc9095d93901f79e374b2e334ca038e3 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 27 Apr 2017 17:02:20 +0800 Subject: [PATCH 2/7] test: fix test case --- index.js | 2 +- lib/core/base_context_class.js | 9 +-------- test/index.test.js | 1 + 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 3492efbbb1..32fdbac8da 100644 --- a/index.js +++ b/index.js @@ -44,7 +44,7 @@ exports.Controller = require('./lib/core/base_context_class'); * @member {Service} Egg#Service * @since 1.1.0 */ -exports.Controller = require('./lib/core/base_context_class'); +exports.Service = require('./lib/core/base_context_class'); /** * @member {Service} Egg#BaseContextClass diff --git a/lib/core/base_context_class.js b/lib/core/base_context_class.js index edee6709eb..5c046576f0 100644 --- a/lib/core/base_context_class.js +++ b/lib/core/base_context_class.js @@ -14,10 +14,9 @@ class BaseContextClass { /** * @constructor * @param {Context} ctx - context instance - * @param {String} pathName - class path name * @since 1.0.0 */ - constructor(ctx, pathName) { + constructor(ctx) { /** * @member {Context} BaseContextClass#ctx * @since 1.0.0 @@ -38,12 +37,6 @@ class BaseContextClass { * @since 1.0.0 */ this.service = ctx.service; - - /** - * @member {Service} BaseContextClass#pathName - * @since 1.0.0 - */ - if (pathName) this.pathName = pathName; } get logger() { diff --git a/test/index.test.js b/test/index.test.js index 3ea1287557..44a3de9470 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -10,6 +10,7 @@ describe('test/index.test.js', () => { 'AgentWorkerLoader', 'AppWorkerLoader', 'Application', + 'BaseContextClass', 'Controller', 'Service', 'startCluster', From ebbe24686d5da8151420e619a1a9169832345523 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 28 Apr 2017 11:14:30 +0800 Subject: [PATCH 3/7] test: use mz-modules/sleep --- test/lib/plugins/schedule.test.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/lib/plugins/schedule.test.js b/test/lib/plugins/schedule.test.js index ff5752f3f9..2fdb8bde61 100644 --- a/test/lib/plugins/schedule.test.js +++ b/test/lib/plugins/schedule.test.js @@ -3,6 +3,7 @@ const path = require('path'); const fs = require('fs'); const utils = require('../../utils'); +const sleep = require('mz-modules/sleep'); describe('test/lib/plugins/schedule.test.js', () => { it('should schedule work', function* () { @@ -17,12 +18,6 @@ describe('test/lib/plugins/schedule.test.js', () => { }); }); -function sleep(time) { - return new Promise(resolve => { - setTimeout(resolve, time); - }); -} - function getLogContent(name) { const logPath = path.join(__dirname, '../../fixtures/apps', name, 'logs', name, `${name}-web.log`); return fs.readFileSync(logPath, 'utf8'); From ca215c9ba9f467d15991b9e8503aa795c09c5165 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 28 Apr 2017 11:20:03 +0800 Subject: [PATCH 4/7] docs: add this.logger in controller and service --- docs/source/zh-cn/basics/controller.md | 1 + docs/source/zh-cn/basics/service.md | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/source/zh-cn/basics/controller.md b/docs/source/zh-cn/basics/controller.md index 6c2aa65970..f043c0fac7 100644 --- a/docs/source/zh-cn/basics/controller.md +++ b/docs/source/zh-cn/basics/controller.md @@ -77,6 +77,7 @@ module.exports = app => { - `this.app`: 当前应用 [Application](./extend.md#application) 对象的实例,通过它我们可以拿到框架提供的全局对象和方法。 - `this.service`:应用定义的 [Service](./service.md),通过它我们可以访问到抽象出的业务层,等价于 `this.ctx.service` 。 - `this.config`:应用运行时的[配置项](./config.md)。 +- `this.logger`:logger 对象,上面有四个方法(DEBUG,INFO,WARN,ERROR),分别代表打印四个不同级别的日志,使用方法和效果与[context logger](../core/logger.md#context-logger)中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。 #### 自定义 Controller 基类 diff --git a/docs/source/zh-cn/basics/service.md b/docs/source/zh-cn/basics/service.md index 9b73803974..bc7a30b5a5 100644 --- a/docs/source/zh-cn/basics/service.md +++ b/docs/source/zh-cn/basics/service.md @@ -28,6 +28,16 @@ title: Service }; ``` +### 属性 + +项目中的 Service 需要继承于 `app.Service`,它拥有下列属性方便我们进行开发: + +- `this.ctx`: 当前请求的上下文 [Context](./extend.md#context) 对象的实例,通过它我们可以拿到框架封装好的处理当前请求的各种便捷属性和方法。 +- `this.app`: 当前应用 [Application](./extend.md#application) 对象的实例,通过它我们可以拿到框架提供的全局对象和方法。 +- `this.service`:应用定义的 [Service](./service.md),通过它我们可以访问到抽象出的业务层,等价于 `this.ctx.service` 。 +- `this.config`:应用运行时的[配置项](./config.md)。 +- `this.logger`:logger 对象,上面有四个方法(DEBUG,INFO,WARN,ERROR),分别代表打印四个不同级别的日志,使用方法和效果与[context logger](../core/logger.md#context-logger)中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。 + ### 注意事项 - Service 文件必须放在 `app/service` 目录,可以支持多级目录,访问的时候可以通过目录名级联访问。 From 85e43cfe4309ccedf83865eb1ee7e4025126adc4 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 28 Apr 2017 11:27:01 +0800 Subject: [PATCH 5/7] docs: fix --- docs/source/zh-cn/basics/controller.md | 2 +- docs/source/zh-cn/basics/service.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/zh-cn/basics/controller.md b/docs/source/zh-cn/basics/controller.md index f043c0fac7..0f4c41c978 100644 --- a/docs/source/zh-cn/basics/controller.md +++ b/docs/source/zh-cn/basics/controller.md @@ -77,7 +77,7 @@ module.exports = app => { - `this.app`: 当前应用 [Application](./extend.md#application) 对象的实例,通过它我们可以拿到框架提供的全局对象和方法。 - `this.service`:应用定义的 [Service](./service.md),通过它我们可以访问到抽象出的业务层,等价于 `this.ctx.service` 。 - `this.config`:应用运行时的[配置项](./config.md)。 -- `this.logger`:logger 对象,上面有四个方法(DEBUG,INFO,WARN,ERROR),分别代表打印四个不同级别的日志,使用方法和效果与[context logger](../core/logger.md#context-logger)中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。 +- `this.logger`:logger 对象,上面有四个方法(`debug`,`info`,`warn`,`error`),分别代表打印四个不同级别的日志,使用方法和效果与 [context logger](../core/logger.md#context-logger) 中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。 #### 自定义 Controller 基类 diff --git a/docs/source/zh-cn/basics/service.md b/docs/source/zh-cn/basics/service.md index bc7a30b5a5..8a932b642e 100644 --- a/docs/source/zh-cn/basics/service.md +++ b/docs/source/zh-cn/basics/service.md @@ -34,9 +34,9 @@ title: Service - `this.ctx`: 当前请求的上下文 [Context](./extend.md#context) 对象的实例,通过它我们可以拿到框架封装好的处理当前请求的各种便捷属性和方法。 - `this.app`: 当前应用 [Application](./extend.md#application) 对象的实例,通过它我们可以拿到框架提供的全局对象和方法。 -- `this.service`:应用定义的 [Service](./service.md),通过它我们可以访问到抽象出的业务层,等价于 `this.ctx.service` 。 +- `this.service`:应用定义的 [Service](./service.md),通过它我们可以访问到其他业务层,等价于 `this.ctx.service` 。 - `this.config`:应用运行时的[配置项](./config.md)。 -- `this.logger`:logger 对象,上面有四个方法(DEBUG,INFO,WARN,ERROR),分别代表打印四个不同级别的日志,使用方法和效果与[context logger](../core/logger.md#context-logger)中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。 +- `this.logger`:logger 对象,上面有四个方法(`debug`,`info`,`warn`,`error`),分别代表打印四个不同级别的日志,使用方法和效果与 [context logger](../core/logger.md#context-logger) 中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。 ### 注意事项 From 12661b688bef75227cbc3f621386941868441e4b Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 28 Apr 2017 12:41:52 +0800 Subject: [PATCH 6/7] test: decrease worker num --- test/lib/plugins/schedule.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/plugins/schedule.test.js b/test/lib/plugins/schedule.test.js index 2fdb8bde61..360fe1d858 100644 --- a/test/lib/plugins/schedule.test.js +++ b/test/lib/plugins/schedule.test.js @@ -8,7 +8,7 @@ const sleep = require('mz-modules/sleep'); describe('test/lib/plugins/schedule.test.js', () => { it('should schedule work', function* () { const app = utils.cluster('apps/schedule', { - workers: 4, + workers: 2, }); yield app.ready(); yield sleep(5000); From 25e2f70967cac12c3017201e07e98101bc39170a Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 28 Apr 2017 14:41:37 +0800 Subject: [PATCH 7/7] docs: update service --- docs/source/zh-cn/basics/service.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/source/zh-cn/basics/service.md b/docs/source/zh-cn/basics/service.md index 8a932b642e..fe986be580 100644 --- a/docs/source/zh-cn/basics/service.md +++ b/docs/source/zh-cn/basics/service.md @@ -38,6 +38,15 @@ title: Service - `this.config`:应用运行时的[配置项](./config.md)。 - `this.logger`:logger 对象,上面有四个方法(`debug`,`info`,`warn`,`error`),分别代表打印四个不同级别的日志,使用方法和效果与 [context logger](../core/logger.md#context-logger) 中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。 +### Service ctx 详解 + +为了可以获取用户请求的链路,我们在 Service 初始化中,注入了请求上下文, 用户在方法中可以直接通过 `this.ctx` 来获取上下文相关信息。关于上下文的具体详解可以参看 [Context](./extend.md#context), +有了 ctx 我们可以拿到框架给我们封装的各种便捷属性和方法。比如我们可以用: + +- `this.ctx.curl` 发起网络调用。 +- `this.ctx.service.otherService` 调用其他 Service。 +- `this.ctx.db` 发起数据库调用等, db 可能是其他插件提前挂载到 app 上的模块。 + ### 注意事项 - Service 文件必须放在 `app/service` 目录,可以支持多级目录,访问的时候可以通过目录名级联访问。 @@ -49,18 +58,9 @@ title: Service ``` - 一个 Service 文件只能包含一个类, 这个类需要通过 `module.exports` 的方式返回。 -- Service 需要通过 Class 的方式定义,父类必须是 `app.Service`, 其中 `app.Service` 会在初始化 Service 的时候通过参数传递进来。 +- Service 需要通过 Class 的方式定义,父类必须是 `app.Service`, `app` 对象会在初始化 Service 的时候通过参数传递进来。 - Service 不是单例,是 **请求级别** 的对象,框架在每次请求中首次访问 `ctx.service.xx` 时延迟实例化,所以 Service 中可以通过 this.ctx 获取到当前请求的上下文。 -### Service ctx 详解 - -为了可以获取用户请求的链路,我们在 Service 初始化中,注入了请求上下文, 用户在方法中可以直接通过 `this.ctx` 来获取上下文相关信息。关于上下文的具体详解可以参看 [Context](./extend.md#context), -有了 ctx 我们可以拿到框架给我们封装的各种便捷属性和方法。比如我们可以用: - -- `this.ctx.curl` 发起网络调用。 -- `this.ctx.service.otherService` 调用其他 Service。 -- `this.ctx.db` 发起数据库调用等, db 可能是其他插件提前挂载到 app 上的模块。 - ## 使用 Service 下面就通过一个完整的例子,看看怎么使用 Service。