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

深入学习 Winston #113

Open
nonocast opened this issue Oct 17, 2020 · 0 comments
Open

深入学习 Winston #113

nonocast opened this issue Oct 17, 2020 · 0 comments

Comments

@nonocast
Copy link
Owner

nonocast commented Oct 17, 2020

目录

  • 基本逻辑
  • 使用方式
  • 其他

基本逻辑

  • 通过winston.createLogger创建logger
  • winston自身就扮演了一个defaultLogger
const winston = require('winston');
winston.add(new winston.transports.Console());
winston.info('hello')
  • winston.loggers是一个Container, Container就是一个logger的map,如果你自己创建多个logger也是一样的
  • 核心的logger对象本身一个Node Stream, pipe到对应的Transport, 需要了解一下Node Stream的基本flow
  • createLogger是一个factory method. 创建对应的logger
  • logger.log直接走到logger然后call this.push, 接着通过_transform来应用logger上的format,将format后的内容pipe到logger对应的transports, transports再应用一次transports上的format,最后根据transport自身属性实现输出
  • logger.info则是通过createLogger中根据level挂上去的方法

使用方式

javascript本身的弱类型带来的灵活和不确定性相辅相成。最简单的方式直接看quick start

标准调用方法:

logger.info(msg);
logger.info(msg, meta);

具体来说:

winston.info('hello'); // {"message":"hello","level":"info"}
winston.info('hello', { foo: 'bar' }); // {"foo":"bar","level":"info","message":"hello"}

winston.info({ foo: 'bar' }); // {"message":{"foo":"bar"},"level":"info"}
winston.info({ message: 'hello' }); // {"message":"hello","level":"info"}
winston.info('hello', { message: 'world' }); // {"message":"hello world","level":"info"}

winston.info(); // {"level":"info","message":""}
winston.info('hello', 'world'); //x {"level":"info","message":"hello"}
winston.info(null); // {"message":null,"level":"info"}
winston.info(''); // {"message":"","level":"info"}
winston.info({}); // {"message":{},"level":"info"}

winston.info({ message: 'hello', foo: 'bar' }, { tag: 'user-service' }); // {"tag":"user-service","level":"info","message":{"message":"hello","foo":"bar"}}
winston.info({ message: 'hello', foo: 'bar' }, { message: 'world', tag: 'user-service' }); // {"message":"[object Object] world","tag":"user-service","level":"info"}

无参数逻辑:

// When provided nothing assume the empty string
if (args.length === 0) {
  self.log(level, '');
  return self;
}

一个参数逻辑:

// Optimize the hot-path which is the single object.
if (args.length === 1) {
  const [msg] = args;
  const info = msg && msg.message && msg || { message: msg };
  info.level = info[LEVEL] = level;
  self._addDefaultMeta(info);
  self.write(info);
  return (this || logger);
}

二个及以上参数逻辑:

// Otherwise build argument list which could potentially conform to either:
// 1. v3 API: log(obj)
// 2. v1/v2 API: log(level, msg, ... [string interpolate], [{metadata}], [callback])
return self.log(level, ...args);

所以一般来说,禁用splat, 然后常规记录信息的方式:

logger.info(message, meta);

如果是error,

logger.error(error);
logger.error(message, error);

但问题是logger.error(error)有bug,所以需要一个format进行修复,将Error提前转换为plain object。

winston.info(new Error('user not found')); // {"level":"info"}

所以需要通过format fix掉这个bug

const winston = require('winston');
const { serializeError } = require('serialize-error');

let fixerror = winston.format((info, opts) => {
  if (info instanceof Error) {
    // logger.error(new Error('message'));
    let error = serializeError(info);
    error[Symbol.for('level')] = info.level;
    return error;
  } else if (info.message instanceof Error) {
    // logger.error(new Error());
    let error = serializeError(info.message);
    if(!error.message) error.message = info.message.constructor.name;
    error[Symbol.for('level')] = info.level;
    return error;
  } else {
    return info;
  }
});

let logger = winston.createLogger({
  transports: [new winston.transports.Console()],
  format: winston.format.combine(fixerror(), winston.format.json()),
});

logger.info('hello');
logger.info(new Error());
logger.info(new Error('user not found'));

输出:

{"name":"Error","message":"Error","stack":"Error\n    at Object../src/index.js (/Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:126:13)\n    at __webpack_require__ (/Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:20:30)\n    at /Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:84:18\n    at Object.<anonymous> (/Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:87:10)\n    at Module._compile (internal/modules/cjs/loader.js:1137:30)\n    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)\n    at Module.load (internal/modules/cjs/loader.js:985:32)\n    at Function.Module._load (internal/modules/cjs/loader.js:878:14)\n    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)\n    at internal/main/run_main_module.js:17:47"}
{"level":"info","name":"Error","message":"user not found","stack":"Error: user not found\n    at Object../src/index.js (/Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:127:13)\n    at __webpack_require__ (/Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:20:30)\n    at /Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:84:18\n    at Object.<anonymous> (/Users/nonocast/Develop/hello/hello-winston/test1/dist/bundle.js:87:10)\n    at Module._compile (internal/modules/cjs/loader.js:1137:30)\n    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)\n    at Module.load (internal/modules/cjs/loader.js:985:32)\n    at Function.Module._load (internal/modules/cjs/loader.js:878:14)\n    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)\n    at internal/main/run_main_module.js:17:47"}

Error有没有message都会发生不一样的情况,所以说,fixerror也只是权宜,还是在log之前就将Error转换为string或者plain object再继续,否则很麻烦。

create-logger.js中,

const info = msg && msg.message && msg || { message: msg };

也就是说传入没有message的Error,winston就会将其置为info.message,而如果是有message则会将Error误认为是info object。winston通过format.error来处理error问题,

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
  ]
});

logger.info(new Error());
logger.info(new Error('user not found'));

此时message会采用error的message, 然后加上stack,完美。

logger.info(new Error());
logger.info(new Error('user not found'));
logger.info('error:', new Error('null refenerce'));

如果有显示message,则最终message会是error: null reference。

其他

  • child
const winston = require('winston');

winston.add(new winston.transports.Console());

let logger = winston.child({
  source: 'user-service'
});

logger.info('hi');

output

{"source":"user-service","message":"hi","level":"info"}

如果每个unit需要对自己的日志打上tag,则通过child就可以很方便实现:

const winston = require('winston');
const logger = winston.createLogger({
  transports: [new winston.transports.Console()],
});

const userServiceLogger = logger.child({ tag: 'service:user' });
userServiceLogger.info('create user1');

const deviceServiceLogger = logger.child({ tag: 'service:device' });
deviceServiceLogger.info('del device1');

output:

{"tag":"service:user","message":"create user1","level":"info"}
{"tag":"service:device","message":"del device1","level":"info"}

如果日志根据情况不同需要写入不同的transport,形式或者文件,那么应该采用多个logger,通过container save&load。而如果是打不同tag,则通过meta实现即可。

参考

@nonocast nonocast changed the title 深入学习Winston 深入学习 Winston Oct 20, 2020
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