【第四期】基於 @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

相關文章
相關標籤/搜索