package main
import (
"compile"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"libconf/goini"
log "log4go"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
const (
AlarmType = iota
AlarmTypeTimeout
AlarmTypeLost
)
//sub struct of outAlarmBody
type outAlarmPara struct {
Alarmlevel int `json:"alarmlevel"`
Alarmtype int `json:"alarmtype"`
Url string `json:"url"`
Alarmip string `json:"alarmip"`
Alarmcontext string `json:"alarmcontext"`
Alarmtimer string `json:"alarmtimer"`
}
//post body to monitor server
type outAlarmBody struct {
Cpid string `json:"cpid"`
Alarms []outAlarmPara `json:"alarms"`
}
var ver string = "1.0.0"
var g_ip string = ""
var g_cpid string = ""
var g_remoteInterface = ""
var pos int64 = 0 //file seek position
func trimstring(src string) string {
rs := []rune(src)
rl := len(src)
src = string(rs[0:rl])
return src
}
//读取文件需要经常进行错误检查,这个帮助方法可以精简下面的错误检查过程。
func check(e error) {
if e != nil {
panic(e)
}
}
//send alarm to monitor
func httpPost(pUrl string, pBody string) {
resp, err := http.Post(pUrl,
"application/x-www-form-urlencoded",
strings.NewReader(pBody))
if err != nil {
log.Fatal("httpPost error:" + err.Error())
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
log.Fatal("no resp error:" + err.Error())
return
}
fmt.Println(string(body))
log.Debug("ugslb resp with body:%s", string(body))
}
func getCurrentDirectory() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatal("in getCurrentDirectory get path error:" + err.Error())
}
return strings.Replace(dir, "\\", "/", -1)
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
//parse the content like st:20170106180425
func parseTimeVal(dateContent string) (dateTime string) {
//fmt.Println(dateContent)
log.Debug("date to parse:%s", dateContent)
content := strings.Split(dateContent, ":")
timeVal := ""
if 0 != len(content[1]) {
timeformatdate, _ := time.Parse("20060102150405", content[1])
parsedTime := timeformatdate.Format("2006-01-02 15:04:05")
log.Debug("timeVal:%s", parsedTime)
timeVal = parsedTime
}
return timeVal
}
//parse line content like st:20171221150609 et:20171221150614 http://guo1guang.live.OOotvcloud.com/otv/xjgg/livess/channel44/700.m3u8 part
func parseLine(line string) (st, et, url string) {
content := strings.Split(line, " ")
fmt.Println(content, len(content))
stVal := ""
etVal := ""
urlVal := ""
for _, a := range content {
if 0 != len(a) {
log.Debug("to parse %s, length %d", a, len(a))
if strings.Contains(a, "st") {
stVal = parseTimeVal(a)
continue
}
if strings.Contains(a, "et") {
etVal = parseTimeVal(a)
continue
}
if strings.Contains(a, "http") {
urlVal = a
}
}
}
return stVal, etVal, urlVal
}
func alarmProcess(fileName string) {
//analyze the log
file, err := os.Open(fileName)
check(err)
defer file.Close()
var alarm_sliceSend []outAlarmPara
const blockSize = 1024 * 32
const timeoutPattern = "st.*part$"
const lostPattern = "st.*404$"
//log.Debug("cur pos:%d, read block size:%d", pos, blockSize)
for {
o2, err := file.Seek(pos, 0)
check(err)
b2 := make([]byte, blockSize)
n2, err := file.Read(b2)
//fmt.Printf("%d bytes @ %d: %s\n", n2, o2, string(b2))
lines := strings.Split(string(b2), "\n")
for i, a := range lines {
//fmt.Printf("line:%d, value:%q\n", i, a)
//check if it is timeout
reg, err := regexp.Compile(timeoutPattern)
if err != nil {
log.Warn(err.Error())
return
}
if true == reg.MatchString(a) {
//parse the line
st, et, url := parseLine(a)
log.Debug("parse Res: st %s, et %s, url %s", st, et, url)
var outAlarm outAlarmPara
outAlarm.Alarmtype = AlarmTypeTimeout
outAlarm.Alarmlevel = 10
outAlarm.Url = url
outAlarm.Alarmip = g_ip
outAlarm.Alarmtimer = st
outAlarm.Alarmcontext = "超时"
alarm_sliceSend = append(alarm_sliceSend, outAlarm)
log.Debug("part detected line:%d,%q,%q", i, a, alarm_sliceSend)
continue
}
//check if it is not found
reg, err = regexp.Compile(lostPattern)
if err != nil {
fmt.Println(err)
return
}
//fmt.Printf("lost %q\n", reg.FindAllString(a, -1))
if true == reg.MatchString(a) {
st, et, url := parseLine(a)
log.Debug("parse Res: st %s, et %s, url %s", st, et, url)
var outAlarm outAlarmPara
outAlarm.Alarmtype = AlarmTypeLost
outAlarm.Alarmlevel = 10
outAlarm.Url = url
outAlarm.Alarmip = g_ip
outAlarm.Alarmtimer = st
outAlarm.Alarmcontext = "缺片"
alarm_sliceSend = append(alarm_sliceSend, outAlarm)
}
}
pos += int64(n2)
log.Debug("offset %d, read %d, pos %d", o2, n2, pos)
if n2 != blockSize {
log.Debug("reach end of file")
break
}
}
//notify monitor when the timeout happened
outM := &outAlarmBody{
g_cpid,
alarm_sliceSend,
}
b, err := json.Marshal(outM)
if err != nil {
log.Fatal("json encode message failed, %s", string(b))
} else {
log.Debug("post content:%s", string(b))
httpPost(g_remoteInterface, string(b))
}
}
func main() {
if err := log.SetupLogWithConf("./log.json"); err != nil {
panic(err)
}
defer log.Close()
// ./main -V
version := flag.Bool("V", false, "is ok")
flag.Parse()
if *version {
verStr := "ver: " + ver + "\t"
fmt.Println("version:", verStr, compile.BuildTime())
return
}
config_ptr := goini.Init("./dlAlarmConf.ini")
stmp := config_ptr.Read_string("root", "cpid", "btv")
stmp = trimstring(stmp)
g_cpid = stmp
log.Debug(g_cpid)
stmp = config_ptr.Read_string("root", "localAddr", "127.0.0.1")
stmp = trimstring(stmp)
g_ip = stmp
log.Debug(g_ip)
stmp = config_ptr.Read_string("root", "remoteInterface", "http://127.0.0.1/otvcloud/Alarm")
stmp = trimstring(stmp)
g_remoteInterface = stmp
log.Debug(g_remoteInterface)
stmp = config_ptr.Read_string("root", "file2Monitor", "/home/otvcloud/hls/123.log")
stmp = trimstring(stmp)
filePath := stmp
log.Debug(filePath)
isPathExist, _ := PathExists(filePath)
if isPathExist != true {
log.Debug("to be monitored file %s not exist?", filePath)
return
}
ticker := time.NewTicker(time.Second * 10)
go func() {
for _ = range ticker.C {
//log.Debug("ticked at %v", time.Now())
alarmProcess(filePath)
}
}()
//maintain the main process
limiter := time.Tick(time.Second * 30)
for true {
<-limiter
}
}