如何在几个模块中使用 Winston?

Posted

技术标签:

【中文标题】如何在几个模块中使用 Winston?【英文标题】:How to use Winston in several modules? 【发布时间】:2013-01-09 23:21:31 【问题描述】:

我有几个模块 - 比如说 server.js、module1.js、...、moduleN.js。

我想在 server.js 中定义日志文件:

winston.add(winston.transports.File,  filename: 'mylogfile.log' );

然后在我的所有模块中使用它。

最好的方法是什么?我可以在每个模块中exports.winston=winston;,然后在server.js中设置,但是有没有更好的解决方案?

提前谢谢你!

【问题讨论】:

简短的回答是:Node.js 模块的行为类似于单例(除了一些“奇怪”的情况 medium.com/@lazlojuly/…)。所以 'winston' 可以在入口点文件(例如 server.js)中被要求和配置,然后在任何其他 module.js 中被要求和使用(它将是记录器的同一个实例). 【参考方案1】:

默认记录器概念很好地处理了这个问题。

Winston 定义了一个默认记录器,任何对 winston 的直接要求(和后续要求)都将检索该记录器。因此,您只需配置这个默认记录器一次,它就可以通过 vanilla require('winston') 在其经过调整的多传输模式下供后续模块使用。

例如这是我完整的日志记录设置,它定义了 3 个传输。我有时将 Loggly 换成 MongoDB。

server.js

var logger=require('./log.js'); 
// requires winston and configures transports for winstons default logger- see code below.

所有其他 .js 文件

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");

log.js - 这是默认记录器的一次性配置

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 容器并从其他模块中的命名容器中检索记录器。这个我没用过。

我唯一的问题是我的部署主机上缺少日志目录,这很容易修复。

希望这会有所帮助。

【讨论】:

如果我想修改控制台传输,这似乎不起作用: var logger = new (winston.Logger)( transports: [ new (winston.transports.Console)( timestamp: true , 级别: '详细', 着色: true ) ] ); module.exports = 记录器;然后,如果我在另一个文件中使用 require('winston') 我的控制台记录器不正确。 如何配置logger.handleExceptions() 文件中的log.js 以记录所有文件中未处理的异常?当我将其设置为logger..handleExceptions(new winston.transports.Console( colorize: true, json: true )); 时,未处理的异常日志记录仅在我的server.js 文件中有效。如果错误在另一个文件中抛出,则不会记录。 这很有帮助,应该被接受为答案恕我直言 不适用于其他文件。它重置为 winston 的默认配置。 给那些和我一样没有在其他模块中工作的人的快速提示。 不要创建new winston.Logger,配置您首先包含的实际logger var。返回它,它不仅在你调用它的模块中可用,而且在你需要winston的任何其他模块中都可用。【参考方案2】:

我所做的(这可能不是最好的方法)是使用一个“全局”模块,在其中导出我通过我的应用程序使用的所有内容。 例如:

//Define your winston instance
winston.add(winston.transports.File,  filename: 'mylogfile.log' );
exports.logger = winston;

exports.otherGlobals = ....

现在只需要其他模块中的这个全局使用的模块

var Global = require(/path/to/global.js);

因为文件在第一次加载后被缓存(您可以通过在全局中包含日志语句来验证;它只会记录一次),再次包含它的开销很小。将它们全部放在一个文件中也比在每个页面上都需要所有全局使用的模块更容易。

【讨论】:

【参考方案3】:

我想使用自定义颜色和级别。

所以我删除了默认的控制台传输并设置了一个彩色的

这是我的 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');        

【讨论】:

我更喜欢 Nick 的回答,因为我看到您需要 'winston' 并假设您的回答不正确(我现在知道这不是因为 Nick 解释了它为什么起作用)。 winston.setLevels 现在在 3.0 中被弃用 github.com/winstonjs/winston-syslog/issues/87【参考方案4】:

有点离题(正如 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 是否也支持这一点。

【讨论】:

【参考方案5】:

我现在正在开发 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')

.

【讨论】:

【参考方案6】:

我正在创建一个新的 Winston 记录器。

log.js

'use strict';

const winston = require('winston');

module.exports = new(winston.Logger)(
    transports: [
        new(winston.transports.Console)(
            level: 'info'
        )
    ]
);

a.js

const log = require('./log');

log.info("from a.js");

b.js

const log = require('./log');

log.info("from b.js");

【讨论】:

【参考方案7】:

这是我的记录器配置,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);
  


希望这会有所帮助!

【讨论】:

【参考方案8】:

我使用工厂函数并传入模块名称,以便可以将其添加到元数据中:

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;
  

app.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'

【讨论】:

【参考方案9】:

如果你想让记录器成为一个全局变量——你必须像这样将它分配给全局变量来专门做

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;

app.js

let logger  = require('./logger')
global.__logger = logger

someController.js

__logger.info('created log successfully')

注意:最好为每个全局变量分配一个前缀,这样您就会知道这是一个全局变量。我使用 __ 作为前缀(双低破折号)

【讨论】:

【参考方案10】:

只需创建 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!');

这甚至为您提供了在需要时交换日志库的选项。接受的答案是完全错误的,离意大利面条代码更近了一步。

【讨论】:

【参考方案11】:

在我的团队中,我们创建了一个包含所有默认配置的私有 npm 包(如您在之前的答案中所示)

我只有一个问题:将记录器对象声明为全局对象以避免在每个模块中导入是一种好习惯吗?

【讨论】:

我不建议这样做。您将在多个模块(例如 db 包、http 包等)中使用的其他包呢?您希望它们全部设为全局,以便在所有相关模块中不需要它们?那是错误的。如果你问这个问题只是因为在你的情况下你应该提到相对路径 - 那么只需将你的包装器作为一个包发布并在没有路径的情况下需要它(就像你对 winston 所做的那样)或使用这个包module-alias 这将允许你给路径命名,然后总是以相同的方式要求它,例如const myWinston = require('@lib/my-winston') @Alexander 当然,这就是我所做的,我理解你的意思,但我有疑问,因为记录器对象和“基础设施”模块之间存在差异:记录器几乎无处不在(或者我希望如此,因为我们提倡代码检测的重要性)

以上是关于如何在几个模块中使用 Winston?的主要内容,如果未能解决你的问题,请参考以下文章

在 winston 日志条目中添加模块名称

Java如何在几个Java文件中共享数据

如何在几个键相等的情况下使用 Python 正确解析嵌套的 json

纱线在构建中找不到模块'logform'winston

如何在几个月前使用 SQL 获得运行平衡的差异?

除第一天外,如何使用 Winston 每天轮换日志