在上一篇文章【第二期】建立 @vue/cli3 插件,並整合 ssr 功能 ----【SSR第二篇中,咱們建立了一個 @vue/cli3
插件,並將 ssr
服務整合到插件中。javascript
這篇文章中,讓咱們來爲插件中的 ssr
服務建立日誌系統。前端
咱們將從以下幾個方面來逐步進行:vue
ssr
插件中基於 nodejs 有一些日誌工具庫可供選擇:java
這裏咱們從中選擇 winston
做爲基礎日誌工具,接入咱們的 ssr
工程node
ssr
插件中咱們打開 winston
的 README,參照 Creating your own Logger 一節,來開始建立咱們的 logger
git
打開咱們在上一篇文章【第二期】建立 @vue/cli3 插件,並整合 ssr 功能 ----【SSR第二篇中建立的 vue-cli-plugin-my_ssr_plugin_demo
工程github
安裝 winston
vue-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"}
複製代碼
至此,咱們已經爲服務端接入了最基礎的日誌功能,接下來,讓咱們考慮一下實際的日誌場景。
簡單起見,咱們將環境區分、日誌分類、日誌等級簡化爲如下幾個具體要求:
error
、warning
、notice
、info
、debug
。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