Skip to content

Commit

Permalink
feat: BaseContextClass add logger (#816)
Browse files Browse the repository at this point in the history
- move BaseContextClass to egg
- add BaseContextLogger
  • Loading branch information
dead-horse authored and popomore committed Apr 28, 2017
1 parent 9871e45 commit 0757655
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 19 deletions.
1 change: 1 addition & 0 deletions docs/source/zh-cn/basics/controller.md
Expand Up @@ -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 基类

Expand Down
30 changes: 20 additions & 10 deletions docs/source/zh-cn/basics/service.md
Expand Up @@ -28,6 +28,25 @@ 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 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` 目录,可以支持多级目录,访问的时候可以通过目录名级联访问。
Expand All @@ -39,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。
Expand Down
10 changes: 8 additions & 2 deletions index.js
Expand Up @@ -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.Service = require('./lib/core/base_context_class');

/**
* @member {Service} Egg#BaseContextClass
* @since 1.2.0
*/
exports.BaseContextClass = require('./lib/core/base_context_class');
48 changes: 48 additions & 0 deletions lib/core/base_context_class.js
@@ -0,0 +1,48 @@
'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
* @since 1.0.0
*/
constructor(ctx) {
/**
* @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;
}

get logger() {
if (!this[LOGGER]) this[LOGGER] = new BaseContextLogger(this.ctx, this.pathName);
return this[LOGGER];
}
}

module.exports = BaseContextClass;
63 changes: 63 additions & 0 deletions 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;
22 changes: 22 additions & 0 deletions lib/egg.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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;
}

/**
Expand Down
22 changes: 22 additions & 0 deletions 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;
}
};
}
7 changes: 7 additions & 0 deletions 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');
};
13 changes: 13 additions & 0 deletions 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'));
}
}
}
@@ -0,0 +1,3 @@
'use strict';

exports.keys = 'test keys';
3 changes: 3 additions & 0 deletions test/fixtures/apps/base-context-class/package.json
@@ -0,0 +1,3 @@
{
"name": "base-context-class"
}
1 change: 1 addition & 0 deletions test/index.test.js
Expand Up @@ -10,6 +10,7 @@ describe('test/index.test.js', () => {
'AgentWorkerLoader',
'AppWorkerLoader',
'Application',
'BaseContextClass',
'Controller',
'Service',
'startCluster',
Expand Down
41 changes: 41 additions & 0 deletions test/lib/egg.test.js
Expand Up @@ -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) {
Expand Down
9 changes: 2 additions & 7 deletions test/lib/plugins/schedule.test.js
Expand Up @@ -3,11 +3,12 @@
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* () {
const app = utils.cluster('apps/schedule', {
workers: 4,
workers: 2,
});
yield app.ready();
yield sleep(5000);
Expand All @@ -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');
Expand Down

0 comments on commit 0757655

Please sign in to comment.