实现的前端监控
- 了解业务的访问情况及用户分布
- 监控业务自定义关键节点(支付,im消息等)稳定情况
- 监控业务系统异常情况(异常明细,异常发生环境)
- 监控业务性能状况(未来)
- 报警,邮件任务(未来)
写在前面
本文分享是在做监控平台的时候实践的结果,可能会有语法运用不当之处,请小伙伴的多多指点,共同进步!
实现的思路
nginx简介
Nginx服务器以其功能丰富著称于世。它既可以作为HTTP服务器,也可以作为反向代理服务器 或者邮件服务器;能够快速响应静态页面(HTML)的请求;支持FastCGI、SSL、Virtual Host、URL Rewrite、HTTP Basic Auth、Gzip等大量使用功能;并且支持更多的第三方功能模块的扩展。
《摘自 Nginx高性能Web服务器详解》
如图:
最外层的花括号将内容整体分为两部分,再加上最开始的内容,即第-行省略号表示的, nginx.conf-共由三部分组成,分别为全局块、events 块和htp块。在http块中,又包含htp全局块、多个server块。每个server块中,可以包含server全局块和多个location块。在同一配置块中嵌套的配置块,各个之间不存在次序关系。
《摘自 Nginx高性能Web服务器详解 2.4章节》
nginx切分的方式
- shell脚本的读取文件目录进行切分
- 根据内置的变量进行切分
shell脚本的读取文件目录进行切分
通过linux的crontab定时进行脚本的执行《不建议的操作》
#!/bin/bash
#nginx分钟日志切割脚本
#设置日志文件存放目录
logs_path="/data/nginx_log/logs/demo/"
#设置pid文件
pid_path="/usr/local/nginx/logs/nginx.pid"
#重命名日志文件
mv ${logs_path}access.log ${logs_path}access_$(date "+%Y-%m-%d-%H:%M:%S").log
# 注意!
# USR1亦通常被用来告知应用程序重载配置文件;
# 例如,向Apache HTTP服务器发送一个USR1信号将导致以下步骤的发生:停止接受新的连接,等待当前连接停止,重新载入配置文件,重新打开日志文件,重启服务器,从而实现相对平滑的不关机的更改。
# 详情地址【https://blog.csdn.net/fuming0210sc/article/details/50906372】
kill -USR1 `cat ${pid_path}`
echo "开始喽~~~"
根据内置的变量进行切分
利用请求的location 根据匹配正则进行命名日志信息《推荐使用》
补充:刚才的set其实是对nginx的变量标记,跟Javsscript的var、let 其实有异曲同工之妙,用正则提取当前的时间节点进行赋值切分,进行日志赋值。
跟JavaScript的正则有异曲同工之妙,如下图:
通过shell脚本读取日志进行入库操作
因为不太了解shell的一些高级语法、写的比较繁琐和冗长,如果有大佬觉得不当之处或者优化方案,留言评论即可
#!/bin/sh
# ACCESSDAY='/data/nginx_log/logs/access_day/2019-07-18-access.log';
# 日志文件夹
ACCESSDAY='/data/nginx_log/logs/access_minute/'
# 当前的时间
This_time=$(date "+%Y-%m-%d %H:%M" )
# 当前的时间戳
This_tamp=$(date -d "$This_time" +"%s")
# 过去5分钟时间
Past_times=$(date -d "-5 minute" +"%Y-%m-%d %H:%M")
# 过去5分钟的时间戳
Past_Minute_tamp=$(date -d "$Past_Minute_tamp" +"%s")
# 昨天的时间
Yester_day=$(date -d "-1 day" +"%Y-%m-%d %H:%M")
# 昨天的时间戳
Past_tamp=$(date -d "$Yester_day" +"%s")
# 打印的时间戳
#echo ${This_tamp}
#echo ${Past_Minute_tamp}
#echo ${Past_tamp}
#echo $(date -d "$This_time" +"%s")
#echo $(date -d "$Past_times" +"%s")
echo "===============脚本开始======================"
echo $This_time
# 写入数据库
Write_Mysqls(){
USER='xxxx'
PASSWORD='xxxxx'
# 登录mysql
mysql -u $USER --password=$PASSWORD <<EOF
use monitoringLogs;
select "写入数据库";
insert into log_table(
prject_id,
user_id,
log_type,
ua_info,
date,
key_name,
key_id,
key_self_info,
error_type,
error_type_info,
error_stack_info,
target_url
)
values(
"${1}",
"${2}",
"${3}",
"${4}",
"${5}",
"${6}",
"${7}",
"${8}",
"${9}",
"${10}",
"${11}",
"${12}"
);
EOF
echo "=================写入数据库完毕======================"
echo $This_time
}
Handle(){
prjectId='null'
userId='null'
logType='null'
uaInfo='null'
Dates='null'
keyName='null'
keyId='null'
keySelfInfo='null'
errorType='null'
errorTypeInfo='null'
errorStackInfo='null'
targetUrl='null'
_prjectId=$(echo $1 | awk -F '[=]' '{print $2}' )
_userId=$(echo $2 | awk -F '[=]' '{print $2}' )
_logType=$(echo $3 | awk -F '[=]' '{print $2}' )
_uaInfo=$(echo $4 | awk -F '[=]' '{print $2}' )
_Dates=$(echo $5 | awk -F '[=]' '{print $2}' )
_keyName=$(echo $6 | awk -F '[=]' '{print $2}' )
_keyId=$(echo $7 | awk -F '[=]' '{print $2}' )
_keySelfInfo=$(echo $8 | awk -F '[=]' '{print $2}' )
_errorType=$(echo $9 | awk -F '[=]' '{print $2}' )
_errorTypeInfo=$(echo $10 | awk -F '[=]' '{print $2}' )
_errorStackInfo=$(echo $11 | awk -F '[=]' '{print $2}' )
_targetUrl=$(echo $12 | awk -F '[=]' '{print $2}' )
if [ -n "$_logType" ]; then
logType=$_logType
fi
if [ -n "$_uaInfo" ]; then
uaInfo=$_uaInfo
fi
if [ -n "$_Dates" ]; then
Dates=$_Dates
fi
if [ -n "$_keyName" ]; then
keyName=$_keyName
fi
if [ -n "$_keyId" ]; then
keyId=$_keyId
fi
if [ -n "$_keySelfInfo" ]; then
keySelfInfo=$_keySelfInfo
fi
if [ -n "$_errorType" ]; then
errorType=$_errorType
fi
if [ -n "$_errorTypeInfo" ]; then
errorTypeInfo=$_errorTypeInfo
fi
if [ -n "$_errorStackInfo" ]; then
errorStackInfo=$_errorStackInfo
fi
if [ -n "$_targetUrl" ]; then
targetUrl=$_targetUrl
fi
Write_Mysqls $_prjectId $_userId $logType $uaInfo $Dates $keyName $keyId $keySelfInfo $errorType $errorTypeInfo $errorStackInfo $targetUrl
}
# 循环切分字符集
foreach_value(){
#采集端定义字段
arr_log=(
"prject_id"
"user_id"
"log_type"
"ua_info"
"date"
"key_name"
"key_id"
"key_self_info"
"error_type"
"error_type_info"
"error_stack_info"
"target_url"
)
#验证规则
re=".*?(?=&)"
prject_id=$(echo $1 | grep -oP "${arr_log[0]}${re}")
user_id=$(echo $1 | grep -oP "${arr_log[1]}${re}")
log_type=$(echo $1 | grep -oP "${arr_log[2]}${re}")
ua_info=$(echo $1 | grep -oP "${arr_log[3]}${re}")
dates=$(echo $1 | grep -oP "${arr_log[4]}${re}")
key_name=$(echo $1 | grep -oP "${arr_log[5]}${re}")
key_id=$(echo $1 | grep -oP "${arr_log[6]}${re}")
key_self_info=$(echo $1 | grep -oP "${arr_log[7]}${re}")
error_type=$(echo $1 | grep -oP "${arr_log[8]}${re}")
error_type_info=$(echo $1 | grep -oP "${arr_log[9]}${re}")
error_stack_info=$(echo $1 | grep -oP "${arr_log[10]}${re}")
target_url=$(echo $1 | grep -oP "${arr_log[11]}${re}")
# 判断必要的参数,为比传值
if [ -n "$prject_id" -a -n "$user_id" ]; then
# 处理参数
Handle $prject_id $user_id $log_type $ua_info $dates $key_name $key_id $key_self_info $error_type $error_type_info $error_stack_info $target_url
fi
}
# 打印出日志所有的文件夹
whlie_log(){
for FLIE in $(ls ${ACCESSDAY})
do
# 切分自定义日期日志名称
NEW_FLIE=$( echo $FLIE | awk -F '[-]' '{print substr($0,1,length($0)-11)}' )
# 切分日期分钟,转换格式时间戳 用于判断
CUT_TIME=$( echo ${NEW_FLIE} | awk -F "[-]" '{print $1"-"$2"-"$3" "$4$5 }' )
GET_TEXT=$( cat $ACCESSDAY$FLIE )
This_Sum_Tamp=$(date -d "$CUT_TIME" +"%s")
if [ $This_Sum_Tamp -gt $Past_tamp ]; then
foreach_value $GET_TEXT
fi
done
}
whlie_log
通过node脚本读取日志进行入库操作
- 安装node依赖【mysql、mysql2、sequelize】
- 创建目录
- 编写逻辑
安装node依赖
npm install mysql
npm install mysql2
npm install sequelize
创建目录
创建目录一览,遵从MVC的原则
入口的index.js 逻辑
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const getLogInfo = require('./models');
// 读取文件路径
const _day = '/data/nginx_log/logs/access_day'; // 每天
const _minute = '/data/nginx_log/logs/access_minute'; // 没分钟
// 同步读取文件的路径的所有文件日志
const AllJournal = fs.readdirSync(_minute)
AllJournal.map(item => `${_minute}/${item}`).forEach(item => {
// 逐行读取文件流信息
readLines(item)
})
async function readLines(_file){
// 创建一个可读取的二进制文件流
const getData = fs.createReadStream(_file)
// 逐行读取
const rl = readline.createInterface({
input:getData, // 监听可读流
crlfDelay:Infinity // \r \n 延迟时间,默认是100
});
// 异步打印每行的内容
for await (const line of rl) {
getInfo(line)
}
}
// 切分字符串的日志信息
function getInfo(url) {
// 为了兼容正则表达式最后的&匹配
url = `${url}&`
const arr_log = [
"prject_id",
"user_id",
"log_type",
"ua_info",
"date",
"key_name",
"key_id",
"key_self_info",
"error_type",
"error_type_info",
"error_stack_info",
"target_url",
]
const reChek = arr_log.map(item => {
const itemRe = new RegExp(`${item}.*?(?=&)`, 'g')
return url.match(itemRe)[0]
}).map(item => {
let _key = item.split('=')[0]
let _val = item.split('=')[1]
return {
[_key]: _val
}
})
// 处理字段资源
const checkLogList = {}
reChek.forEach(item => {
Object.assign(checkLogList, item);
})
// 每行读取数据存储入库
getLogInfo(checkLogList)
}
config/db.js
/**
* 数据库配置项【https://sequelize.org/v5/class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor】
*
* @typedef {Object} MySql
* @property {string} host 数据库的IP地址
* @property {string} user // 数据库用户名
* @property {string} password // 数据库密码
* @property {string} database // 数据库名称
* @property {string} dialect // 数据库类型【mysql,postgres,sqlite和mssql之一。】
* @property {string} port // 数据库端口
* @property {Object} pool // 排序连接池配置
*/
const sqlConfig = {
host: "xxxx",
user: "xxx",
password: "xxxx",
database: "xxxx"
};
const sequelize = new Sequelize(sqlConfig.database, sqlConfig.user, sqlConfig.password, {
host: sqlConfig.host,
dialect: 'mysql',
port:'3306',
pool: {
max: 100, // 池中的最大连接数
min: 10, // 池中的最小连接数
acquire: 30000, // 该池在抛出错误之前将尝试获得连接的最长时间(以毫秒为单位)
idle: 10000 // 连接释放之前可以空闲的最长时间(以毫秒为单位)。
}
});
config/index.js
const Db = require('./db');
Db
.authenticate()
.then(() => {
console.log('小老弟,链接成功了');
})
.catch(err => {
console.error('报错:', err);
});
models/index.js
// 获取用户传递的参数信息
const getLogInfo = function (log) {
logTable.bulkCreate([log])
}
// logTable.bulkCreate([
// {
// prject_id: 'ceshi11111111111',
// user_id: 'ceshi',
// log_type: 'ceshi',
// ua_info: 'ceshi',
// date: '20180111',
// key_name: 'ceshi',
// key_self_info: 'ceshi',
// error_type: 'ceshi',
// error_type_info: 'ceshi',
// error_stack_info: 'ceshi',
// target_url: 'ceshi',
// key_id: 'ceshi',
// },
// {
// prject_id: 'hhhhh',
// user_id: 'ceshi',
// log_type: 'ceshi',
// ua_info: 'ceshi',
// date: '20180111',
// key_name: 'ceshi',
// key_self_info: 'ceshi',
// error_type: 'ceshi',
// error_type_info: 'ceshi',
// error_stack_info: 'ceshi',
// target_url: 'ceshi',
// key_id: 'ceshi',
// }
// ])
// ;(async function (){
// const all = await logTable.findAll();
// console.log(all)
// console.log('查询多条语句')
// }())
module.exports = getLogInfo
models/logTable.js
根据数据库的类型、自行定义
const DataTypes = require('sequelize');
const Config = require('../config');
const logTable = Config.define('LogTableModel',
{
prject_id: {
field: 'prject_id',
type: DataTypes.STRING(255),
allowNull: false
},
user_id: {
field: 'user_id',
type: DataTypes.STRING(255),
allowNull: false
},
log_type: {
field: 'log_type',
type: DataTypes.INTEGER(64),
allowNull: false
},
ua_info: {
field: 'ua_info',
type: DataTypes.STRING(1000),
allowNull: false
},
date: {
field: 'date',
type: DataTypes.DATE,
allowNull: false
},
key_name: {
field: 'key_name',
type: DataTypes.STRING(255),
allowNull: false
},
key_self_info: {
field: 'key_self_info',
type: DataTypes.STRING(1000),
allowNull: false
},
error_type: {
field: 'error_type',
type: DataTypes.INTEGER(64),
allowNull: false
},
error_type_info: {
field: 'error_type_info',
type: DataTypes.STRING(1000),
allowNull: false
},
error_stack_info: {
field: 'error_stack_info',
type: DataTypes.STRING(1000),
allowNull: false
},
target_url: {
field: 'target_url',
type: DataTypes.STRING(1000),
allowNull: false
},
key_id: {
field: 'key_id',
type: DataTypes.STRING(64),
allowNull: false
},
},
{
timestamps: false,
tableName: 'log_table'
}
)
module.exports = logTable;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。