在上一篇文章【第二期】建立 @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