实现的前端监控
  1. 了解业务的访问情况及用户分布
  2. 监控业务自定义关键节点(支付,im消息等)稳定情况
  3. 监控业务系统异常情况(异常明细,异常发生环境)
  4. 监控业务性能状况(未来)
  5. 报警,邮件任务(未来)

写在前面

本文分享是在做监控平台的时候实践的结果,可能会有语法运用不当之处,请小伙伴的多多指点,共同进步!


实现的思路

image.png

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章节》

image.png

nginx切分的方式
  1. shell脚本的读取文件目录进行切分
  2. 根据内置的变量进行切分
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脚本读取日志进行入库操作
  1. 安装node依赖【mysql、mysql2、sequelize】
  2. 创建目录
  3. 编写逻辑
安装node依赖
npm install mysql 
npm install mysql2 
npm install sequelize 
创建目录

创建目录一览,遵从MVC的原则

image.png

入口的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;

项目地址:


THIS
765 声望9 粉丝

多读书、多看报、少吃零食、多睡觉