golang+influxdb+grafana 实现 nginx 日志流量、响应时间等监控系统

本贴最后更新于 2122 天前,其中的信息可能已经时过境迁

先看下具体效果图
imagepng

主要是对自己博客服务器的流量的监控
imagepng

主要思路

读取日志-> 正则解析-> 写入 influxdb->grafana 获取数据渲染

首先我们需要先将 influxdb 和 grafana 安装部署好 以便于后面使用

centos7 安装 influxdb

wget https://dl.influxdata.com/influxdb/releases/influxdb-0.13.0.x86_64.rpm

sudo yum localinstall influxdb-0.13.0.x86_64.rpm

service influxdb restart

如果遇到influxdb 8083端口访问不到web管理页面则需要到/etc/influxdb/influxdb.conf 修改参数

vim 命令模式下:/admin搜索到对应部分去掉注释#[admin] #enabled=true

 [admin]
  # Determines whether the admin service is enabled.
   enabled =true

  # The default bind address used by the admin service.
   bind-address = ":8083"

  # Whether the admin service should use HTTPS.
  # https-enabled = false

  # The SSL certificate used when HTTPS is enabled.
  # https-certificate = "/etc/ssl/influxdb.pem"
  
  
  然后具体操作添加用户,建库什么均在百度,和mysql大同小异
  

centos7 下安装 grafana
官方文档中有很多办法
我是通过配置 yum 然后 install 的

Add the following to a new file at /etc/yum.repos.d/grafana.repo

[grafana]
name=grafana
baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packagecloud.io/gpg.key https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt

sudo yum install grafana

service grafana-server  restart

默认跑在3000端口
账号密码为
admin
admin 

代码实现

type Reader interface {
	Read(rc chan []byte)
}

type Writer interface {
	Write(rc chan *Message)
}

//存入db的基本数据
type Message struct {
	Host string

	TimeLocal                  time.Time
	Method, Resource, Protocol string
	Status                     string
	BytesSent                  int
	Scheme                     string
	Url                        string
	UpstreamTime, RequestTime  float64
}

//扩展性实现接口的struct
type ReadFromFile struct {
	path string
}

type WriteToInfluxDB struct {
	influxDBDsn string
}

//日志解析中转
type LogProcess struct {
	rc    chan []byte
	wc    chan *Message
	read  Reader
	write Writer
}

从文件中读取数据(注释中的数据为我 nginx 日志中的一条数据)

/*14.215.176.15 - - [23/APR/2018:21:43:39 +0800]
"GET /CONSOLE/DIST/LAYOUTS/DEFAULT.F5E6C608DE637CAC3F50.JS HTTP/1.1"
200 5665 "HTTPS://WWW.XHXBLOG.CN/?B3ID=H9OXZSYM"
"MOZILLA/5.0 (WINDOWS NT 6.1; WOW64; RV:43.0) GECKO/20100101 FIREFOX/43.0" "-"*/

func (r *ReadFromFile) Read(rc chan []byte) {
	//读取数据

	f, err := os.Open(r.path)
	if err != nil {
		panic(fmt.Sprintf("open file fail:%s", err.Error()))
	}

	//跳到文件末尾
	f.Seek(0, 2)

	rd := bufio.NewReader(f)
    //循环读取数据将每行数据送入rc channel
	for {

		line, err := rd.ReadBytes('\n')
		if err == io.EOF {

			time.Sleep(500 * time.Millisecond)
			continue
		} else if err != nil {
			panic(fmt.Sprintf("ReadBytes error: %s", err.Error()))
		}
		TypeMonitorChan <- TypeHandleLine
		rc <- line[:len(line)-1]

	}

}

正则解析数据(参考正则表达解析,使用该工具调试)

正则测试模块没有单独写出来了,放在如下函数的注释中测试

其中 method,resource,protocol 等是通过正则解析后的字符串再次通过空格 split 分割获取的

func (l *LogProcess) Process() {
	//解析数据

	r := regexp.MustCompile(`([\d\.]+)\s+([^\[]+)\s+([^\[]+)\s+\[([^\]]+)\]\s+\"([^"]+)\"\s+(\d{3})\s+(\d+)\s+\"([^"]+)\"\s+\"([^"]+)\"\s+`)

	/**
	  测试flag
	*/
	//flag := 0
	loc, _ := time.LoadLocation("Asia/Shanghai")
	for v := range l.rc {

		ret := r.FindStringSubmatch(string(v))

		/*测试**/
		/*
		if flag != 2 {
			sp := strings.Split(ret[5], " ")

			fmt.Println(ret)
			fmt.Println("Host:", ret[1])

			fmt.Println("LocalTime:", ret[4])

			fmt.Println("Method:", sp[0])
			uu, _ := url.Parse(sp[1])
			fmt.Println(uu.Path)
			fmt.Println("Path:", sp[1])
			fmt.Println("Protocol:", sp[2])
			fmt.Println("Status:", ret[6])
			fmt.Println("BytesSent:", ret[7])
			fmt.Println("Scheme:", ret[9])
			fmt.Println(ret[8])
			flag++
		}
		*/
		if len(ret) != 10 {
			TypeMonitorChan <- TypeErrNum
			log.Println("FindStringSubmatch fail:", string(v))
			continue
		}

		t, err := time.ParseInLocation("02/Jan/2006:15:04:05 +0800", ret[4], loc)
		if err != nil {
			log.Println("ParseInLocation fail:", err.Error(), ret[4])
		}
		message := &Message{}

		//14.215.176.15
		message.Host = ret[1]

		//23/APR/2018:21:43:39 +0800
		message.TimeLocal = t
		//MOZILLA/5.0 (WINDOWS NT 6.1; WOW64; RV:43.0) GECKO/20100101 FIREFOX/43.0
		message.Scheme = ret[9]

		//GET /CONSOLE/DIST/LAYOUTS/DEFAULT.F5E6C608DE637CAC3F50.JS HTTP/1.1
		sp := strings.Split(ret[5], " ")

		if len(sp) != 3 {
			TypeMonitorChan <- TypeErrNum
			log.Println("strings.Split fail:", ret[5])
			continue
		}
		//请求方法
		message.Method = sp[0]

		//请求路径
		u, err := url.Parse(sp[1])

		if err != nil {
			TypeMonitorChan <- TypeErrNum
			log.Println("url parse fail:", err)
			continue
		}

		message.Resource = u.Path

		//请求协议
		message.Protocol = sp[2]
		//200
		message.Status = ret[7]

		//HTTPS://WWW.XHXBLOG.CN/?B3ID=H9OXZSYM
		message.Url = ret[8]
		//5665
		message.BytesSent, _ = strconv.Atoi(ret[7])
		l.wc <- message
	}

}

写入 influxdb 客户端是用 golang 写的 influxdb gay 地址为:InfluxDB Client

func (w *WriteToInfluxDB) Write(wc chan *Message) {

	sp := strings.Split(w.influxDBDsn, "@")

	// Create a new HTTPClient
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     sp[0],
		Username: sp[1],
		Password: sp[2],
	})
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	for v := range wc {
		// Create a new point batch
		bp, err := client.NewBatchPoints(client.BatchPointsConfig{
			Database:  sp[3],
			Precision: sp[4],
		})
		if err != nil {
			log.Fatal(err)
		}

		// Create a point and add to batch
		tags := map[string]string{"Path": v.Resource, "Method": v.Method, "Scheme": v.Scheme, "Status": v.Status, "Protocol": v.Protocol}
		fields := map[string]interface{}{
			"RequestTime": 2.0,
			"BytesSent":   v.BytesSent,
		}

		pt, err := client.NewPoint("nginx_log", tags, fields, v.TimeLocal)
		if err != nil {
			log.Fatal(err)
		}
		bp.AddPoint(pt)

		// Write the batch
		if err := c.Write(bp); err != nil {
			log.Fatal(err)
		}

		// Close client resources
		if err := c.Close(); err != nil {
			log.Fatal(err)
		}

		log.Println("write success")
	}

}


type SystemInfo struct {
	HandleLine   int     `json:"handleLine"`
	Tps          float64 `json:"tps"`
	ReadChanLen  int     `json:"readChanLen"`
	WriteChanLen int     `json:"writeChanLen"`
	RunTime      string  `json:"runTime"`
	ErrNum       int     `json:"errNum"`
}

type Monitor struct {
	startTime time.Time
	data      SystemInfo
	tpsSli    []int
}

此处主要监控运行时间,channel 阻塞数量,处理条数监听一个端口在 8999 上
其次该监听将阻塞在 main 函数上

//专门接收内容的channel,错误数量和处理条数
var TypeMonitorChan = make(chan int, 200)

func (m *Monitor) start(lp *LogProcess) {
	go func() {
		for n := range TypeMonitorChan {
			switch n {
			case TypeErrNum:
				m.data.ErrNum += 1
			case TypeHandleLine:
				m.data.HandleLine += 1
			}
		}
	}()

	ticker := time.NewTicker(time.Second * 5)

	go func() {
		<-ticker.C
		m.tpsSli = append(m.tpsSli, m.data.HandleLine)
		//目的是为了通过两次读取的行数除以单位时间就能得到大概的吞吐量
		if len(m.tpsSli) > 2 {
			m.tpsSli = m.tpsSli[1:]
		}
	}()

	http.HandleFunc("/monitor", func(writer http.ResponseWriter, request *http.Request) {
		m.data.RunTime = time.Now().Sub(m.startTime).String()
		m.data.ReadChanLen = len(lp.rc)
		m.data.WriteChanLen = len(lp.wc)

		if len(m.tpsSli) >= 2 {
			m.data.Tps = float64(m.tpsSli[1]-m.tpsSli[0]) / 5
		}
		ret, _ := json.MarshalIndent(m.data, "", "\t")

		io.WriteString(writer, string(ret))
	})

	http.ListenAndServe(":8999", nil)

}
func main() {
    
    //通过命令行模式输入参数简单化,不输则为默认值
	var path, influxDsn string
	flag.StringVar(&path, "path", "/var/log/nginx/access.log", "read file path")
	flag.StringVar(&influxDsn, "influxDsn", "http://127.0.0.1:8086@haoxiong@8080@nginx_log@s", "influx data source")
	flag.Parse()
	r := &ReadFromFile{
		path: path,
	}

	w := &WriteToInfluxDB{
		influxDBDsn: influxDsn,
	}

	lp := &LogProcess{
		rc:    make(chan []byte, 200),
		wc:    make(chan *Message),
		read:  r,
		write: w,
	}

	go lp.read.Read(lp.rc)
	for i := 0; i < 2; i++ {
		go lp.Process()

	}
	for i := 0; i < 4; i++ {
		go lp.write.Write(lp.wc)
	}

	m := &Monitor{
		startTime: time.Now(),
		data:      SystemInfo{},
	}
	//监听一个端口阻塞main
	m.start(lp)
}


具体代码
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    497 引用 • 1386 回帖 • 324 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
xhaoxiong
站在巨人的肩膀上学习与创新

推荐标签 标签

  • OnlyOffice
    4 引用 • 15 关注
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 616 关注
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    58 引用 • 22 回帖 • 10 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    14 引用 • 7 回帖
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    5 引用 • 62 回帖 • 10 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    84 引用 • 139 回帖 • 2 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 615 关注
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 53 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 42 关注
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    337 引用 • 1751 回帖 • 1 关注
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 728 关注
  • BND

    BND(Baidu Netdisk Downloader)是一款图形界面的百度网盘不限速下载器,支持 Windows、Linux 和 Mac,详细介绍请看这里

    107 引用 • 1281 回帖 • 31 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 656 关注
  • abitmean

    有点意思就行了

    32 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 54 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    17 引用 • 236 回帖 • 371 关注
  • Mac

    Mac 是苹果公司自 1984 年起以“Macintosh”开始开发的个人消费型计算机,如:iMac、Mac mini、Macbook Air、Macbook Pro、Macbook、Mac Pro 等计算机。

    164 引用 • 594 回帖 • 1 关注
  • 分享

    有什么新发现就分享给大家吧!

    246 引用 • 1780 回帖
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    91 引用 • 751 回帖
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    175 引用 • 407 回帖 • 497 关注
  • 支付宝

    支付宝是全球领先的独立第三方支付平台,致力于为广大用户提供安全快速的电子支付/网上支付/安全支付/手机支付体验,及转账收款/水电煤缴费/信用卡还款/AA 收款等生活服务应用。

    29 引用 • 347 回帖
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 364 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    4 引用 • 16 回帖 • 3 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖 • 6 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    540 引用 • 672 回帖