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

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

这篇文章中,让咱们来为插件中的 ssr 服务建立日志系统。前端

咱们将从以下几个方面来逐步进行:vue

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

选择日志工具库

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

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

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

咱们打开 winstonREADME,参照 Creating your own Logger 一节,来开始建立咱们的 loggergit

建立 logger

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

安装 winstonvue-cli

yarn add winston
复制代码

在根目录文件夹下的 app 中建立文件夹 lib,并在 lib 中建立 logger.js 文件,咱们在这个文件中定制本身的 loggerjson

目录结构以下:bash

├── 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 实现了。

对于第2、三条要求,咱们打开 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 设计并集成监控系统。


水滴前端团队招募伙伴,欢迎投递简历到邮箱:fed@shuidihuzhu.com

相关文章
相关标签/搜索