So let’s go back to where we were last time, where we said:

  • Runtime environment
  • The Config configuration
  • Middleware
  • routing
  • Controller

Service

A Service is an abstraction layer used to encapsulate business logic in complex business scenarios

Usage scenarios

  • Complex data processing, such as the information to be displayed needs to be obtained from the database, but also through certain rules calculation, can be returned to the user display. Or after the calculation is complete, update to the database.
  • Third party service invocation, such as GitHub information retrieval.

Define the Service

// app/service/user.js
const Service = require('egg').Service;

class UserService extends Service {
  async find(uid) {
    const user = await this.ctx.db.query('select * from user where uid = ? ', uid);
    returnuser; }}module.exports = UserService;
Copy the code

attribute

The framework instantiates the corresponding Service instance for each user request, and since it inherits from egg.service, it has the following properties to facilitate development:

  • this.ctx: Instance of the current request Context object
  • this.app: Indicates the instance of the current Application object
  • this.service: Applies the defined Service
  • this.config: Indicates the configuration item of the application runtime
  • this.logger: logger object, which has four methods (debug.info.warn.error) indicates that four levels of logs are printed. The method and effect are the same as those described in Context Logger, but logs recorded by this Logger object are preceded by the path of the log file to quickly locate the log location.

Service CTX,

  • this.ctx.curlInitiate a network call.
  • this.ctx.service.otherServiceCall other services.
  • this.ctx.dbInitiate database calls, etc. Db may be a module that other plug-ins mount to app in advance.

Matters needing attention

  • The Service file must be placedapp/serviceMultiple levels of directories can be supported. Access can be cascaded by directory name.
app/service/biz/user.js => ctx.service.biz.user // Multi-level directory, based on the directory name cascaded access
app/service/sync_user.js => ctx.service.syncUser // Underline is automatically converted to auto hump
app/service/HackerNews.js => ctx.service.hackerNews // Uppercase automatically converts to hump
Copy the code
  • A Service file can contain only one class, which is returned via module.exports.
  • A Service needs to be defined as a Class, and its parent must be egg.service.
  • Service is not a singleton, it isRequest levelObject that the framework accesses for the first time on each requestctx.service.xxTime-delayed instantiation, so the context of the current request can be retrieved from the Service via this.ctx.

Use the Service

// app/controller/user.js
const Controller = require('egg').Controller;
class UserController extends Controller {
  async info() {
    const { ctx } = this;
    const userId = ctx.params.id;
    const userInfo = awaitctx.service.user.find(userId); ctx.body = userInfo; }}module.exports = UserController;

// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
  // No constructor is required by default.
  // constructor(ctx) {
  // super(ctx); If you need to do something in a constructor, make sure you use this. CTX.
  // // to get CTX directly from this. CTX
  // // can also get the app directly from this.app
  // }
  async find(uid) {
    // Get user details from the database
    const user = await this.ctx.db.query('select * from user where uid = ? ', uid);

    // Assume there are some more complex calculations here, and return the required information.
    const picture = await this.getPicture(uid);

    return {
      name: user.user_name,
      age: user.age,
      picture,
    };
  }

  async getPicture(uid) {
    const result = await this.ctx.curl(`http://photoserver/uid=${uid}`, { dataType: 'json' });
    returnresult.data; }}module.exports = UserService;
Copy the code

The plug-in

Why plug-ins

The following issues were found in using Koa middleware:

  • In fact, the middleware load order, but the middleware itself can not manage the order, only to the user. This is actually very unfriendly and can make all the difference if the order is out of order.
  • Middleware is positioned to intercept user requests and do things around them, such as authentication, security checks, access logging, and so on. However, the reality is that some functions are independent of the request, such as scheduled tasks, message subscriptions, background logic, and so on.
  • Some features have very complex initialization logic that needs to be done at application startup time. This is obviously not a good place to implement in middleware.

Relationships between middleware, plug-ins, and applications

A plugin is essentially a “mini app”, almost the same as an app: +

  • It includes services, middleware, configuration, framework extensions, and so on.
  • It has no separate Router and Controller.
  • It has noplugin.jsYou can only declare dependencies on other plug-ins, not enable or disable them.

Their relationship is:

  • Applications can be imported directly into Koa’s middleware.
  • The plug-in itself can contain middleware.
  • Multiple plug-ins can be wrapped as a single upper-layer framework.

The use of plug-in

Plug-ins are typically reused as NPM modules:

npm i egg-mysql --save
Copy the code

Introducing dependencies by ^ is recommended, and locking versions is strongly discouraged.

{
  "dependencies": {
    "egg-mysql": "^ 3.0.0"}}Copy the code

You then need to declare this in the config/plugin.js of your application or framework:

// config/plugin.js
// Use the mysql plugin
exports.mysql = {
  enable: true.package: 'egg-mysql'};Copy the code

App.mysql. query(SQL, values);

Egg -mysql plug-in documentation

Parameter is introduced

Each configuration item in plugin.js supports:

  • {Boolean} enable– Whether to enable this plug-in. The default value is true
  • {String} package– NPM module name. Plug-ins are imported through the NPM module
  • {String} path– Plug-in absolute path, mutually exclusive with package configuration
  • {Array} env– This function is enabled only in the specified runtime environment and overrides the plugin’s own package.json configuration

On and off

There is no need to configure package or path when using the built-in plug-in in the upper-layer framework. You only need to specify enable or not:

// For built-in plug-ins, you can turn them on or off in the following concise way
exports.onerror = false;
Copy the code

Configuration based on the environment

We also support the plugin.{env}.js pattern, which loads the plug-in configuration based on the runtime environment.

For example, if you define a plug-in for your development environment, egg-dev, which you only want to load locally, can be installed in devDependencies.

// npm i egg-dev --save-dev
// package.json
{
  "devDependencies": {
    "egg-dev": "*"}}Copy the code

Then declare in plugin.local.js:

// config/plugin.local.js
exports.dev = {
  enable: true.package: 'egg-dev'};Copy the code

NPM I –production does not need to download the egg-dev package.

Note:

  • There is noplugin.default.js
  • This command can be used only at the application layer, not at the framework layer.

Package and the path

  • packagenpmWay of introduction, is also the most common way of introduction
  • pathThese are absolute path introductions, such as an in-app plug-in that has not yet reached the stage of open source distribution of a separate NPM, or an application that overlays some plug-ins of the framework itself
// config/plugin.js
const path = require('path');
exports.mysql = {
  enable: true.path: path.join(__dirname, '.. /lib/plugin/egg-mysql'),};Copy the code

The plug-in configuration

Plugins generally include their own default configuration, which can be overridden by application developers in config.default.js:

// config/config.default.js
exports.mysql = {
  client: {
    host: 'mysql.com'.port: '3306'.user: 'test_user'.password: 'test_password'.database: 'test',}};Copy the code

The plugin list

The framework has built-in plug-ins commonly used in enterprise applications by default:

  • Onerror Unified exception handling
  • The Session Session implementation
  • I18n multilingual
  • Watcher file and folder monitoring
  • Multipart file streaming upload
  • Security safety
  • Development Development environment configuration
  • Logrotator Log segmentation
  • Schedule Scheduled task
  • Static Static server
  • The json json support
  • View template engine

More community plugins can be searched on GitHub for egg-plugin.

See Plug-in Development for more details

Timing task

Although the HTTP Server we developed through the framework is a request-response model, there are still many scenarios where some scheduled tasks need to be performed

  • Report application status periodically.
  • Periodically update the local cache from the remote interface.
  • Periodically cut files and delete temporary files.

Writing a scheduled Task

All scheduled tasks are stored in the APP /schedule directory. Each file is an independent scheduled task. You can configure the attributes and execution methods of scheduled tasks.

Create an update_cache.js file in the app/schedule directory

const Subscription = require('egg').Subscription;

class UpdateCache extends Subscription {
  // Use the schedule attribute to set the execution interval of scheduled tasks
  static get schedule() {
    return {
      interval: '1m'.// 1 minute interval
      type: 'all'.// specify that all workers need to be executed
    };
  }

  // subscribe is the function that is run when the actual scheduled task is executed
  async subscribe() {
    const res = await this.ctx.curl('http://www.api.com/cache', {
      dataType: 'json'});this.ctx.app.cache = res.data; }}module.exports = UpdateCache;
Copy the code

I could also write it as theta

module.exports = {
  schedule: {
    interval: '1m'.// 1 minute interval
    type: 'all'.// specify that all workers need to be executed
  },
  async task(ctx) {
    const res = await ctx.curl('http://www.api.com/cache', {
      dataType: 'json'}); ctx.app.cache = res.data; }};Copy the code

This scheduled task will be executed every 1 minute on each Worker process to mount the remote data request back to app.cache.

task

  • tasksubscribeAt the same time supportgenerator functioasync function.
  • taskIn for refsctx, anonymous Context instance that can be called fromserviceAnd so on.

Regular way

Timing tasks can be set to interval or CRon.

interval

Interval specifies the execution time of a scheduled task. The scheduled task will be executed at a specified interval. Interval can be configured as

  • The value is a number in milliseconds, for example, 5000
  • Character type, converted to milliseconds by MS, such as 5s.
module.exports = {
  schedule: {
    // This command is executed every 10 seconds
    interval: '10s',}};Copy the code
cron

The schedule.cron parameter is used to set the execution time of scheduled tasks. The scheduled tasks will be executed at a specific point in time based on the CRON expression. Cron expressions are parsed by cron-parser.

Note: Cron-Parser supports optional seconds (Linux crontab does not).

* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ day of week (0 to 7) (0 or 7 is Sun │ │ │ │ └ ─ ─ ─ ─ ─ the month (1-12) │ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ day of the month (1-31) │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ the adrenaline-charged (0-23) │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ minute (0-59) └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ the second (0 to 59, optional)Copy the code
module.exports = {
  schedule: {
    // Execute every three hours on time
    cron: '0 0 */3 * * *',}};Copy the code

Type the type

The worker, and all. Both workers and all support the above two timing modes, but when it comes to execution time, different workers will execute the scheduled task:

  • workerType: Only one worker on each machine will perform the scheduled task, and the selection of the worker to perform the scheduled task is random.
  • allType: Each worker on each machine will perform this scheduled task.

The other parameters

In addition to the parameters just described, scheduled tasks also support these parameters:

  • cronOptionsFor example, configure the time zone of cron. See the Cron-parser document
  • immediate: If this parameter is set to true, the scheduled task will be executed immediately after the application is started and ready.
  • disable: If this parameter is set to true, the scheduled task will not be started.
  • env: array. The scheduled task is started only in the specified environment.

Perform log

Perform log will be output to the ${appInfo. Root} / logs / {app_name} / egg – the schedule. The log, the default is not output to the console, can through the config. CustomLogger. ScheduleLogger from definition.

// config/config.default.js
config.customLogger = {
  scheduleLogger: {
    // consoleLevel: 'NONE',
    // file: path.join(appInfo.root, 'logs', appInfo.name, 'egg-schedule.log'),}};Copy the code

Dynamically configure scheduled tasks

module.exports = app= > {
  return {
    schedule: {
      interval: app.config.cacheTick,
      type: 'all',},async task(ctx) {
      const res = await ctx.curl('http://www.api.com/cache', {
        contentType: 'json'}); ctx.app.cache = res.data; }}; };Copy the code

Manually execute scheduled tasks

We can run a scheduled task via app.runschedule (schedulePath). App.runschedule takes a scheduled task file path (relative or complete absolute path in the app/schedule directory), executes the corresponding scheduled task, and returns a Promise.

  • You can write unit tests for scheduled tasks more elegantly by manually executing them.
const mm = require('egg-mock');
const assert = require('assert');

it('should schedule work fine'.async() = > {const app = mm.app();
  await app.ready();
  await app.runSchedule('update_cache');
  assert(app.cache);
});
Copy the code
  • When an application starts, manually initialize the system by performing scheduled tasks. After the initialization is complete, start the application. seeEnable application customizationChapter, we can do inapp.jsTo write the initialization logic.
module.exports = app= > {
  app.beforeStart(async() = > {// Ensure that the data is ready before the application starts the listening port
    // Subsequent data updates are automatically triggered by scheduled tasks
    await app.runSchedule('update_cache');
  });
};
Copy the code

Framework extension

The framework provides multiple extension points to extend its functionality: Application, Context, Request, Response, and Helper.

Application

access

  • ctx.app
  • Controller, Middleware, Helper, and Service are all acceptablethis.appAccess the Application object, for examplethis.app.configAccess configuration objects.
  • In app.js, the app object is injected into the entry function as the first argument
// app.js
module.exports = app= > {
  // Use the app object
};
Copy the code

scaling

Framework will put the app/extend/application. Js defined in object and Koa application of prototype object merge, the prototype after application startup based on extended when they generate app object.

// app/extend/application.js
module.exports = {
  foo(param) {
    // This is the app object in which you can call other methods on the app or access properties}};Copy the code

Attribute extensions

Generally, attributes need to be computed only once. Therefore, you must implement caching. Otherwise, attributes will be computed multiple times when they are accessed multiple times, which reduces application performance.

The recommended approach is to use the Symbol + Getter pattern.

// app/extend/application.js
const BAR = Symbol('Application#bar');

module.exports = {
  get bar() {
    // This is the app object in which you can call other methods on the app or access properties
    if (!this[BAR]) {
      // The reality is definitely more complicated
      this[BAR] = this.config.xx + this.config.yy;
    }
    return this[BAR]; }};Copy the code

Context

Context refers to Koa’s request Context, which is a request-level object that generates an instance of Context per request, often abbreviated to CTX. In all documents, Context and CTX refer to Koa Context objects.

access

  • The first argument returned to a function in Middleware is CTX, for examplectx.cookies.get('foo').
  • There are two ways to write controller, and the class way passesthis.ctxThe method is written directly throughctxThe parameters.
  • Helper, service this points to the helper, service object itself, use this.ctx to access the context object, for examplethis.ctx.cookies.get('foo').

scaling

The framework merges objects defined in app/extend/context.js with Koa Context’s Prototype object and generates CTX objects based on the extended Prototype when processing requests.

// app/extend/context.js
module.exports = {
  foo(param) {
    // This is the CTX object from which you can call other methods on the CTX or access properties}};Copy the code

Property extension is the same as Application

Request

The Request object, like the Koa Request object, is a request-level object that provides a number of request-specific properties and methods for use.

access

ctx.request

Many properties and methods on CTX are proxied to the Request object. Using CTX to access these properties and methods is equivalent to using Request, for example, ctx.url === ctx.request.url.

scaling

The framework merges the objects defined in app/extend/request.js with the built-in Request Prototype object and generates the Request object based on the extended Prototype when processing the request.

// app/extend/request.js
module.exports = {
  get foo() {
    return this.get('x-request-foo'); }};Copy the code

Response

The Response object, like Koa’s Response object, is request-level and provides a number of response-related properties and methods for use.

access

ctx.response

Many properties and methods on CTX are proxied to the Response object. It is equivalent to access these properties and methods using CTX and Response. For example, ctx.status = 404 is equivalent to ctx.response.status = 404.

scaling

The framework will merge the object defined in app/extend/ Response.js with the prototype object of built-in Response and generate a Response object based on the extended Prototype when processing requests.

// app/extend/response.js
module.exports = {
  set foo(value) {
    this.set('x-response-foo', value); }};Copy the code

Foo = ‘bar’; this.response.foo = ‘bar’;

Helper

The Helper functions are used to provide utility functions.

What it does is it allows you to separate common actions from helper.js into separate functions, so that you can write complex logic in JavaScript without the logic being scattered around. Another benefit is that Helper, a simple function, makes it easier to write test cases.

There are some common Helper functions built into the framework. We can also write custom Helper functions.

access

Access helper objects through ctx.helper, for example:

// Assume that the home router is defined in app/router.js
app.get('home'.'/'.'home.index');

// Use helper calculation to specify url path
ctx.helper.pathFor('home', { by: 'recent'.limit: 20 })
/ / = > /? by=recent&limit=20
Copy the code

scaling

The framework merges the objects defined in app/extend/helper.js with the prototype object of the built-in helper and generates helper objects based on the extended Prototype when processing requests.

// app/extend/helper.js
module.exports = {
  foo(param) {
    // This is a helper object from which other helper methods can be called
    // this.ctx => context object
    // this.app => Application object}};Copy the code

Enabling Customization

The framework provides a unified entry file (app.js) to customize the Boot process. This file returns a Boot class, and we can initialize the Boot application process by defining the lifecycle method in the Boot class.

The framework provides these lifecycle functions for developers to work with:

  • The configuration file is about to load, and this is the last time to dynamically modify the configuration (configWillLoad)
  • The configuration file is loaded (configDidLoad)
  • The file is loaded (didLoad)
  • Plug-in started (willReady)
  • The worker is ready (didReady)
  • Application startup completed (serverDidReady)
  • Application is about to close (beforeClose)
// app.js
class AppBootHook {
  constructor(app) {
    this.app = app;
  }

  configWillLoad() {
    // The config file has been read and merged, but has not yet taken effect
    // This is the last time for the application layer to modify the configuration
    // Note: this function only supports synchronous calls

    For example, the password in the parameter is encrypted and can be decrypted here
    this.app.config.mysql.password = decrypt(this.app.config.mysql.password);
    For example, insert a piece of middleware between the framework's coreMiddleware
    const statusIdx = this.app.config.coreMiddleware.indexOf('status');
    this.app.config.coreMiddleware.splice(statusIdx + 1.0.'limit');
  }

  async didLoad() {
    // All configurations have been loaded
    // It can be used to load customized application files and start customized services

    // Example: Creating a custom application
    this.app.queue = new Queue(this.app.config.queue);
    await this.app.queue.init();

    // For example, load a customized directory
    this.app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', {
      fieldClass: 'tasksClasses'}); }async willReady() {
    // All plug-ins are started, but the application is not ready yet
    // You can perform some operations, such as data initialization, to start the application only after these operations succeed

    // For example, load data from database into memory cache
    this.app.cacheData = await this.app.model.query(QUERY_CACHE_SQL);
  }

  async didReady() {
    // The application has been started

    const ctx = await this.app.createAnonymousContext();
    await ctx.service.Biz.request();
  }

  async serverDidReady() {
    // The HTTP/HTTPS server is started to accept external requests
    // We can get the server instance from app.server

    this.app.server.on('timeout', socket => {
      // handle socket timeout}); }}module.exports = AppBootHook;
Copy the code

Application deployment

For local development, we use egg-bin dev to start the service, but this should not be used when deploying the application. Because egg-bin dev does a lot of processing for local development, production runs require a simpler, more stable approach.

The deployment of

Node.js must be installed on the server. The framework supports Node version >= 8.0.0.

Egg-cluster is built into the framework to start the Master process, which is stable enough to eliminate the need for pM2 daemon modules.

The framework also provides egg-scripts for running and stopping online environments.

 npm i egg-scripts --save
Copy the code
{
  "scripts": {
    "start": "egg-scripts start --daemon"."stop": "egg-scripts stop"}}Copy the code

This allows us to start or stop the application with the NPM start and NPM stop commands.

Start the command

egg-scripts start --port=7001 --daemon --title=egg-server-showcase
Copy the code

The following parameters are supported:

  • --port=7001Port number, which reads environment variables by defaultprocess.env.PORT, if not passed will use frame built-in port 7001.
  • --daemonWhether to allow background mode? Nonohup. If using Docker, it is recommended to run directly in the foreground.
  • --env=prodThe framework runs the environment, which reads environment variables by defaultprocess.env.EGG_SERVER_ENV, if not passed, the frame built-in environment prod will be used.
  • --workers=2Frame number of worker threads. By default, the number of app workers equal to the number of CPU cores will be created to make full use of CPU resources.
  • --title=egg-server-showcaseGrep is used to facilitate ps process. The default value is grepegg-server-${appname}.
  • --framework=yadanIf your application uses a custom framework, you can configure the egg.framework of package.json or specify this parameter.
  • --ignore-stderrIgnore errors reported during startup.
  • --https.keySpecify the full path to the key file required for HTTPS.
  • --https.certSpecify the full path to the certificate file required for HTTPS.

See the egg-scripts and egg-cluster documentation for more parameters.

Boot configuration item

// config/config.default.js

exports.cluster = {
  listen: {
    port: 7001.hostname: '127.0.0.1'.// path: '/var/run/egg.sock',}}Copy the code

Stop command

egg-scripts stop [--title=egg-server]
Copy the code

This command kills the master process and tells the worker and agent to exit gracefully.

–title=egg-server Kills specified egg applications, or terminates all egg applications if not passed.

The log

Powerful enterprise-class logging support is built into the framework, provided by the Egg-Logger module.

  • Log classification
  • Unified error log. All error level logs printed with.error() in the Logger are printed to a unified error log file for easy tracing
  • Startup logs are separated from run logs
  • Custom Logs
  • Multi-process log
  • Automatic cutting log
  • A high performance

Log path

  • All log files are placed by default${appInfo.root}/logs/${appInfo.name}Path, for example/home/admin/logs/example-app.
  • In both local development environments (env: local) and unittest environments (env: unittest), to avoid conflicts and centralized management, logs are printed in the logs directory under the project directory, for example/path/to/example-app/logs/example-app.

If you want to customize the log path:

// config/config.${env}.js
exports.logger = {
  dir: '/path/to/your/custom/log/dir'};Copy the code

Classification of log

There are several types of logging built into the framework, which are used in different scenarios:

  • appLogger ${appInfo.name}-web.log, e.g.example-app-web.log, application-related logs, which are used by developers. We use it in the vast majority of cases.
  • coreLogger egg-web.logFramework kernel, plug-in log.
  • errorLogger common-error.logIt is not used directly in practice, and any log output from logger.error () calls will be redirected to this point, with the emphasis on looking at this log to locate exceptions.
  • agentLogger egg-agent.logAgent process logs are printed here by the framework and plug-ins that use the Agent process to perform tasks.

If you want to customize the above log file names, you can override the default values in the config file:

// config/config.${env}.js
module.exports = appInfo= > {
  return {
    logger: {
      appLogName: `${appInfo.name}-web.log`.coreLogName: 'egg-web.log'.agentLogName: 'egg-agent.log'.errorLogName: 'common-error.log',}}; };Copy the code

How to Print Logs

Context Logger

Logs are used to record Web behaviors.

Each line of the log automatically records the basic information about the current request, such as [$userId/$IP /$traceId/${cost}ms $method $URL].

ctx.logger.debug('debug info');
ctx.logger.info('some request data: %j', ctx.request.body);
ctx.logger.warn('WARNNING!!!! ');

// errorLog. The complete stack information of error logs is directly recorded and output to errorLog
// In order to be traceable, it is necessary to ensure that all exceptions thrown are of the Error type, because only the Error type will carry stack information to locate the problem.
ctx.logger.error(new Error('whoops'));
Copy the code

The Context Logger that framework developers and plug-in developers use is also ctx.coreLogger.

App Logger

If we want to do some application-level logging, such as recording data during startup, we can do this through the App Logger.

// app.js
module.exports = app= > {
  app.logger.debug('debug info');
  app.logger.info('Startup time %d ms'.Date.now() - start);
  app.logger.warn('warning! ');

  app.logger.error(someErrorObj);
};
Copy the code

The App Logger that framework and plug-in developers will use is also app.coreLogger.

// app.js
module.exports = app= > {
  app.coreLogger.info('Startup time %d ms'.Date.now() - start);
};
Copy the code

Agent Logger

When developing frameworks and plug-ins, you will sometimes need to run code in the Agent process, using agent.coreLogger.

// agent.js
module.exports = agent= > {
  agent.logger.debug('debug info');
  agent.logger.info('Startup time %d ms'.Date.now() - start);
  agent.logger.warn('warning! ');

  agent.logger.error(someErrorObj);
};
Copy the code

Log file encoding

The default encoding is UTF-8, which can be overwritten in the following ways:

// config/config.${env}.js
exports.logger = {
  encoding: 'gbk'};Copy the code

Log file format

// config/config.${env}.js
exports.logger = {
  outputJSON: true};Copy the code

The level of logging

Logs are classified into NONE, DEBUG, INFO, WARN, and ERROR levels.

Logs are printed to a file and also to a terminal for easy development.

File Log Level

By default, only INFO and above logs (WARN and ERROR) are output to the file.

Print logs of all levels to a file:

// config/config.${env}.js
exports.logger = {
  level: 'DEBUG'};Copy the code

Disable all logs printed to a file:

// config/config.${env}.js
exports.logger = {
  level: 'NONE'};Copy the code

Debug logs are generated in the production environment

To avoid performance problems caused by printing DEBUG logs of some plug-ins in the production environment, the production environment does not print DEBUG logs by default. If you need to print DEBUG logs in the production environment, enable the allowDebugAtProd configuration item.

// config/config.prod.js
exports.logger = {
  level: 'DEBUG'.allowDebugAtProd: true};Copy the code

Terminal Log Level

By default, only INFO and above logs (WARN and ERROR) are output to the terminal. (Note: these logs are only printed to terminals in local and UNITTest environments by default)

Logger. consoleLevel: Level of output logs to the terminal. The default value is INFO

Printing logs of all levels to terminals:

// config/config.${env}.js
exports.logger = {
  consoleLevel: 'DEBUG'};Copy the code

Disable all logs printed to the terminal:

// config/config.${env}.js
exports.logger = {
  consoleLevel: 'NONE'};Copy the code

To ensure performance, terminal log output is disabled by default in a formal environment. If necessary, you can enable it through the following configuration. (Not recommended)

// config/config.${env}.js
exports.logger = {
  disableConsoleAfterReady: false};Copy the code

Log cutting

Framework support for log cutting is provided by the egg-Logrotator plug-in.

According to the days of cutting

This is the framework’s default log cutting method, which is performed at 00:00 every day according to the.log.YYYY-MM-DD file name.

Log is currently written to appLog. At 00:00 am, logs generated in the past day are cut into separate files in the format of example-app-web.log. yyyY-MM-DD.

Cut by file size

// config/config.${env}.js
const path = require('path');

module.exports = appInfo= > {
  return {
    logrotator: {
      filesRotateBySize: [
        path.join(appInfo.root, 'logs', appInfo.name, 'egg-web.log')],maxFileSize: 2 * 1024 * 1024 * 1024,}}; };Copy the code

Cut by the hour

This is very similar to the default cut by day, only the time is shortened to hour.

// config/config.${env}.js
const path = require('path');

module.exports = appInfo= > {
  return {
    logrotator: {
      filesRotateByHour: [
        path.join(appInfo.root, 'logs', appInfo.name, 'common-error.log'),]}}; };Copy the code

performance

Generally, Web access is frequently accessed. Writing logs to disks every time causes frequent DISK I/OS. To improve performance, we use the following file log writing policies:

Logs are written to the memory synchronously and flushed asynchronously at intervals (1 second by default)

See egg-Logger and egg-logrotator for more details.

HttpClient

Framework based on urllib built-in implementation of an HttpClient, applications can be very easy to complete any HTTP request.

Use HttpClient via app

HttpClient is automatically initialized to app.httpClient during application initialization. Curl (url, options), which is equivalent to app.httpClient.request (URL, options).

// app.js
module.exports = app= > {
  app.beforeStart(async() = > {/ / example: start time to read the version information for https://registry.npm.taobao.org/egg/latest
    const result = await app.curl('https://registry.npm.taobao.org/egg/latest', {
      dataType: 'json'}); app.logger.info('Egg latest version: %s', result.data.version);
  });
};
Copy the code

Use HttpClient through CTX

The framework also provides ctx.curl(URL, options) and ctx. httpClient in the Context, keeping the same experience as in the app. This makes it very convenient to use the ctx.curl() method to complete an HTTP request where the Context is available (for example, in a controller).

// app/controller/npm.js
class NpmController extends Controller {
  async index() {
    const ctx = this.ctx;

    // Example: Request an NPM module information
    const result = await ctx.curl('https://registry.npm.taobao.org/egg/latest', {
      // Automatically parse JSON response
      dataType: 'json'.// 3 seconds out
      timeout: 3000}); ctx.body = {status: result.status,
      headers: result.headers,
      package: result.data, }; }}Copy the code

Basic HTTP request

GET

// app/controller/npm.js
class NpmController extends Controller {
  async get() {
    const ctx = this.ctx;
    const result = await ctx.curl('https://httpbin.org/get?foo=bar'); ctx.status = result.status; ctx.set(result.headers); ctx.body = result.data; }}Copy the code

POST

const result = await ctx.curl('https://httpbin.org/post', {
  // Method must be specified
  method: 'POST'.// Tells HttpClient to send it in JSON format via contentType
  contentType: 'json'.data: {
    hello: 'world'.now: Date.now(),
  },
  // Explicitly tells HttpClient to process the returned response body in JSON format
  dataType: 'json'});Copy the code

PUT

const result = await ctx.curl('https://httpbin.org/put', {
  // Method must be specified
  method: 'PUT'.// Tells HttpClient to send it in JSON format via contentType
  contentType: 'json'.data: {
    update: 'foo bar',},// Explicitly tell HttpClient to process the response body in JSON format
  dataType: 'json'});Copy the code

DELETE

const result = await ctx.curl('https://httpbin.org/delete', {
  // Method must be specified
  method: 'DELETE'.// Explicitly tell HttpClient to process the response body in JSON format
  dataType: 'json'});Copy the code

Options Parameter description

httpclient.request(url, options)

The HttpClient default global configuration can be overridden by using config/config.default.js.

The commonly used

  • data: ObjectIf the request data needs to be sent, the system automatically selects the correct data processing method based on method.
    • GET.HEADThrough:querystring.stringify(data)The processing is then concatenated to the QUERY parameter of the URL.
    • POST.PUTDELETEEtc. : Need basiscontentTypeMake further judgment processing.
      • contentType = jsonThrough:JSON.stringify(data)Process, and set it to send as body.
      • Others: Passedquerystring.stringify(data)Process, and set it to send as body.
  • files: Mixed
  • method: StringSet the request method, default is GET. All HTTP methods, including GET, POST, PUT, DELETE, and PATCH, are supported.
  • contentType: StringSet the request data format, default is undefined, HttpClient automatically set based on data and content parameters.When data is object, the default setting is form. Support JSON format.
  • dataType: StringSet the format of the response data. By default, no processing is done on the response data, and the original buffer format is returned. Supports text and JSON formats.
  • headers: ObjectCustom request headers.
  • timeout: Number|ArrayRequest timeout, default is[5000, 5000]That is, the timeout for creating a connection is 5 seconds, and the timeout for receiving a response is 5 seconds.

Debug support (CTX. Curl)

If you need to debug HttpClient requests, you can add the following configuration to config.local.js:

// config.local.js
module.exports = (a)= > {
  const config = {};

  // add http_proxy to httpclient
  if (process.env.http_proxy) {
    config.httpclient = {
      request: {
        enableProxy: true.rejectUnauthorized: false.proxy: process.env.http_proxy,
      },
    };
  }

  return config;
}
Copy the code

Then launch your grab tool, such as Charles or Fiddler.

Finally, start the application with the following command:

Http_proxy = http://127.0.0.1:8888 NPM run devCopy the code

On Windows you can use cmder or Git bash

setHttp_proxy =http://127.0.0.1:8888 && NPM run devCopy the code

All requests that pass through HttpClient can be viewed in your packet capture tool.