【第四期】基于 @vue/cli3 插件,集成日志系统【SSR第三篇】

在上一篇文章【第二期】创建 @vue/cli3 插件,并整合 ssr 功能 ----【SSR第二篇中,我们创建了一个 @vue/cli3 插件,并将 ssr 服务整合到插件中。

这篇文章中,让我们来为插件中的 ssr 服务创建日志系统。

我们将从如下几个方面来逐步进行:

  • 选择日志工具库
  • 将日志工具集成到 ssr 插件中
  • 日志支持区分环境、日志支持分类、日志支持等级
  • 日志切割

选择日志工具库

基于 nodejs 有一些日志工具库可供选择:

这里我们从中选择 winston 作为基础日志工具,接入我们的 ssr 工程

将日志工具集成到 ssr 插件中

我们打开 winstonREADME,参照 Creating your own Logger 一节,来开始创建我们的 logger

创建 logger

打开我们在上一篇文章【第二期】创建 @vue/cli3 插件,并整合 ssr 功能 ----【SSR第二篇中创建的 vue-cli-plugin-my_ssr_plugin_demo 工程

安装 winston

yarn add winston
复制代码

在根目录文件夹下的 app 中创建文件夹 lib,并在 lib 中创建 logger.js 文件,我们在这个文件中定制自己的 logger

目录结构如下:

├── app
│   ├── lib
│   │   ├── logger.js
│   ├── middlewares
│   │   ├── dev.ssr.js
│   │   ├── dev.static.js
│   │   └── prod.ssr.js
│   └── server.js
...
复制代码

logger.js 的内容如下:

const winston = require('winston')

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'combined.log' })
  ]
})

module.exports = {
  logger
}
复制代码

然后打开我们的 app/server.js ,在服务启动的过程中,为全局对象挂载上我们刚创建的 logger

...

const { logger } = require('./lib/logger.js')

...

app.listen(port, host, () => {
  logger.info(`[${process.pid}]server started at ${host}:${port}`)
})

...
复制代码

启动服务,除了在终端看到输出外,还会发现在根目录下多了一个 combined.log 文件,里面的内容与终端输出一致

{"message":"[46071]server started at 127.0.0.1:3000","level":"info"}
复制代码

至此,我们已经为服务端接入了最基础的日志功能,接下来,让我们考虑一下实际的日志场景。

日志支持区分环境、日志支持分类、日志支持等级

简单起见,我们将环境区分、日志分类、日志等级简化为以下几个具体要求:

  1. 日志需要写入到文件中。
  2. 日志需要支持自定义级别,级别由大到小依次是:errorwarningnoticeinfodebug
  3. 开发环境,日志的输出最好能带颜色,格式能更加方便在终端阅读。
  4. 增加用户请求日志 access 类型,此日志需要写入到单独的文件中,与其他类型的日志区分开。

关于第一条要求,我们在上一个例子中,已经通过 winston.transports.File 实现了。

对于第二、三条要求,我们打开 lib/logger.js 添加相关的代码,最终代码如下:

const winston = require('winston')

const options = {
  // 我们在这里定义日志的等级
  levels: { error: 0, warning: 1, notice: 2, info: 3, debug: 4 },
  transports: [
    // 文件中我们只打印 warning 级别以上的日志(包含 warning)
    new winston.transports.File({ filename: 'combined.log', level: 'warning' })
  ]
}

// 开发环境,我们将日志也输出到终端,并设置上颜色
if (process.env.NODE_ENV === 'development') {
  options.format = winston.format.combine(
    winston.format.colorize(),
    winston.format.json()
  )

  // 输出到终端的信息,我们调整为 simple 格式,方便看到颜色;
  // 并设置打印 debug 以上级别的日志(包含 debug)
  options.transports.push(new winston.transports.Console({
    format: winston.format.simple(), level: 'debug'
  }))
}

const logger = winston.createLogger(options)

module.exports = {
  logger
}
复制代码

我们在 app/servier.js 中输入以下代码:

...

logger.error('this is the error log')
logger.warning('this is the warning log')
logger.notice('this is the info log')
logger.info('this is the info log')
logger.debug('this is the debug log')

...
复制代码

在发开环境启动服务后,能看到终端打印出如下内容:

error: this is the error log
warning: this is the warning log
notice: this is the info log
info: this is the info log
debug: this is the debug log
复制代码

而日志文件 combined.log 中的内容为:

{"message":"this is the error log","level":"\u001b[31merror\u001b[39m"}
{"message":"this is the warning log","level":"\u001b[31mwarning\u001b[39m"}
复制代码

在测试和产品环境启动服务后,日志并不会输出到终端,只输出到文件中:

{"message":"this is the error log","level":"error"}
{"message":"this is the warning log","level":"warning"}
复制代码

接下来我们来看第四条要求:

增加用户请求日志 access 类型,此日志需要写入到单独的文件中,与其他类型的日志区分开。

如果我们需要增加一个 access 日志类型,并将它的内容输出到独立的文件中,最简单的方式就是再创建一个 logger 实例:

...
winston.loggers.add('access', {
  levels: { access: 0 },
  level: 'access',
  format: winston.format.combine(
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'access.log', level: 'access' })
  ]
})
...
复制代码

我们在 app/servier.js 中添加打印 access 日志的代码:

const { logger, accessLogger } = require('./log.js')
...
accessLogger.access('this is the access log')

复制代码

在开发环境启动服务后,我们发现除了 combined.log 日志文件外,又多了一个 access.log 文件,内容为:

{"message":"this is the access log","level":"access"}
复制代码

至此,我们的日志中还没有自动记录当前的时间,我们在 lib/logger.js 中为两类日志都添加上时间,添加后的代码如下:

const winston = require('winston')

const options = {
  // 我们在这里定义日志的等级
  levels: { error: 0, warning: 1, notice: 2, info: 3, debug: 4 },
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' })
  ),
  transports: [
    // 文件中我们只打印 warning 级别以上的日志(包含 warning)
    new winston.transports.File({ filename: 'combined.log', level: 'warning' })
  ]
}

// 开发环境,我们将日志也输出到终端,并设置上颜色
if (process.env.NODE_ENV === 'development') {
  options.format = winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
    winston.format.colorize(),
    winston.format.json()
  )

  // 输出到终端的信息,我们调整为 simple 格式,方便看到颜色;
  // 并设置打印 debug 以上级别的日志(包含 debug)
  options.transports.push(new winston.transports.Console({
    format: winston.format.simple(), level: 'debug'
  }))
}

winston.loggers.add('access', {
  levels: { access: 0 },
  level: 'access',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'access.log', level: 'access' })
  ]
})

const logger = winston.createLogger(options)

module.exports = {
  logger,
  accessLogger: winston.loggers.get('access')
}
复制代码

在开发环境启动服务后,我们发现日志携带了当前的时间信息,终端内容为:

error: this is the error log {"timestamp":"2019-06-06 17:02:36.736"}
warning: this is the warning log {"timestamp":"2019-06-06 17:02:36.740"}
notice: this is the info log {"timestamp":"2019-06-06 17:02:36.741"}
info: this is the info log {"timestamp":"2019-06-06 17:02:36.741"}
debug: this is the debug log {"timestamp":"2019-06-06 17:02:36.741"}
复制代码

``文件的内容为:

{"message":"this is the error log","level":"\u001b[31merror\u001b[39m","timestamp":"2019-06-06 17:02:36.736"}
{"message":"this is the warning log","level":"\u001b[31mwarning\u001b[39m","timestamp":"2019-06-06 17:02:36.740"}
复制代码

``文件的内容为:

{"message":"this is the access log","level":"access","timestamp":"2019-06-06 17:02:36.741"}
复制代码

日志切割

将来我们的服务部署上线后,服务器端记录的日志会不断得往同一个文件中写入日志内容,这对于长时间运行的服务来说,是有日志过大隐患的。

这里,我们按照每小时分割日志,将每个小时内的日志内容,写入不同的文件中。

另外,因为部署产品服务会有多个 worker 进程服务,所以,我们为每个进程分配一个独立的文件夹(以进程id+日期区分),来存储此进程的全部日志:

logs
├──your_project_name
│   ├──pid_1236_2019_01_01
│   │   ├──access-2019-01-01-23.log
│   │   └──combined-2019-01-01-23.log
│   └──pid_1237_2019_01_01
│      ├──access-2019-01-01-23.log
│      └──combined-2019-01-01-23.log
复制代码

为了实现我们预计的日志切割功能,我们需要引入一个库:winston-daily-rotate-file

yarn add winston-daily-rotate-file
复制代码

安装完后,我们在 lib/logger.js 中引入

require('winston-daily-rotate-file')
复制代码

引入后,我们可以通过 winston.transports.DailyRotateFile 创建拥有自动切割功能的 tracsports 实例

const _getToday = (now = new Date()) => `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`

let dirPath += '/pid_' + pid + '_' + _getToday() + '/'

let accessTransport = new (winston.transports.DailyRotateFile)({
  filename: dirPath + 'access-%DATE%.log', // 日志文件存储路径 + 日志文件名称
  datePattern: 'YYYY-MM-DD-HH', // 日志文件切割的粒度,这里为每小时
  zippedArchive: true, // 是否压缩
  maxSize: '1g', // 每个日志文件最大的容量,如果达到此容量则触发切割
  maxFiles: '30d' // 日志文件保留的时间,这里为 30 天,30天之前的日志会被删除掉
})
复制代码

添加切割功能后的 lib/logger.js 内容如下:

const winston = require('winston')
const { format } = winston
const { combine, timestamp, json } = format

const _getToday = (now = new Date()) => `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`

const rotateMap = {
  'hourly': 'YYYY-MM-DD-HH',
  'daily': 'YYYY-MM-DD',
  'monthly': 'YYYY-MM'
}

module.exports = (dirPath = './', rotateMode = '') => {

  if (!~Object.keys(rotateMap).indexOf(rotateMode)) rotateMode = ''

  let accessTransport
  let combineTransport

  if (rotateMode) {
    require('winston-daily-rotate-file')

    const pid = process.pid

    dirPath += '/pid_' + pid + '_' + _getToday() + '/'

    const accessLogPath = dirPath + 'access-%DATE%.log'
    const combineLogPath = dirPath + 'combine-%DATE%.log'

    const datePattern = rotateMap[rotateMode] || 'YYYY-MM'

    accessTransport = new (winston.transports.DailyRotateFile)({
      filename: accessLogPath,
      datePattern: datePattern,
      zippedArchive: true,
      maxSize: '1g',
      maxFiles: '30d'
    })

    combineTransport = new (winston.transports.DailyRotateFile)({
      filename: combineLogPath,
      datePattern: datePattern,
      zippedArchive: true,
      maxSize: '500m',
      maxFiles: '30d'
    })
  }

  const options = {
    // 我们在这里定义日志的等级
    levels: { error: 0, warning: 1, notice: 2, info: 3, debug: 4 },
    format: combine(
      timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' })
    ),
    transports: rotateMode ? [
      combineTransport
    ] : []
  }

  // 开发环境,我们将日志也输出到终端,并设置上颜色
  if (process.env.NODE_ENV === 'development') {
    options.format = combine(
      timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
      winston.format.colorize(),
      json()
    )

    // 输出到终端的信息,我们调整为 simple 格式,方便看到颜色;
    // 并设置打印 debug 以上级别的日志(包含 debug)
    options.transports.push(new winston.transports.Console({
      format: format.simple(), level: 'debug'
    }))
  }

  winston.loggers.add('access', {
    levels: { access: 0 },
    level: 'access',
    format: combine(
      timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
      json()
    ),
    transports: rotateMode ? [
      accessTransport
    ] : []
  })

  const logger = winston.createLogger(options)

  return {
    logger: logger,
    accessLogger: winston.loggers.get('access')
  }
}
复制代码

app/server.js 中引入 lib/logger.js 也需要调整为以下方式:

const { logger, accessLogger } = require('./lib/logger.js')('./', 'hourly')
复制代码

在开发环境启动服务,我们会发现除了终端输出了日志外,我们的日志文件变成了如下的结构:

./pid_48794_2019-6-6
├── access-2019-06-06-18.log
└── combine-2019-06-06-18.log
复制代码

最终,插件 vue-cli-plugin-my_ssr_plugin_demo 的完整目录结构如下:

├── app
│   ├── middlewares
│   │   ├── dev.ssr.js
│   │   ├── dev.static.js
│   │   └── prod.ssr.js
│   ├── lib
│   │   └── logger.js
│   └── server.js
├── generator
│   ├── index.js
│   └── template
│       ├── src
│       │   ├── App.vue
│       │   ├── assets
│       │   │   └── logo.png
│       │   ├── components
│       │   │   └── HelloWorld.vue
│       │   ├── entry-client.js
│       │   ├── entry-server.js
│       │   ├── main.js
│       │   ├── router
│       │   │   └── index.js
│       │   ├── store
│       │   │   ├── index.js
│       │   │   └── modules
│       │   │       └── book.js
│       │   └── views
│       │       ├── About.vue
│       │       └── Home.vue
│       └── vue.config.js
├── index.js
└── package.json
复制代码

至此,我们的日志系统完成了。下一篇文章,我们讲如何为 vue-cli-plugin-my_ssr_plugin_demo 设计并集成监控系统。


水滴前端团队招募伙伴,欢迎投递简历到邮箱:[email protected]

转载于:https://juejin.im/post/5d030cc4f265da1bd04edc64

猜你喜欢

转载自blog.csdn.net/weixin_34038652/article/details/93182168