Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

log4js #91

Open
uniquejava opened this issue Apr 27, 2017 · 2 comments
Open

log4js #91

uniquejava opened this issue Apr 27, 2017 · 2 comments

Comments

@uniquejava
Copy link
Owner

uniquejava commented Apr 27, 2017

本文针对目前最新的log4js 2.x系列

2.x内置了对cluster的支持(不必写任何代码), 并且作者说2.x的性能更好(特别是file appender).

安装

npm install log4js --save

输出

[2017-09-17 12:56:44.378] [INFO] startup - Listening on port 3000
[2017-09-17 12:56:49.029] [INFO] console - This is from console.log
[2017-09-17 12:56:49.339] [WARN] http - GET /admin/import

第一行是通过logger.info打印出来的, 第二行是console.log, 第三行是expresss.js内部打出来的access log. 稍后将解释怎么处理这三种日志.

最小用法

var log4js = require('log4js');
var logger = log4js.getLogger();
logger.level = 'debug'; // default level is OFF - which means no logs at all.
logger.debug("Some debug messages");

两种Configure方式

我喜欢第二种, 就像java中的log4j有log4j.xml或log4j.properties.

// 方式一
log4js.configure({
  appenders: { console: { type: 'console' } },
  categories: { default: { appenders: [ 'console' ], level: 'info' } }
});

// 方式二
log4js.configure('./config/log4js.json');

最小配置

见: Console Appender, 作者不推荐使用console, (因为它的内部实现全部是用console.log的形式输出的), 推荐使用stdout, 性能更好.
如果用stdout, 如下:

log4js.configure({
  appenders: { 'out': { type: 'stdout' } },
  categories: { default: { appenders: ['out'], level: 'info' } }
});

中等配置

{
  "appenders": {
    "console": { "type": "console" },
    "cheeseLogs": { "type": "file", "filename": "logs/cheese.log" },
    "no-debugs": { "type": "logLevelFilter", "appender":"cheeseLogs", "level": "info"}
  },
  "categories": {
    "cheese": { "appenders": ["cheeseLogs"], "level": "info" },
    "another": { "appenders": ["console"], "level": "debug" },
    "default": { "appenders": ["console", "no-debugs"], "level": "debug"}
  }
}

以中等配置为例, 理解一下最前面输出中的startup, console, http都是些什么. (这些都是日志的category), 那么在log4j.json中定义的是cheese, another, default这三种啊. (哦, log4js.getLogger("startup");这个参数传什么名字, 日志中就打印出来什么), 如果传的category在log4j.json中没有定义, 就会用default那个categories对应的配置. (明白了)

另外在categories中定义了每类日志的输出 level, 小于这个level的日志将不予处理.

在appenders中也可以定义level, 但是只有type为logLevelFilter的appender才能定义level.

并且logLevelFilter类型的appender只能基于某个已存在的appender.

以上中等配置中. 如果项目中用console.debug('this is some debug message');, 那么这句话只会出现在控制台, 并不会出现在日志文件中. (这是因为虽然总体的输出level为debug, 所以console类型的appender会打印出这句话, 但是no-debugs(名字乱取的)这个类型的appender限制了该类型的appender只输出info级别的日志)

startup
在./bin/www中, 有如下代码

/**
 * Initialise log4js first, so we don't miss any log messages
 */
const log4js = require('log4js');
log4js.configure('./config/log4js.json');

const log = log4js.getLogger("startup");
log.info("Listing on port 3000");

其中log4js.getLogger(CATEGORY_NAME); 这是新建一个logger的实例, 会自动去找配置中categories中同名的category, 如果找不到就用default那个category, 比如这里的startup并不存在, 所以实际用的default category.

自动处理console.log/console.error等

项目中大家都习惯了用console.log来记录日志 , 如何不更改已有的代码, 让log4js自动接手console.log?
作者在FAQ中有回答这个问题.

/**
 * Initialise log4js first, so we don't miss any log messages
 */
const log4js = require('log4js');
log4js.configure('./config/log4js.json');

const log = log4js.getLogger("startup");

// see https://nomiddlename.github.io/log4js-node/faq.html
const consoleLogger = log4js.getLogger('console');
console.debug = consoleLogger.debug.bind(consoleLogger);
console.log = consoleLogger.info.bind(consoleLogger);
console.error = consoleLogger.error.bind(consoleLogger);

如何处理express.js自带的access log

这次从./bin/www转移到app.js中来.

var log4js = require('log4js');
var express = require('express');

//We won't need this.
//var logger = require('morgan');
var log = log4js.getLogger("app");

// replace this with the log4js connect-logger
// app.use(logger('dev'));
app.use(log4js.connectLogger(log4js.getLogger("http"), {level: 'auto', format: ':method :url', nolog: '\\.js|\\.css|\\.png'}));

看代码中的注释, 我们可以用npm uninstall morgan --save卸载掉express generator包含的morgan组件. 因为在以上的代码中我们用log4js(而非morgan)接管了connect-logger.
auto, format及nolog的含义见: Connect / Express Logger

cyper实战

经过权衡, 我暂时配置了如下这款, 除了一点: 不知道怎么去掉时间的毫秒数, 查了文档, 对%d没有像log4j那样提供更精细的控制, 其它都足够满意.

{
  "appenders": {
    "stdout": {
      "type": "stdout",
      "layout": {
        "type": "pattern",
        "pattern": "[%d %p] %m"
      }
    },
    "daily": {
      "type": "dateFile",
      "filename": "logs/app",
      "pattern": ".yyyy-MM-dd.log",
      "alwaysIncludePattern": true,
      "compress": true,
      "daysToKeep": 30,
      "layout": {
        "type": "pattern",
        "pattern": "[%d %p] %m"
      }
    },
    "file": {
      "type": "file",
      "filename": "logs/error.log",
      "maxLogSize": 10485760,
      "backups": 3,
      "compress": true
    },
    "error": {
      "type": "logLevelFilter",
      "appender": "file",
      "level": "error"
    }
  },
  "categories": {
    "default": {
      "appenders": [
        "stdout",
        "daily",
        "error"
      ],
      "level": "info"
    }
  }
}

同时, 我会在开发模式下, 把日志级别动态调整为debug, 在./bin/www中还添加了如下代码:

/**
 * Initialise log4js first, so we don't miss any log messages
 */
const log4js = require('log4js');
log4js.configure('./config/log4js.json');

const logger = log4js.getLogger("startup");

const consoleLogger = log4js.getLogger('console');
console.debug = consoleLogger.debug.bind(consoleLogger);
console.log = consoleLogger.info.bind(consoleLogger);
console.error = consoleLogger.error.bind(consoleLogger);

logger.info('env=', app.get('env'));

if(app.get('env') === 'development') {
  logger.level = 'debug';
  consoleLogger.level = 'debug';
}

会打印出如下格式的日志:

[2017-09-18 00:32:33.578 INFO] env= development
[2017-09-18 00:32:33.584 INFO] Listening on port 3000
@uniquejava
Copy link
Owner Author

uniquejava commented Sep 17, 2017

log4js 1.x

log4js的1.x版本和最新的2.x版本变化很大, 以下都是1.x时的笔记, 仅供参考.

1.x的能找到一篇不错的博客: http://www.lkhweb.com/node-js-zhi-log4js-wan-quan-jiang-jie/

简单

简单版中要用logger.info才能将日志打印到文件中, 使用console.log还是只会打印在console, 不知道是哪里设置不对, cluster版没有问题, 使用console.log/info/error都会打印到日志文件.

原因: 作者去掉了对replaceConsole的支持, 见https://nomiddlename.github.io/log4js-node/faq.html

log.js

var log4js = require('log4js');
log4js.configure({
    appenders: [
        {type: "console"},
        {
            type: "dateFile",
            filename: 'logs/wss.log',
            pattern: "_yyyy-MM-dd.log",
            category: 'normal'
        }
    ],
    levels: {
        "[all]": "INFO"
    },
    replaceConsole: true
});

var logger = log4js.getLogger('normal');
exports.logger = logger;

exports.use = function (app) {
    app.use(log4js.connectLogger(logger, {level: log4js.levels.INFO, format: ':method :url'}));
};

在app.js中use

var log = require('./log');
var logger = log.logger;

var app = express();

//日志
log.use(app);

其它地方使用

var logger = require('./log').logger;
logger.info('hello world');

cluster版

./bin/www

var app = require('../app');
var debug = require('debug')('wefact:server');
var http = require('http');
var cluster = require('cluster');
var cpuNums = require('os').cpus().length;
var util = require('util');
var log4js = require('log4js');
var env = process.env.NODE_ENV || 'development';
/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);


var appWrap = function (worker, app) {
    return function (req, res, next) {
        if (!req.url.startsWith('/assets/')) {
            console.log(util.format('WorkerId:%d, url:%s', worker.id, req.url));
        }
        app(req, res, next);
    }
}

if (cluster.isMaster) {
    // configuration for log
    log4js.configure({
        appenders: [
            {
                type: "clustered",
                appenders: [
                    {type: 'dateFile', filename: "logs/xxxxx.log", "pattern": "-yyyy-MM-dd.log"}
                ]
            }
        ],
        replaceConsole: true
    });

    for (var i = 0; i < cpuNums; i++) {
        cluster.fork();
    }
    cluster.on('exit', function (worker, exitCode, signal) {
        console.log(util.format('Worker-%d Exit', worker.id));
        cluster.fork();
    });
} else {
    var myApp = appWrap(cluster.worker, app);

    /**
     * Create HTTP server.
     */

    var server = http.createServer(myApp);
    // Init worker loggers, adding only the clustered appender here.
    var appenders = [{type: "clustered"}];

    if (env === 'development') {
        appenders.unshift({type: "console"});
    }
    log4js.configure({
        appenders: appenders,
        replaceConsole: true
    });
    var logger = log4js.getLogger('app');
    logger.setLevel('INFO');
    app.use(log4js.connectLogger(logger, {level: log4js.levels.INFO}));

    /**
     * Listen on provided port, on all network interfaces.
     */

    server.listen(port);
    server.on('error', onError);
    server.on('listening', onListening);

    console.log(util.format('Worker-%d Listening On Port %d', cluster.worker.id, port));

    process.on('uncaughtException', function (err) {
        console.error("Uncaught exception", err.message);
        console.error(err.stack);
        process.exit(1);
    });
}

@uniquejava
Copy link
Owner Author

在页面上查看logs

我设置了个取日志文件列表, 以及查看日志内容的工具类models/Logs.js, 如下

var Q = require('q');
var fs = require('fs');
var path = require('path');
var moment = require('moment');
var LOGS_PATH = '../logs/';

module.exports.getLog = getLog;

/**
 * return {names: ['error.log', 'app.yyyy-MM-dd.log', ..], data: 'log file content for that specific filename'}
 * @param date an optional string yyyy-MM-dd
 */
function getLog(fileName) {
  var d = Q.defer();
  var logDir = path.join(__dirname, LOGS_PATH);
  fileName = fileName || ('app.' + moment().format('YYYY-MM-DD') + '.log');

  // list all log file names
  fs.readdir(logDir, 'utf8', function (err, files) {
    if (err) {
      d.resolve({names: [], fileName: fileName, content: err});
    } else {
      var names = [];
      files.forEach(function (name) {
        if (name.endsWith('.log')) {
          names.push(name);
        }
      });

      // user only allowed to access .log file within the logs directory
      if (!fileName.endsWith('.log') || fileName.indexOf("/") !== -1) {
        d.resolve({names: names, fileName: fileName, content: "Illegal log file name: " + fileName});

      } else {
        // get specified file content
        var logFile = path.join(__dirname, LOGS_PATH, fileName);
        fs.readFile(logFile, 'utf8', function (err, data) {
          if (err) {
            d.resolve({names: names, fileName: fileName, content: err});
          } else {
            d.resolve({names: names, fileName: fileName, content: data});
          }
        });
      }


    }
  });

  return d.promise;
}

在routes中这样使用:

router.get('/logs', function (req, res, next) {
  var fileName = req.query.fileName;
  Logs.getLog(fileName).then(function (log) {
    res.render('admin/logs', {log: log});
  }, function (error) {
    res.render('admin/logs', {log: error.message});
  });

});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant