Open uniquejava opened 7 years ago
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');
./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);
});
}
我设置了个取日志文件列表, 以及查看日志内容的工具类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});
});
});
本文针对目前最新的log4js 2.x系列
2.x内置了对cluster的支持(不必写任何代码), 并且作者说2.x的性能更好(特别是file appender).
安装
输出
第一行是通过logger.info打印出来的, 第二行是console.log, 第三行是expresss.js内部打出来的access log. 稍后将解释怎么处理这三种日志.
最小用法
两种Configure方式
我喜欢第二种, 就像java中的log4j有log4j.xml或log4j.properties.
最小配置
见: Console Appender, 作者不推荐使用console, (因为它的内部实现全部是用console.log的形式输出的), 推荐使用stdout, 性能更好. 如果用stdout, 如下:
中等配置
以中等配置为例, 理解一下最前面输出中的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中, 有如下代码
其中
log4js.getLogger(CATEGORY_NAME);
这是新建一个logger的实例, 会自动去找配置中categories中同名的category, 如果找不到就用default那个category, 比如这里的startup并不存在, 所以实际用的default
category.自动处理console.log/console.error等
项目中大家都习惯了用console.log来记录日志 , 如何不更改已有的代码, 让log4js自动接手console.log? 作者在FAQ中有回答这个问题.
如何处理express.js自带的access log
这次从./bin/www转移到app.js中来.
看代码中的注释, 我们可以用
npm uninstall morgan --save
卸载掉express generator包含的morgan组件. 因为在以上的代码中我们用log4js(而非morgan)接管了connect-logger. auto, format及nolog的含义见: Connect / Express Loggercyper实战
经过权衡, 我暂时配置了如下这款, 除了一点: 不知道怎么去掉时间的毫秒数, 查了文档, 对%d没有像log4j那样提供更精细的控制, 其它都足够满意.
同时, 我会在开发模式下, 把日志级别动态调整为debug, 在./bin/www中还添加了如下代码:
会打印出如下格式的日志: