我有几个模块 - 比如说 server.js、module1.js、...、moduleN.js。
我想在我的 server.js 中定义日志文件:
winston.add(winston.transports.File, { filename: 'mylogfile.log' });
然后在我的所有模块中使用它。
最好的方法是什么?我可以exports.winston=winston;
在每个模块中,然后在 server.js 中设置它,但是有没有更好的解决方案?
先感谢您!
默认记录器概念很好地处理了这个问题。
Winston 定义了一个默认记录器,任何对 winston 的直接要求(和后续要求)都将检索该记录器。因此,您只需配置这个默认记录器一次,它就可以通过 vanilla require('winston') 在其经过调整的多传输模式下供后续模块使用。
例如,这是我完整的日志记录设置,它定义了 3 个传输。我有时将 Loggly 换成 MongoDB。
var logger=require('./log.js');
// requires winston and configures transports for winstons default logger- see code below.
var logger=require('winston'); // this retrieves default logger which was configured in log.js
logger.info("the default logger with my tricked out transports is rockin this module");
var logger = require('winston');
var Loggly = require('winston-loggly').Loggly;
var loggly_options={ subdomain: "mysubdomain", inputToken: "efake000-000d-000e-a000-xfakee000a00" }
logger.add(Loggly, loggly_options);
logger.add(winston.transports.File, { filename: "../logs/production.log" });
logger.info('Chill Winston, the logs are being captured 3 ways- console, file, and Loggly');
module.exports=logger;
或者,对于更复杂的场景,您可以使用 winston 容器并从其他模块中的命名容器中检索记录器。这个我没用过。
我唯一的问题是我的部署主机上缺少日志目录,这很容易修复。
希望这可以帮助。
我所做的(这可能不是最好的方法)是使用一个“全局”模块,在其中导出我通过我的应用程序使用的所有东西。例如:
//Define your winston instance
winston.add(winston.transports.File, { filename: 'mylogfile.log' });
exports.logger = winston;
exports.otherGlobals = ....
现在只需要从你的其他模块中使用这个全局使用的模块
var Global = require(/path/to/global.js);
因为文件在第一次加载后被缓存(您可以通过在全局中包含一条日志语句来验证;它只会记录一次),再次包含它的开销很小。将它们全部放入一个文件也比在每一页上要求所有全局使用的模块更容易。
我想使用自定义颜色和级别。
所以我删除了默认的控制台传输并设置了一个彩色的
这是我的logger.js
var logger = require('winston');
logger.setLevels({
debug:0,
info: 1,
silly:2,
warn: 3,
error:4,
});
logger.addColors({
debug: 'green',
info: 'cyan',
silly: 'magenta',
warn: 'yellow',
error: 'red'
});
logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, { level: 'debug', colorize:true });
module.exports = logger;
从 app.js 加载:
var logger = require('./lib/log.js');
从其他模块加载:
var logger = require('winston');
稍微偏离主题(正如 OP 询问温斯顿),但我喜欢 Bunyan 的“儿童记录器”方法:
var bunyan = require('bunyan');
var log = bunyan.createLogger({name: 'myapp'});
app.use(function(req, res, next) {
req.log = log.child({reqId: uuid()});
next();
});
app.get('/', function(req, res) {
req.log.info({user: ...});
});
它解决了 OP 的问题,因为记录器可通过 req 对象使用(因此每个模块中不需要“require(log)”)。此外,属于特定请求的所有日志条目都将具有将它们连接在一起的唯一 ID。
{"name":"myapp","hostname":"pwony-2","pid":14837,"level":30,"reqId":"XXXX-XX-XXXX","user":"...@gmail.com","time":"2014-05-26T18:27:43.530Z","v":0}
我不确定温斯顿是否也支持这一点。
我现在正在开发Winston 3.0.0。似乎配置默认记录器的方式发生了一些变化。对我有用的方式如下:
log.js // 全局记录器的设置
const winston= require('winston');
winston.configure({
level:"debug",
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
transports: [
new winston.transports.Console()
]
});
另一部分是一样的。在您应用程序的开头require('log.js')
,以及require ('winston'),
在所有其他文件中,只需require('winston')
.
我正在创建一个新的 Winston 记录器。
日志.js
'use strict';
const winston = require('winston');
module.exports = new(winston.Logger)({
transports: [
new(winston.transports.Console)({
level: 'info'
})
]
});
一个.js
const log = require('./log');
log.info("from a.js");
b.js
const log = require('./log');
log.info("from b.js");
这是我的记录器配置,winston 版本是3.2.1
.
它将日志存储在application.log
文件中并用于我正在使用的错误堆栈跟踪以及在错误情况下打印堆栈跟踪的函数中的errors({ stack: true })
小技巧。printf
const {format, transports} = require('winston');
const { timestamp, colorize, printf, errors } = format;
const { Console, File } = transports;
LoggerConfig = {
level: process.env.LOGGER_LEVEL || 'debug',
transports: [
new Console(),
new File({filename: 'application.log'})
],
format: format.combine(
errors({ stack: true }),
timestamp(),
colorize(),
printf(({ level, message, timestamp, stack }) => {
if (stack) {
// print log trace
return `${timestamp} ${level}: ${message} - ${stack}`;
}
return `${timestamp} ${level}: ${message}`;
}),
),
expressFormat: true, // Use the default Express/morgan request formatting
colorize: false, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red).
ignoreRoute: function (req, res) {
return false;
} // optional: allows to skip some log messages based on request and/or response
}
我express-winston
也在通用日志中使用相同的配置。我__logger
在全局声明了对象,这样您就不需要每次都在每个文件中导入。通常在节点 js 中,所有全局变量前缀都带有 2 个下划线(__
),因此最好遵循这一点。
服务器.js
const winston = require('winston');
const expressWinston = require('express-winston');
/**
* winston.Logger
* logger for specified log message like console.log
*/
global.__logger = winston.createLogger(LoggerConfig);
/**
* logger for every HTTP request comes to app
*/
app.use(expressWinston.logger(LoggerConfig));
__logger
是全球性的,因此您可以在任何地方使用它,例如:
blog.controller.js
function save(req, res) {
try {
__logger.debug('Blog add operation');
.
.
return res.send(blog);
} catch (error) {
__logger.error(error);
return res.status(500).send(error);
}
}
希望这会有所帮助!
我使用工厂函数并传入模块名称,以便可以将其添加到元数据中:
logger-factory.js
const path = require('path');
const { createLogger, format, transports } = require('winston');
const { combine, errors, timestamp } = format;
const baseFormat = combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
errors({ stack: true }),
format((info) => {
info.level = info.level.toUpperCase();
return info;
})(),
);
const splunkFormat = combine(
baseFormat,
format.json(),
);
const prettyFormat = combine(
baseFormat,
format.prettyPrint(),
);
const createCustomLogger = (moduleName) => createLogger({
level: process.env.LOG_LEVEL,
format: process.env.PRETTY_LOGS ? prettyFormat : splunkFormat,
defaultMeta: { module: path.basename(moduleName) },
transports: [
new transports.Console(),
],
});
module.exports = createCustomLogger;
app-harness.js(这样我就可以运行导出的索引模块)
const index = require('./index');
// https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html
const sampleEvent = {
"Records": [
{
"eventVersion": "2.1",
"eventSource": "aws:s3",
"awsRegion": "us-east-2",
"eventTime": "2019-09-03T19:37:27.192Z",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "AWS:AIDAINPONIXQXHT3IKHL2"
},
"requestParameters": {
"sourceIPAddress": "205.255.255.255"
},
"responseElements": {
"x-amz-request-id": "D82B88E5F771F645",
"x-amz-id-2": "vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo="
},
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "828aa6fc-f7b5-4305-8584-487c791949c1",
"bucket": {
"name": "lambda-artifacts-deafc19498e3f2df",
"ownerIdentity": {
"principalId": "A3I5XTEXAMAI3E"
},
"arn": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df"
},
"object": {
"key": "b21b84d653bb07b05b1e6b33684dc11b",
"size": 1305107,
"eTag": "b21b84d653bb07b05b1e6b33684dc11b",
"sequencer": "0C0F6F405D6ED209E1"
}
}
}
]
};
index.handler(sampleEvent)
.then(() => console.log('SUCCESS'))
.catch((_) => console.log('FAILURE'));
index.js
const logger = require('./logger-factory')(__filename);
const app = require('./app');
exports.handler = async function (event) {
try {
logger.debug('lambda triggered with event', { event });
await app.run(event);
logger.debug(`lambda finished`);
} catch(error) {
logger.error('lambda failed: ', error);
// rethrow the error up to AWS
throw error;
}
}
应用程序.js
const logger = require('./logger-factory')(__filename);
const run = async (event) => {
logger.info('processing S3 event', event);
try {
logger.info('reading s3 file')
// throws because I used "Record" instead of "Records"
const s3 = event.Record[0].s3;
// use s3 to read the file
} catch (error) {
logger.error('failed to read from S3: ', error);
throw error;
}
};
module.exports = { run };
当我在本地运行应用程序时WARN
:
~/repos/ghe/lambda-logging (master * u=)> LOG_LEVEL=warn node -r dotenv/config ./src/app-harness.js
{
module: 'app.js',
level: 'ERROR',
message: "failed to read from S3: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:34:06'
}
{
module: 'index.js',
level: 'ERROR',
message: "lambda failed: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:34:06'
}
当我在DEBUG
水平运行时:
~/repos/ghe/lambda-logging (master * u=)> LOG_LEVEL=debug node -r dotenv/config ./src/test-harness.js
{
module: 'index.js',
event: {
Records: [
{
eventVersion: '2.1',
eventSource: 'aws:s3',
awsRegion: 'us-east-2',
eventTime: '2019-09-03T19:37:27.192Z',
eventName: 'ObjectCreated:Put',
userIdentity: { principalId: 'AWS:AIDAINPONIXQXHT3IKHL2' },
requestParameters: { sourceIPAddress: '205.255.255.255' },
responseElements: {
'x-amz-request-id': 'D82B88E5F771F645',
'x-amz-id-2': 'vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo='
},
s3: {
s3SchemaVersion: '1.0',
configurationId: '828aa6fc-f7b5-4305-8584-487c791949c1',
bucket: {
name: 'lambda-artifacts-deafc19498e3f2df',
ownerIdentity: { principalId: 'A3I5XTEXAMAI3E' },
arn: 'arn:aws:s3:::lambda-artifacts-deafc19498e3f2df'
},
object: {
key: 'b21b84d653bb07b05b1e6b33684dc11b',
size: 1305107,
eTag: 'b21b84d653bb07b05b1e6b33684dc11b',
sequencer: '0C0F6F405D6ED209E1'
}
}
}
]
},
level: 'DEBUG',
message: 'lambda triggered with event',
timestamp: '2020-05-11 17:38:21'
}
{
module: 'app.js',
Records: [
{
eventVersion: '2.1',
eventSource: 'aws:s3',
awsRegion: 'us-east-2',
eventTime: '2019-09-03T19:37:27.192Z',
eventName: 'ObjectCreated:Put',
userIdentity: { principalId: 'AWS:AIDAINPONIXQXHT3IKHL2' },
requestParameters: { sourceIPAddress: '205.255.255.255' },
responseElements: {
'x-amz-request-id': 'D82B88E5F771F645',
'x-amz-id-2': 'vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo='
},
s3: {
s3SchemaVersion: '1.0',
configurationId: '828aa6fc-f7b5-4305-8584-487c791949c1',
bucket: {
name: 'lambda-artifacts-deafc19498e3f2df',
ownerIdentity: { principalId: 'A3I5XTEXAMAI3E' },
arn: 'arn:aws:s3:::lambda-artifacts-deafc19498e3f2df'
},
object: {
key: 'b21b84d653bb07b05b1e6b33684dc11b',
size: 1305107,
eTag: 'b21b84d653bb07b05b1e6b33684dc11b',
sequencer: '0C0F6F405D6ED209E1'
}
}
}
],
level: 'INFO',
message: 'processing S3 event',
timestamp: '2020-05-11 17:38:21'
}
{
message: 'reading s3 file',
level: 'INFO',
module: 'app.js',
timestamp: '2020-05-11 17:38:21'
}
{
module: 'app.js',
level: 'ERROR',
message: "failed to read from S3: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:38:21'
}
{
module: 'index.js',
level: 'ERROR',
message: "lambda failed: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:38:21'
}
如果你想让记录器成为一个全局变量 - 你必须专门通过将它分配给全局变量来做
logger.js
var winston = require('winston')
var winston = winston.createLogger({
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({
filename: './logs/logger.log'
})
]
});
module.exports=winston;
应用程序.js
let logger = require('./logger')
global.__logger = logger
someController.js
__logger.info('created log successfully')
注意:最好为每个全局变量分配一个前缀,这样您就会知道这是一个全局变量。我使用 __ 作为前缀(双低破折号)
只需创建 logger.js 并放入
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
transports: [
new winston.transports.Console()
]
});
module.exports = logger
然后你可以在任何地方请求和使用它,因为 logger 现在是单例的。
const logger = require('./utils/logger');
logger.info('Hello!');
如果需要,这甚至可以让您选择交换日志库。接受的答案是完全错误的,离意大利面条代码更近了一步。
在我的团队中,我们创建了一个包含所有默认配置的私有 npm 包(如您在之前的答案中所示)
我只有一个问题:将记录器对象声明为全局对象以避免在每个模块中导入是一种好习惯吗?