title: Logger

There is no doubt that logs are important part for monitoring application and debugging in Web development.

Built-in enterprise scaled logger, egg-logger, makes developer implement logging more easily than ever before.

Core features:

  • Levels
  • Universal logging, .error() will save ERROR level logs into a file for later debugging
  • Logs from dispatch and runtime are separated
  • Create your logger
  • Multi-process logs
  • Automatic sharding
  • High Performance

Location

  • Log files are located in ${appInfo.root}/logs/${appInfo.name} by default. For instance, /home/admin/logs/example-app.
  • To avoid conflicts between environments and provide a more convenience way to manage logs, log files will be written into logs directory. For instance, /path/to/example-app/logs/example-app.

Change dir in logger:

  1. // config/config.${env}.js
  2. exports.logger = {
  3. dir: '/path/to/your/custom/log/dir',
  4. };

Type of Logs

Egg offers a few loggers for different scenarios:

  • appLogger ${appInfo.name}-web.log,for example example-app-web.log stores logs from application, it will be used in general.
  • coreLogger egg-web.log, logs from Egg’s core and plugin.
  • errorLogger common-error.log should not be invoked directly. However, .error() in every logger will redirect logs to it for debugging.
  • agentLogger egg-agent.log, logs from agent process.

If you want to change names of above loggers, you can override them in config:

  1. // config/config.${env}.js
  2. module.exports = appInfo => {
  3. return {
  4. logger: {
  5. appLogName: `${appInfo.name}-web.log`,
  6. coreLogName: 'egg-web.log',
  7. agentLogName: 'egg-agent.log',
  8. errorLogName: 'common-error.log',
  9. },
  10. };
  11. };

Printing

Context Logger

It’s proper to log details in requests with context logger. The logger will append basics about requests to each log. For example, [$userId/$ip/$traceId/${cost}ms $method $url].

  1. ctx.logger.debug('debug info');
  2. ctx.logger.info('some request data: %j', ctx.request.body);
  3. ctx.logger.warn('WARNING!!!!');
  4. // .error will save information in call stack into errorLog file.
  5. // Exceptions must be guaranteed to be Error or object extended from Error, which offers a trace of what functions were called.
  6. ctx.logger.error(new Error('whoops'));

For developers who create frameworks or plugins, ctx.coreLogger is another option in Context Logger.

  1. ctx.coreLogger.info('info');

App Logger

For developers who want to know more details about dispatch in Egg, they can easily use App Logger to make that happen:

  1. // app.js
  2. module.exports = app => {
  3. app.logger.debug('debug info');
  4. app.logger.info('Latency: %d ms', Date.now() - start);
  5. app.logger.warn('warning!');
  6. app.logger.error(someErrorObj);
  7. };

app.coreLogger in app is similar to ctx.coreLogger in context:

  1. // app.js
  2. module.exports = app => {
  3. app.coreLogger.info('Latency: %d ms', Date.now() - start);
  4. };

Agent Logger

Agent also supports agent.coreLogger as the same feature to context and app above.

  1. // agent.js
  2. module.exports = agent => {
  3. agent.logger.debug('debug info');
  4. agent.logger.info('Latency: %d ms', Date.now() - start);
  5. agent.logger.warn('warning!');
  6. agent.logger.error(someErrorObj);
  7. };

For more about Agent, you can take a look at Multi-process.

Encoding

The default encoding setting(utf-8) can be changed via encoding in config:

  1. // config/config.${env}.js
  2. exports.logger = {
  3. encoding: 'gbk',
  4. };

Log Level

Logs are designed in 5 levels, including NONE, DEBUG, INFO, WARN and ERROR. For inspecting in development, they will also be written into files and printed into terminal as well.

Levels

Generally, Egg will only write logs in levels higher than INFO, so it means that NONE and DEBUG information will be lost in files.

If you want to change the level of the logger, you can make it as follow:

  1. // config/config.${env}.js
  2. exports.logger = {
  3. level: 'DEBUG', // logs in all level will be written into files
  4. };

Stop writing logs in all levels:

  1. // config/config.${env}.js
  2. exports.logger = {
  3. level: 'NONE',
  4. };

DEBUG Log in Prodction Environment

To avoid some plugin’s DEBUG logs printing in the production environment causing performance problems, the production environment prohibits printing DEBUG-level logs by default. If there is a need to print DEBUG logs for debugging in the production environment, you need to set allowDebugAtProd configuration to ture.

  1. // config/config.prod.js
  2. exports.logger = {
  3. level: 'DEBUG',
  4. allowDebugAtProd: true,
  5. };

In terminal

By default, Egg will only print out INFO, WARN and ERROR in terminal. logger.consoleLevel(default: INFO) is defined as the logger level in terminal. Similarly, it can be changed as following:

Print logs in all levels:

  1. // config/config.${env}.js
  2. exports.logger = {
  3. consoleLevel: 'DEBUG',
  4. };

Stop printing logs in all levels:

  1. // config/config.${env}.js
  2. exports.logger = {
  3. consoleLevel: 'NONE',
  4. };

Create your logger

Customized

For common scenarios, it’s unnecessary to create new logger, because too many loggers will make them hard to be managed for later debugging.

The logger you create can be declared in config:

  1. // config/config.${env}.js
  2. const path = require('path');
  3. module.exports = appInfo => {
  4. return {
  5. customLogger: {
  6. xxLogger: {
  7. file: path.join(appInfo.root, 'logs/xx.log'),
  8. },
  9. },
  10. };
  11. };

Now, you can get loggers via app.getLogger('xxLogger') or ctx.getLogger('xxLogger'), and the logs printed from those loggers are similar to the ones from coreLogger.

Advanced

Logs will be written into files by default. Further, they will also be printed into terminal in development. But what if we need to print those into another place? Creating customized transport can take you there.

Transport can be considered as a tunnel to transfer data in Egg. A logger contains multiple transports, for example the one by default contains fileTransport and consoleTransport.

For concrete scenario, we take common-error.log as an example, which not only printed into files, but also sent to another remote service. At first, we can create a new transport for sending logs to remote:

  1. const co = require('co');
  2. const util = require('util');
  3. const Transport = require('egg-logger').Transport;
  4. class RemoteErrorTransport extends Transport {
  5. // Create log() to upload logs
  6. log(level, args) {
  7. let log;
  8. if (args[0] instanceof Error) {
  9. const err = args[0];
  10. log = util.format('%s: %s\n%s\npid: %s\n', err.name, err.message, err.stack, process.pid);
  11. } else {
  12. log = util.format(...args);
  13. }
  14. this.options.app.curl('http://url/to/remote/error/log/service/logs', {
  15. data: log,
  16. method: 'POST',
  17. }).catch(console.error);
  18. }
  19. }
  20. // Transport attached to errorLogger in app.js, makes logs sync to it once those are created.
  21. app.getLogger('errorLogger').set('remote', new RemoteErrorTransport({ level: 'ERROR', app }));

Performance is what we always consider as important part in our services so that logs will firstly be written into memory and transferred to remote later.

Log Sharding

One common requirement you can find in enterprise logs is automatic log sharding, which offers a convenient way for management. Luckily, Egg takes egg-logrotator as built-in solution to meet the need.

Daily Sharding

This is the default way in Egg to cut the logs into files named by .log.YYYY-MM-DD at every 00:00. For example, example-app-web.log will be cut into files as follow, example-app-web.log.YYYY-MM-DD.

Size Sharding

The log file also can be cut into ones by size. For example, Egg will process egg-web.log when its size reach 2G:

  1. // config/config.${env}.js
  2. const path = require('path');
  3. module.exports = appInfo => {
  4. return {
  5. logrotator: {
  6. filesRotateBySize: [
  7. path.join(appInfo.root, 'logs', appInfo.name, 'egg-web.log'),
  8. ],
  9. maxFileSize: 2 * 1024 * 1024 * 1024,
  10. },
  11. };
  12. };

Logs written into filesRotateBySize file will never be processed again by date.

Hourly Sharding

There is another option that the log files can be divided into small ones by hour.

For example, we need to cut common-error.log by hour just like following implementation.

  1. // config/config.${env}.js
  2. const path = require('path');
  3. module.exports = appInfo => {
  4. return {
  5. logrotator: {
  6. filesRotateByHour: [
  7. path.join(appInfo.root, 'logs', appInfo.name, 'common-error.log'),
  8. ],
  9. },
  10. };
  11. };

Logs written into filesRotateByHour file will never be processed again by date.

Performance

Generally, requests are frequent events to Web services, so writing logs into disk after each event will cause more unexpected I/O. Egg takes following strategy to write logs:

Logs will be firstly transferred into memory, and then Egg will asynchronously write them into files by second.

More about egg-logger and egg-logrotator