做一个监听本地文件的状态的小项目。比如监听日志log文件的修改,如果日志文件中出现error字段,则发送邮件或者短信提醒开发者。
简单拆分一下,主要有监听和“发送消息”(不同形式的消息)这两个功能需要实现。监听打算用go语言的tail包,发送邮件可以用smtp包。文章先分别讲监听、发送消息等前置知识,最后整合做满足效果的Demo。
1 监听文件
参考简书的文章Go实时监听日志文件内容
首先Terminal命令行安装tail包
go get github.com/hpcloud/tail
- 1 命令行在项目文件下新建foo.log文件
echo > foo.log #新建foo.log空文件
echo wukangzuishuai111 >> foo.log #为foo.log文件,新增一行wukangzuishuai111
- 2 新建tailTest目录,目录下建main.go
package main
import (
"fmt"
"github.com/hpcloud/tail"
"time"
)
func main() {
fileName := "./foo.log" // 被监听的文件,即foo日志文件
config := tail.Config{
ReOpen: true, // 重新打开
Follow: true, // 是否跟随
Location: &tail.SeekInfo{Offset: 0, Whence: 2}, // 从文件的哪个地方开始读
MustExist: false, // 文件不存在不报错
Poll: true, // 监听新行,使用tail -f,这个参数非常重要
}
tails, err := tail.TailFile(fileName, config)
if err != nil {
fmt.Println("tail file failed, err:", err)
return
}
var line *tail.Line
var ok bool
for {
line, ok = <-tails.Lines
if !ok {
fmt.Printf("tail file close reopen, filename:%s\n", tails.Filename)
time.Sleep(time.Second)
continue
}
// 打印修改新增的内容
fmt.Println(line.Text)
}
}
- 3 运行跑起来,修改foo.log文件,发现新增的信息被打印在控制台
2 发送邮件
参考简书文章go发送邮件
smtp标准库就是golang实现邮件发送的一个标准库,你可以查看src中的net/stmp
来查看他的源码。
smtp库不需要我们视线接口,我们只需要构造好参数,通过SendMail()
来发送邮件即可。这里采用qq邮箱演示。
新建mailText目录,下建main.go
package main
import (
"fmt"
"net/smtp"
)
func main() {
// 一些参数
subject := fmt.Sprintf("Subject: %s\r\n", "日志异常提醒")
send := fmt.Sprintf("From: %s 测试发件邮箱\r\n", "12059XXXXX.com")
receiver := fmt.Sprintf("To: %s\r\n", "6736XXXXX@qq.com")
contentType := "Content-Type: text/plain" + "; charset=UTF-8\r\n\r\n"
// 拼接body
content := "你好,这里是邮件的内容..."
msgMail := []byte(subject + send + receiver + contentType + content)
addr := "smtp.qq.com:25"
auth := smtp.PlainAuth("", "12059XXXXX@qq.com", "rdzzXXXXXXXX", "smtp.qq.com")
from := "12059XXXXX@qq.com"
to := []string{"673XXXXX@qq.com"}
// 发送邮件
err := smtp.SendMail(addr, auth, from, to, msgMail)
fmt.Println(err)
}
可以收到邮件
3 配置文件
参考CSSDN上的文章golang几种常用配置文件使用方法总结(yaml、toml、json、xml、ini)
使用配置文件存一些常用的配置,避免多处修改。项目必备。
新建yamlTest目录,目录下建conf.yaml文件
host: localhost:3306
user: tigerwolfc
pwd: 654321
dbname: tablename
然后命令行下载第三方库
go get gopkg.in/yaml.v2
yamlTest目录下写main.go
package main
import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
)
func main() {
var c conf
conf := c.getConf()
fmt.Println(conf)
}
// conf 配置文件结构体
type conf struct {
Host string `yaml:"host"`
User string `yaml:"user"`
Pwd string `yaml:"pwd"`
Dbname string `yaml:"dbname"`
}
func (c *conf) getConf() *conf {
yamlFile, err := ioutil.ReadFile("./yamlTest/conf.yaml")
if err != nil {
fmt.Println(err.Error())
}
err = yaml.Unmarshal(yamlFile, c)
if err != nil {
fmt.Println(err.Error())
}
return c
}
运行,控制台打印出了配置文件中的数据信息
4 监听多个文件并实时发送消息
先看看项目目录:
1 先看看配置文件conf.yaml
,将文件名以及发送邮件的常用参数放在配置文件中 方便修改和调用:
fileName: ./log/foo1.log,./log/foo2.log
email:
sendMail: 12059XXXXXX@qq.com
password: rdzzXXXXXXXXXXXXX
acceptMail: 673XXXXXX@qq.com
subject: 日志异常提醒
2 main.go主函数,采用WaitGroup
的方式控制检测多个文件的gorouting,所有协程结束之后才退出主函数。直接遍历文件名,为每个文件检测函数GetMessage
分配一个gorouting。
package main
import (
"fmt"
tail "github.com/hpcloud/tail"
"goLogMessage/controller"
"goLogMessage/entity"
"strings"
"sync"
)
func main() {
// 获取配置文件结构体
var c entity.Conf
conf := c.GetConf()
fileList := strings.Split(conf.FileName, ",")
//fmt.Println(conf)
config := tail.Config{
ReOpen: true, // 重新打开
Follow: true, // 是否跟随
Location: &tail.SeekInfo{Offset: 0, Whence: 2}, // 从文件的哪个地方开始读
MustExist: false, // 文件不存在不报错
Poll: true, // 监听新行,使用tail -f,这个参数非常重要
}
// waitGroup等待所有协程退出
var wg sync.WaitGroup
wg.Add(len(fileList))
for _, s := range fileList {
tailFile, err := tail.TailFile(s, config)
if err != nil {
fmt.Printf("tail file failed,file: %v err: %v/n", s, err)
return
}
go controller.GetMessage(tailFile, wg)
}
wg.Wait()
}
3 controller层下的getMessage.go
,for循环一直遍历执行,监听文件的变化。controller层采用工厂模式的形式调用service层的发送消息的方法:发送邮件消息或者D-Chat消息。
package controller
import (
"fmt"
"github.com/hpcloud/tail"
"goLogMessage/entity"
"goLogMessage/service"
"strings"
"sync"
"time"
)
func GetMessage(tailFile *tail.Tail, wg sync.WaitGroup) {
var line *tail.Line
var ok bool
// for循环一直遍历执行,监听文件的变化
for {
line, ok = <-tailFile.Lines
if !ok {
fmt.Printf("tail file close reopen, filename:%s\n", tailFile.Filename)
time.Sleep(time.Second)
continue
}
fmt.Println(line.Text)
text := line.Text
if strings.Contains(text, "error") {
var message entity.Message
message.FileName = tailFile.Filename
message.Body = text
message.GetTime = time.Now()
service.NewSendMessage("mail").Send(message)
service.NewSendMessage("note").Send(message)
}
}
wg.Done()
}
4 service层发送消息的逻辑sendMessage.go
,里面有一个发送消息的接口,分别有发送邮件和发送D-chat消息两种实现。
package service
import (
"fmt"
"goLogMessage/entity"
"net/smtp"
)
func NewSendMessage(str string) SendMessage {
switch str {
case "mail":
return SendMail{}
case "note":
return SendDChat{}
}
return SendMail{}
}
// SendMessage 发送消息的接口
type SendMessage interface {
Send(entity.Message) bool
}
// SendMail 邮件实现接口
type SendMail struct{}
// Send sendMail函数实现接口中的send方法
func (fMail SendMail) Send(msg entity.Message) bool {
var c = entity.Conf{}
emailConf := c.GetConf().Email
fmt.Println(msg)
// 发送邮件配置
subject := fmt.Sprintf("Subject: %s\r\n", emailConf.Subject)
send := fmt.Sprintf("From: %s 测试发件邮箱\r\n", emailConf.SenMail)
receiver := fmt.Sprintf("To: %s\r\n", emailConf.AcceptMail)
contentType := "Content-Type: text/plain" + "; charset=UTF-8\r\n\r\n"
// 拼接body
content := "文件:" + msg.FileName + ",存在error日志行为:" + msg.Body + ",出现时间:" + msg.GetTime.String()
msgMail := []byte(subject + send + receiver + contentType + content)
addr := "smtp.qq.com:25"
auth := smtp.PlainAuth("", emailConf.SenMail, emailConf.Password, "smtp.qq.com")
from := emailConf.SenMail
to := []string{emailConf.AcceptMail}
// 发送邮件
err := smtp.SendMail(addr, auth, from, to, msgMail)
if err != nil {
fmt.Println(err)
return false
}
return true
}
// SendDChat DChat实现接口
type SendDChat struct{}
func (fChat SendDChat) Send(msg entity.Message) bool {
fmt.Println(msg)
return true
}
5 entity包下面两个简单的实体类,分别表示配置和消息
- 配置和获取配置
package entity
import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
)
type Conf struct {
FileName string `yaml:"fileName"`
Email struct {
SenMail string `yaml:"sendMail"`
Password string `yaml:"password"`
AcceptMail string `yaml:"acceptMail"`
Subject string `yaml:"subject"`
}
}
func (c *Conf) GetConf() *Conf {
yamlFile, err := ioutil.ReadFile("conf.yaml")
if err != nil {
fmt.Println(err.Error())
}
err = yaml.Unmarshal(yamlFile, c)
if err != nil {
fmt.Println(err.Error())
}
return c
}
- 表示消息的实体类
package entity
import "time"
type Message struct {
FileName string
Body string
GetTime time.Time
}
6 最log包下新建两个日志文件foo1、foo2,启动项目,修改编辑日志文件,当日志中出现error字段时,会发送消息给指定的用户
全文代码地址点击这里