Commit 1fc283e0 by 刘豪

更新日志

parent a9711d52
package code
import (
"fmt"
"github.com/gorilla/websocket"
"github.com/kataras/iris/v12"
"log"
"net/url"
"os"
"strings"
"time"
"web_log/config"
"web_log/utils"
)
type User struct {
......@@ -20,45 +20,44 @@ func GetApp(directory string, deBug bool) *iris.Application {
w := &WebHandler{directory}
app.Get("/", func(ctx iris.Context) {
w.loginhandle(ctx, nil)
w.loginHandle(ctx, nil)
})
app.Post("/login", func(ctx iris.Context) {
var user User
ctx.ReadForm(&user)
//user.Username = "zzl"
//user.Password = "Lx@520m!@@#"
if ok2, msg := VerifyUser(user.Username, user.Password); ok2 {
ctx.SetCookieKV("sessionid", msg)
ctx.Redirect("/index", 301)
} else {
fmt.Println("验证不通过")
w.loginhandle(ctx, msg)
utils.Log.Warn("验证不通过")
w.loginHandle(ctx, msg)
}
})
if !deBug {
app.Use(func(ctx iris.Context) {
sessionid := ctx.GetCookie("sessionid")
ok1, msg := VerifySessionID(sessionid)
fmt.Println(msg)
utils.Log.Info(msg)
if ok1 {
ctx.Next()
} else {
w.loginhandle(ctx, "未登录")
w.loginHandle(ctx, "未登录")
}
})
}
app.Get("/index", w.indexhandle)
app.Get("/ws", genCtxHand(directory, manager.filemap))
app.Get("/index", w.indexHandle)
app.Get("/ws", getCtxHand(manager.filemap))
app.Get("/wsSearchLog", getSearchLogResult)
app.Get("/searchLog", w.searchLogHandle)
app.Get("/downLog", w.downLogHandle)
go manager.start()
return app
}
// file_map -> {filename : {client: True}}
func genCtxHand(directory string, file_map map[string]map[Client]bool) func(ctx iris.Context) {
// getCtxHand 获取单个文件的最后五百行内容,并开启额外协程监控文件是否更新以达到实时加载的目的
func getCtxHand(fileMap map[string]map[Client]bool) func(ctx iris.Context) {
f := func(ctx iris.Context) {
w := ctx.ResponseWriter()
r := ctx.Request()
......@@ -68,46 +67,120 @@ func genCtxHand(directory string, file_map map[string]map[Client]bool) func(ctx
if err != nil {
return
}
cli := &Client{time.Now().String(), ws, make(chan []byte, 1024), ""}
queryform, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
log.Fatal(err)
}
filename := queryform["file"][0]
//初始监听时加载最后500行全部内容
file_path := directory
for _, s := range strings.Split(filename, "~~") {
file_path += "/" + s
}
_, err = os.Stat(file_path)
queryForm, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
utils.Log.Errorf("解析参数错误, %s", err.Error())
return
}
lastcontent_msg, err := return_init_str(file_path, 500)
if err != nil {
log.Fatal(err)
return
}
cli.Initstr = lastcontent_msg
// 此文件路径是完整的文件路径
filePath := strings.ReplaceAll(queryForm["file"][0], "\\", "")
// 建立ws客户端
cli := &Client{time.Now().String(), ws, make(chan []byte, 1024), ""}
// 监控文件
if _, ok := file_map[filename]; ok {
file_map[filename][*cli] = true
fmt.Printf("%s has been monitored\n", filename)
//加载最后500行全部内容
content := logResult(nil, filePath, "500")
cli.Initstr = content
// 判断该文件的client对象是否已存在,不存在则新开监控的协程
if _, ok := fileMap[filePath]; ok {
fileMap[filePath][*cli] = true
utils.Log.Infof("%s has been monitored", filePath)
} else {
go monitor(file_path, filename)
// 监控文件
go monitor(filePath, cli)
l := make(map[Client]bool)
l[*cli] = true
file_map[filename] = l
fmt.Printf("Add a new monitored file [%s]\n", filename)
fileMap[filePath] = l
utils.Log.Infof("Add a new monitored file [%s]", filePath)
}
//注册client并开始传输信息
manager.register <- cli
go cli.read()
//go cli.read()
cli.write()
}
return f
}
// logResult 负责执行linux命令获取结果
func logResult(ctx iris.Context, filePath, lineNums string) string {
// 通过ctx是否为nil来判断是否是批量查询
if ctx == nil {
// 二进制文件直接用grep过滤全部将会很慢
// 不用-a是因为此处过来关键词是空字符串,且文件不确定是否为二进制文件,目的是过滤最后的全部行,如果用-a,grep命令不会报错,将会在此处卡很久
//所以此处命令作用相当于判断是否为二进制文件,
cmd := `grep "" ` + filePath + ` | tail -n ` + lineNums
logResult, err := utils.GetLinuxResult(cmd)
if err != nil {
return "执行过滤命令出错, 命令:" + cmd + ",错误原因:" + err.Error()
}
// 判断日志文件是否为二进制文件,二进制文件结果会返回Binary file,是的话再用strings命令
if strings.Contains(logResult, "Binary file") {
cmd = `strings ` + filePath + ` | tail -n ` + lineNums
logResult, err = utils.GetLinuxResult(cmd)
if err != nil {
return "执行过滤命令出错, 命令:" + cmd + ",错误原因:" + err.Error()
}
}
return logResult
}
keyword := ctx.URLParam("keyword")
filePath = strings.TrimSpace(ctx.URLParam("filePath"))
logs := ctx.URLParam("logs")
logArr := strings.Split(logs, ",")
logResults := ""
haveContent := false
cmd := ""
// 遍历所有日志文件
for index, logName := range logArr {
//-a 防止日志文件是二进制文件查询出错
if keyword == "" {
cmd = `grep "" ` + config.Directory + filePath + "/" + logName + ` | tail -n ` + lineNums
} else {
cmd = `grep -a ` + keyword + ` ` + config.Directory + filePath + "/" + logName + ` | tail -n ` + lineNums
}
logResult, err := utils.GetLinuxResult(cmd)
if err != nil {
return "执行过滤命令出错, 命令:" + cmd + ",错误原因:" + err.Error()
}
// 判断日志文件是否为二进制文件,二进制文件结果会返回Binary file,是的话再用strings命令
if strings.Contains(logResult, "Binary file") {
cmd = `strings ` + config.Directory + filePath + "/" + logName + ` | tail -n ` + lineNums
logResult, err = utils.GetLinuxResult(cmd)
if err != nil {
return "执行过滤命令出错, 命令:" + cmd + ",错误原因:" + err.Error()
}
}
if logResult != "" {
haveContent = true
}
if index == len(logArr)-1 {
logResults += logResult
} else {
logResults += logResult + "\n"
}
}
if haveContent == false {
logResults = "查询结果为空, 命令:" + cmd
}
return logResults
}
// 获取批量查询结果
func getSearchLogResult(ctx iris.Context) {
w := ctx.ResponseWriter()
r := ctx.Request()
// 完成ws协议的握手操作
// Upgrade:websocket
ws, err := upGrader.Upgrade(w, r, nil)
if err != nil {
return
}
cli := &Client{time.Now().String(), ws, make(chan []byte, 1024), ""}
content := logResult(ctx, "", "200")
cli.Initstr = content
//manager.clients[cli] = false
_ = cli.Socket.WriteMessage(websocket.TextMessage, []byte(content))
_ = cli.Socket.Close()
//manager.unregister <- cli
}
......@@ -3,27 +3,39 @@ package code
import (
"github.com/gorilla/websocket"
"html/template"
"log"
"net/http"
"web_log/utils"
)
func return_res(w http.ResponseWriter, template_path string, data interface{}) {
func returnRes(w http.ResponseWriter, template_path string, data interface{}) {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(200)
tmpl, err := template.ParseFiles(template_path)
if err != nil {
log.Println("err:", err)
utils.Log.Errorf("return_res错误: %s", err.Error())
return
}
tmpl.Execute(w, data)
}
func return_error_res(w http.ResponseWriter, str_file string) {
func returnErrorRes(w http.ResponseWriter, str_file string) {
template_path := "web/views/file_not_exist.html"
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(200)
tmpl, err := template.ParseFiles(template_path)
if err != nil {
log.Println("err:", err)
utils.Log.Errorf("return_error_res错误: %s", err.Error())
return
}
tmpl.Execute(w, str_file)
}
func returnDownErrorRes(w http.ResponseWriter, str_file string) {
template_path := "web/views/down_file_error.html"
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(200)
tmpl, err := template.ParseFiles(template_path)
if err != nil {
utils.Log.Errorf("return_error_res错误: %s", err.Error())
return
}
tmpl.Execute(w, str_file)
......
......@@ -4,16 +4,17 @@ import (
"bufio"
"bytes"
"io"
"log"
"os"
"strings"
"web_log/utils"
)
// 读取最后五百行数据
func return_init_str(file_path string, lines_num int) (string, error) {
lines := int64(lines_num)
file, err := os.Open(file_path)
if err != nil {
log.Println(err)
utils.Log.Errorf("打开文件失败: %s", err.Error())
return "", err
}
fileInfo, _ := file.Stat()
......@@ -39,7 +40,7 @@ func return_init_str(file_path string, lines_num int) (string, error) {
continue
}
if err != nil {
log.Println("Read file error:", err)
utils.Log.Errorf("Read file error: %s", err.Error())
return "", err
}
strs := strings.Split(string(data[:n]), "\n")
......
......@@ -12,6 +12,7 @@ import (
"regexp"
"sync"
. "web_log/config"
"web_log/utils"
)
//var userMap = make(map[string]string)
......@@ -33,7 +34,7 @@ func init() {
//初始化读取配置文件
func ReadUserConfig(path string) *MainConfig {
if conf != nil && path != UserConfigPath {
log.Printf("the config is already initialized, oldPath=%s, path=%s", UserConfigPath, path)
utils.Log.Errorf("the config is already initialized, oldPath=%s, path=%s", UserConfigPath, path)
}
instanceOnce.Do(func() {
allConfigs, mainConfig := LoadConfig(path)
......@@ -164,17 +165,17 @@ func CutWord(s string) string {
func LoadConfig(path string) (Configs, *MainConfig) {
buf, err := ioutil.ReadFile(path)
if err != nil {
log.Panicln("load config conf failed: ", err)
utils.Log.Panicf("load config conf failed: %s", err.Error())
}
mainConfig := &MainConfig{}
err = json.Unmarshal(buf, mainConfig)
if err != nil {
log.Panicln("decode config file failed:", string(buf), err)
utils.Log.Panicf("decode config file failed:", string(buf), err.Error())
}
allConfigs := make(Configs, 0)
err = json.Unmarshal(buf, &allConfigs)
if err != nil {
log.Panicln("decode config file failed:", string(buf), err)
utils.Log.Panicf("decode config file failed:", string(buf), err.Error())
}
return allConfigs, mainConfig
......
package code
import (
"log"
"os"
"time"
"web_log/utils"
)
// 监控日志文件
func monitor(filePath string, filename string) {
func monitor(filePath string, cli *Client) {
defer func() {
if err := recover(); err != nil {
log.Printf("[seelog] error:%+v", err)
utils.Log.Errorf("defer [seelog] error:%+v", err)
}
}()
var fileInfo os.FileInfo
......@@ -18,45 +18,54 @@ func monitor(filePath string, filename string) {
for i := 1; i <= 10; i++ {
fileInfo, err = os.Stat(filePath)
if err != nil {
log.Printf("[seelog] error:%v", err.Error())
utils.Log.Errorf("循环Stat [seelog] error:%v", err.Error())
continue
}
break
}
offset := fileInfo.Size()
startMonitorTime := time.Now().Unix()
for {
fileInfo, err = os.Stat(filePath)
if err != nil {
log.Printf("[seelog] error:%v", err.Error())
continue
utils.Log.Errorf("Stat [seelog] error:%v", err.Error())
return
}
newOffset := fileInfo.Size()
// 根据文件大小来判断是否新内容
if offset < newOffset {
msg := make([]byte, newOffset-offset)
file, err := os.Open(filePath)
if err != nil {
log.Printf("[seelog] error:%v", err.Error())
utils.Log.Errorf("Open [seelog] error:%v", err.Error())
continue
}
_, err = file.Seek(offset, 0)
if err != nil {
log.Printf("[seelog] error:%v", err.Error())
utils.Log.Errorf("Seek [seelog] error:%v", err.Error())
continue
}
_, err = file.Read(msg)
if err != nil {
log.Printf("[seelog] error:%v", err.Error())
utils.Log.Errorf("Read [seelog] error:%v", err.Error())
continue
}
str_msg := string(msg)
s := filename + "$$" + str_msg
strMsg := string(msg)
s := filePath + "$$" + strMsg
manager.broadcast <- []byte(s)
offset = newOffset
file.Close()
}
offset = newOffset
time.Sleep(200 * time.Millisecond)
// 防止协程开启太多
if startMonitorTime < (time.Now().Unix() - 3600) {
utils.Log.Info("超过1小时限制时间,退出协程")
return
} else {
time.Sleep(500 * time.Millisecond)
}
}
}
package code
import (
"fmt"
"github.com/kataras/iris/v12"
"os"
"net/http"
"strings"
"web_log/utils"
)
......@@ -11,9 +11,7 @@ type WebHandler struct {
Directory string
}
type Datafile struct {
Htmltitle string
CurFiles []FileInfo
UrlLocation string
CurFiles []FileInfo
}
type FileInfo struct {
......@@ -22,56 +20,75 @@ type FileInfo struct {
LastModify string
}
type SearchLog struct {
LogResult string
Keyword string
LogPath string
Servers string
}
var base_path = "web/views/base.html"
func (wbh *WebHandler) loginhandle(ctx iris.Context, data interface{}) {
return_res(ctx.ResponseWriter(), "web/views/login.html", data)
// loginHandle 登录界面
func (wbh *WebHandler) loginHandle(ctx iris.Context, data interface{}) {
returnRes(ctx.ResponseWriter(), "web/views/login.html", data)
}
func (wbh *WebHandler) indexhandle(ctx iris.Context) {
innerip := ctx.URLParam("innerip")
server_direc := ctx.URLParam("server_direc")
file := ctx.URLParam("filename")
if innerip != "" && server_direc != "" && file != "" {
str_filepath := innerip + "~~" + server_direc + "~~" + file
filename := innerip + "/" + server_direc + "/" + file
_, err := os.Stat(wbh.Directory + "/" + filename)
if err != nil {
return_error_res(ctx.ResponseWriter(), filename)
return
}
fmt.Printf("view log [%s]\n", file)
return_res(ctx.ResponseWriter(), "web/views/page.html", str_filepath)
// indexHandle 主界面
func (wbh *WebHandler) indexHandle(ctx iris.Context) {
filePath := ctx.URLParam("filePath")
filePath = wbh.Directory + filePath
isFile := utils.IsDir(filePath)
if isFile != true {
returnRes(ctx.ResponseWriter(), "web/views/page.html", filePath)
return
} else {
var htmltitle, urlloction string
dir := wbh.Directory
if innerip == "" && server_direc == "" && file == "" {
htmltitle = "选择服务器"
urlloction = "?innerip="
}
if innerip != "" && server_direc == "" && file == "" {
htmltitle = "选择服务"
urlloction = "&server_direc="
dir += "/" + innerip
}
if innerip != "" && server_direc != "" && file == "" {
htmltitle = "选择文件"
urlloction = "&filename="
dir += "/" + innerip + "/" + server_direc
}
filesinfo, err := utils.PathFileInfo(dir)
if err != nil {
return_error_res(ctx.ResponseWriter(), dir)
}
var fs = make([]FileInfo, 0)
for _, v := range filesinfo {
fs = append(fs, FileInfo{Name: v["file_name"], Size: v["file_size"], LastModify: v["last_modify"]})
}
filesInfo, err := utils.PathFileInfo(filePath)
if err != nil {
returnErrorRes(ctx.ResponseWriter(), filePath)
return
}
var fs = make([]FileInfo, 0)
isLogFile := false
for _, v := range filesInfo {
if !utils.IsDir(filePath + "/" + v["file_name"]) {
isLogFile = true
}
data := Datafile{Htmltitle: htmltitle, CurFiles: fs, UrlLocation: urlloction}
return_res(ctx.ResponseWriter(), base_path, data)
fs = append(fs, FileInfo{Name: v["file_name"], Size: v["file_size"], LastModify: v["last_modify"]})
}
data := Datafile{CurFiles: fs}
if isLogFile == false {
returnRes(ctx.ResponseWriter(), base_path, data)
} else {
returnRes(ctx.ResponseWriter(), "web/views/base_search.html", data)
}
}
// searchLogHandle 过滤界面
func (wbh *WebHandler) searchLogHandle(ctx iris.Context) {
returnRes(ctx.ResponseWriter(), "web/views/search_log.html", nil)
return
}
// downLogHandle 过滤界面
func (wbh *WebHandler) downLogHandle(ctx iris.Context) {
filePath := strings.TrimSpace(ctx.URLParam("filePath"))
filePath = wbh.Directory + filePath
utils.Log.Infof(filePath)
fileBody, readErr := utils.ReadFile(filePath)
if readErr != nil {
utils.Log.Errorf("读取文件失败:%s, %v", filePath, readErr.Error())
returnDownErrorRes(ctx.ResponseWriter(), readErr.Error())
return
}
//_ = strings.Split(logs, ",")
//body := []byte("数据测试")
ctx.ResponseWriter().Header().Set("Content-Type", "application/octet-stream")
ctx.ResponseWriter().WriteHeader(http.StatusOK)
_, err := ctx.ResponseWriter().Write(fileBody)
if err != nil {
utils.Log.Errorf("写入数据失败:%s, %v", filePath, err.Error())
returnDownErrorRes(ctx.ResponseWriter(), err.Error())
}
return
}
package code
import (
"fmt"
"github.com/gorilla/websocket"
"log"
"strings"
"time"
"web_log/utils"
)
// websocket客户端
......@@ -19,9 +19,9 @@ type Client struct {
type clientManager struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
filemap map[string]map[Client]bool
register chan *Client //注册通道
unregister chan *Client //注销通道
filemap map[string]map[Client]bool //存储文件对象对应的客户端对象
}
var manager = clientManager{
......@@ -37,40 +37,42 @@ func getfilemap() map[string]map[Client]bool {
return file_map
}
// 死循环,负责处理通道
func (manager *clientManager) start() {
defer func() {
if err := recover(); err != nil {
log.Printf("[seelog] error:%+v", err)
utils.Log.Errorf("[seelog] error:%+v", err)
}
}()
for {
select {
// 注册通道接收到客户端对象
case conn := <-manager.register:
manager.clients[conn] = true
fmt.Printf("A client connect, current clients count is %d \n", len(manager.clients))
utils.Log.Infof("A client connect, current clients count is %d", len(manager.clients))
// 注销通道接收到客户端对象
case conn := <-manager.unregister:
if _, ok := manager.clients[conn]; ok {
close(conn.Send)
delete(manager.clients, conn)
for _, map_tmp := range manager.filemap {
delete(map_tmp, *conn)
close(conn.Send) //关闭发送通道
delete(manager.clients, conn) //删除存储对象
for _, mapTmp := range manager.filemap {
delete(mapTmp, *conn) // 从fileMap删除客户端对象
}
fmt.Printf("A client disconnect, current clients count is %d \n", len(manager.clients))
utils.Log.Infof("A client disconnect, current clients count is %d", len(manager.clients))
}
// 消息通道接收到消息
case message := <-manager.broadcast:
str_msg := string(message)
tmp := strings.Split(str_msg, "$$")
strMsg := string(message)
tmp := strings.Split(strMsg, "$$")
filename := tmp[0]
msg := tmp[1]
map_tmp := manager.filemap[filename]
for conn, _ := range map_tmp {
mapTmp := manager.filemap[filename]
for conn, _ := range mapTmp {
select {
// 将消息发送到客户端的Send通道
case conn.Send <- []byte(msg):
default:
close(conn.Send)
delete(manager.clients, &conn)
}
}
......@@ -86,14 +88,14 @@ func (manager *clientManager) start() {
}
}
// ws 写方法
func (c *Client) write() {
//defer func() {
// manager.unregister <- c
// c.Socket.Close()
//}()
// 写消息
if err := c.Socket.WriteMessage(websocket.TextMessage, []byte(c.Initstr)); err != nil {
return
}
// 死循环,监听ws客户端的发送通道,接收到关闭信号则退出循环
for {
select {
case message, ok := <-c.Send:
......@@ -106,6 +108,7 @@ func (c *Client) write() {
}
}
//ws 读方法,接收前段通过同一个ws客户端发过来的消息
func (c *Client) read() {
defer func() {
manager.unregister <- c
......@@ -114,74 +117,18 @@ func (c *Client) read() {
for {
_, reply, err := c.Socket.ReadMessage()
if err != nil {
fmt.Println("Error! Can't receive message...")
utils.Log.Error("Error! Can't receive message...")
break
}
// 前端会有定时器,定时发送heart消息
if string(reply) != "heart" {
manager.unregister <- c
c.Socket.Close()
} else {
fmt.Println("heart package...")
}
//reply = []byte
//time.Sleep(2000 * time.Millisecond)
time.Sleep(5000 * time.Millisecond)
//jsonMessage, _ := json.Marshal(&Message{Sender: c.Id, Content: string(message)})
//manager.broadcast <- jsonMessage
}
}
//func (c *client) monitor(filePath string, filename string) {
// defer func() {
// if err := recover(); err != nil {
// log.Printf("[seelog] error:%+v", err)
// }
// }()
// var fileInfo os.FileInfo
// var err error
// for i := 1; i <= 10; i++ {
// fileInfo, err = os.Stat(filePath)
// if err != nil {
// log.Printf("[seelog] error:%v", err.Error())
// continue
// }
// break
// }
//
// offset := fileInfo.Size()
// for {
// fileInfo, err = os.Stat(filePath)
// if err != nil {
// log.Printf("[seelog] error:%v", err.Error())
// continue
// }
// newOffset := fileInfo.Size()
// if offset < newOffset {
// msg := make([]byte, newOffset-offset)
// file, err := os.Open(filePath)
// if err != nil {
// log.Printf("[seelog] error:%v", err.Error())
// continue
// }
// _, err = file.Seek(offset, 0)
// if err != nil {
// log.Printf("[seelog] error:%v", err.Error())
// }
//
// _, err = file.Read(msg)
// if err != nil {
// log.Printf("[seelog] error:%v", err.Error())
// }
// str_msg := string(msg)
// s := filename + "$$" + str_msg
//
// manager.broadcast <- []byte(s)
// offset = newOffset
// file.Close()
// }
// offset = newOffset
// time.Sleep(200 * time.Millisecond)
// }
//
//}
......@@ -20,7 +20,7 @@ func GetCurrentPath() string {
return strings.Replace(dir, "\\", "/", -1)
}
func InitConfig() {
flag.StringVar(&Directory, "d", "/data/inotify_logs", "监听目录")
flag.StringVar(&Directory, "d", "/data", "监听目录")
flag.StringVar(&Port, "p", "9997", "监听端口")
flag.Parse()
_, err := os.Stat(Directory)
......
{
"weblog": "824d8eb068e6917d21e8d39db9edffe7"
"weblog": "e10adc3949ba59abbe56e057f20f883e"
}
\ No newline at end of file
......@@ -4,29 +4,35 @@ go 1.14
require (
github.com/CloudyKit/jet/v3 v3.0.1 // indirect
github.com/gin-gonic/gin v1.6.3 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/gorilla/websocket v1.4.2
github.com/ajg/form v1.5.1 // indirect
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/websocket v1.5.0
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/iris-contrib/jade v1.1.4 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/kataras/golog v0.1.6 // indirect
github.com/kataras/iris/v12 v12.1.8
github.com/klauspost/compress v1.11.4 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/microcosm-cc/bluemonday v1.0.4 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/moul/http2curl v1.0.0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.19.0 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/ugorji/go v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
golang.org/x/text v0.3.4 // indirect
google.golang.org/protobuf v1.25.0 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/valyala/fasthttp v1.35.0 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/yudai/gojsondiff v1.0.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
go.uber.org/zap v1.21.0
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
This diff is collapsed. Click to expand it.
......@@ -3,6 +3,8 @@ package main
import (
"fmt"
"github.com/kataras/iris/v12"
"go.uber.org/zap/zapcore"
"runtime"
"web_log/code"
"web_log/config"
"web_log/utils"
......@@ -12,6 +14,12 @@ import (
func main() {
config.InitConfig()
sysType := runtime.GOOS
if sysType == "windows" {
utils.LogSetUp(zapcore.DebugLevel, false)
} else {
utils.LogSetUp(zapcore.DebugLevel, true)
}
addrs := "0.0.0.0:" + config.Port
fmt.Printf("查看日志目录: %s \n用户配置文件: %s\n", config.Directory, config.UserConfigPath)
......
package utils
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"os"
"time"
)
var Log *zap.SugaredLogger //在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。
//在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。
func LogSetUp(logLevel zapcore.Level, isLinux bool) {
config := zapcore.EncoderConfig{
MessageKey: "msg", //结构化(json)输出:msg的key
LevelKey: "level", //结构化(json)输出:日志级别的key(INFO,WARN,ERROR等)
TimeKey: "time", //结构化(json)输出:时间的key(INFO,WARN,ERROR等)
CallerKey: "file", //结构化(json)输出:打印日志的文件对应的Key
StacktraceKey: "stacktrace", //结构化(json)输出:堆栈信息的key
EncodeLevel: zapcore.CapitalLevelEncoder, //将日志级别转换成大写(INFO,WARN,ERROR等)
EncodeCaller: zapcore.ShortCallerEncoder, //采用短文件路径编码输出(test/main.go:14 )
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}, //输出的时间格式
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(int64(d) / 1000000)
},
}
encoder := zapcore.NewJSONEncoder(config)
debugLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.DebugLevel
})
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.InfoLevel
})
//自定义日志级别:自定义Error级别
errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.WarnLevel && lvl >= logLevel
})
var core zapcore.Core
// 获取io.Writer的实现
if isLinux {
logPath := "/data/liexin_logs/web_log/"
debugWriter := getLogWriter(logPath + "debug.log")
infoWriter := getLogWriter(logPath + "info.log")
errorWriter := getLogWriter(logPath + "error.log")
// 实现多个输出
core = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(debugWriter), debugLevel),
zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoWriter), infoLevel), //将info写入infoPath
zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorWriter), errorLevel), //warn及以上写入errPath
)
} else {
core = zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), logLevel) //将日志输出到控制台
}
Log = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)).Sugar()
return
}
func getLogWriter(filename string) zapcore.WriteSyncer {
lumberJackLogger := zapcore.AddSync(&lumberjack.Logger{
Filename: filename,
MaxSize: 100,
MaxBackups: 10,
MaxAge: 1,
Compress: false,
LocalTime: true,
})
return lumberJackLogger
}
package utils
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"os/exec"
"runtime"
"sort"
"strconv"
)
// 判断所给路径是否为文件夹
func IsDir(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
......@@ -55,6 +68,7 @@ func Unwrap(num int64, retain int) float64 {
func PathFileInfo(path string) ([]map[string]string, error) {
files, err := ioutil.ReadDir(path)
if err != nil {
fmt.Println(err.Error())
return nil, err
}
var filesinfo = make([]map[string]interface{}, 0)
......@@ -96,3 +110,62 @@ func RunEnv() bool {
return true
}
}
// 使用linux命令获取结果
func GetLinuxResult(linuxCmd string) (string, error) {
cmd := exec.Command("/bin/bash", "-c", linuxCmd)
//创建获取命令输出管道
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("Error:can not obtain stdout pipe for command:%s\n", err)
return "", err
}
//执行命令
if err := cmd.Start(); err != nil {
fmt.Println("Error:The command is err,", err)
return "", err
}
//读取所有输出
bytes, err := ioutil.ReadAll(stdout)
if err != nil {
fmt.Println("ReadAll Stdout:", err.Error())
return "", err
}
if err := cmd.Wait(); err != nil {
fmt.Println("wait:", err.Error())
return "", err
}
//fmt.Printf("stdout:\n\n %s", bytes)
return string(bytes), nil
}
func ReadFile(filePath string) ([]byte, error) {
// 创建句柄
fi, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer fi.Close()
// 创建 Reader
r := bufio.NewReader(fi)
s := ""
// 每次读取 1024 个字节
buf := make([]byte, 1024)
for {
//func (b *Reader) Read(p []byte) (n int, err error) {}
n, err := r.Read(buf)
if err != nil && err != io.EOF {
return nil, err
}
if n == 0 {
break
}
s += string(buf[:n])
}
return []byte(s), nil
}
......@@ -8,7 +8,7 @@
</head>
<body>
<div style="font-size: 20px" >{{ .Htmltitle }}</div>
<div style="font-size: 20px" >请选择</div>
<div id="show" style="overflow-y: scroll;">
</div>
......@@ -23,24 +23,28 @@
if (1024>sz){
sz = sz.toFixed(3)
sz +='K'
}else {
} else {
sz /= 1024
sz = sz.toFixed(3)
sz+= 'M'
}
var f_name = "<label class='tmp' style='margin-right: 1px'>" + {{ .Name }} + "</label>"
{{/* 如果 url里面没有server_direc这个参数,说明还没进到选文件的页面,也就是展示的列表全是文件夹,文件夹不予显示大小*/}}
if ( window.location.href.indexOf("server_direc")==-1){
sz = ""
}
var f_size = "<label class='size' style='margin-right: 100px'>" + sz + "</label>"
var f_size = "<label class='size' style='margin-right: 100px'></label>"
var f_lastmodify ="<label>" + (new Date(lastmodify*1000)).toLocaleString() + "</label>"
var f_info = f_name+f_size+f_lastmodify
$('#show').append("<a style='padding: 10px' href=" + window.location.href + {{ $.UrlLocation }} +{{ .Name }} + ">"+ f_info +"</a>")
$('#show').append("<a id='file' onclick='fileClick(this)'>"+ f_info +"</a>")
{{ end }}
function fileClick(ele) {
const text = $(ele).find(".tmp")[0].innerText
if ( window.location.href.indexOf("filePath")===-1){
window.location.href += "?filePath=/" + text
} else {
window.location.href = window.location.href + "/" + text
}
}
</script>
......@@ -57,26 +61,26 @@
margin: 0;
background-color: #cccccc;
}
a:active,
a:visited,
a:link {
display: block;
text-decoration: none;
margin: 6px 10px 6px 0px;
padding: 2px 6px 2px 6px;
color: #00527f;
background-color: #d9e8f3;
border: 1px solid #004266;
}
a:hover {
color: red;
background-color: #8cbbda;
}
/*a:active,*/
/*a:visited,*/
/*a:link {*/
/* display: block;*/
/* text-decoration: none;*/
/* margin: 6px 10px 6px 0px;*/
/* padding: 2px 6px 2px 6px;*/
/* color: #00527f;*/
/* background-color: #d9e8f3;*/
/* border: 1px solid #004266;*/
/*}*/
/*a:hover {*/
/* color: red;*/
/* background-color: #8cbbda;*/
/*}*/
label{
display:inline-block;
width:200px;
text-align:right;
text-align:left;
}
.tmp{
display:inline-block;
......@@ -84,6 +88,15 @@
text-align:left;
}
#file {
padding: 10px;
border: 1px solid #004266;
background-color:#d9e8f3;
display: block;
margin:6px 10px 6px 0px;
color:#00527f;
}
</style >
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>选择</title>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<div style="font-size: 20px" >请选择</div>
<div id="show" style="overflow-y: scroll;">
</div>
<label ></label>
<script>
String.prototype.endWith=function(endStr){undefined
const d = this.length - endStr.length;
return (d>=0&&this.lastIndexOf(endStr)===d);
}
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (let i=0; i<vars.length; i++) {
const pair = vars[i].split("=");
if(pair[0] === variable){return pair[1];}
}
return false;
}
function downLoad(url) {
const xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "blob";
xhr.onload = function () {
const blob = this.response;
const fileName = url.split("/")[url.split("/").length - 2]+"/"+url.split("/")[url.split("/").length - 1]; // 导出文件名
let a = document.createElement("a");
a.download = fileName;
a.style.display = "none";
a.href = window.URL.createObjectURL(blob);
a.target = "_blank";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(a.href); // 释放URL 对象
document.body.removeChild(a); // 删除 a 标签
};
xhr.send();
}
$("#show").height(document.documentElement.scrollHeight-50)
$('#show').append("<span><label class='key'>过滤关键词</label><input id='keyword' type='text'><button id='but'>过滤</button><button id='download'>下载文件</button></span><label class='desc'>请输入关键词,查询方法采用grep原生方法, 下载文件反应时间和文件大小有关,请耐心等待</label>")
//
{{ range .CurFiles }}
var lastmodify = {{ .LastModify }}
var sz = {{ .Size }}
sz = sz/1024
if (1024>sz){
sz = sz.toFixed(3)
sz +='K'
} else {
sz /= 1024
sz = sz.toFixed(3)
sz+= 'M'
}
var f_name = "<label class='name'>" + {{ .Name }}+"</label>"
var f_size = "<label class='size' style='margin-right: 100px'>" + sz + "</label>"
var f_lastmodify ="<label>" + (new Date(lastmodify*1000)).toLocaleString() + "</label>"
var f_info = f_name+f_size+f_lastmodify
$('#show').append("<span class='checkSpan'><input type='checkbox' name='check' class='tmp' style='margin-right: 1px'><a id='file' onclick='fileClick(this)'>"+f_info+"</a></span>")
{{ end }}
$("#but").click(function () {
const checkSpanArr = document.getElementsByClassName("checkSpan");
const logs = [];
for (let i = 0; i < checkSpanArr.length; i++) {
const tmp = checkSpanArr[i].getElementsByClassName("tmp")[0];
if (tmp.checked === true){
logs.push(checkSpanArr[i].getElementsByClassName("name")[0].textContent)
}
}
if (logs.length === 0){
alert("请勾选需要查询的日志")
return
}
const keyword = encodeURIComponent(document.getElementById("keyword").value)
const filePath = getQueryVariable("filePath")
window.location.href = "/searchLog?keyword=" + keyword + "&filePath="+filePath+"&logs=" + logs
});
$("#download").click(function () {
const checkSpanArr = document.getElementsByClassName("checkSpan");
const logs = [];
for (let i = 0; i < checkSpanArr.length; i++) {
const tmp = checkSpanArr[i].getElementsByClassName("tmp")[0];
if (tmp.checked === true){
logs.push(checkSpanArr[i].getElementsByClassName("name")[0].textContent)
const size = checkSpanArr[i].getElementsByClassName("size")[0].textContent;
if (size.indexOf("M")!==-1){
if (parseFloat(size) > 20){
alert("暂不支持下载超过20M的日志")
return
}
}
}
}
if (logs.length === 0){
alert("请勾选需要下载的日志")
return
}
if (logs.length > 1){
alert("请只勾选一个需要下载的日志")
return
}
const filePath = getQueryVariable("filePath")
var href = "http://" + window.location.host + "/downLog?filePath="+filePath+"/"+logs[0]
downLoad(href)
// window.open(href);
});
function fileClick(ele) {
const text = $(ele).find(".name")[0].innerText
console.log(text)
if ( window.location.href.indexOf("filePath")===-1){
window.location.href += "?filePath=/" + text
} else {
window.location.href = window.location.href + "/" + text
}
}
</script>
<style>
html{
width: 100%;
height: 100%;
overflow: hidden;
}
body{
width: 100%;
height: 100%;
font-family: 'Open Sans',sans-serif;
margin: 0;
background-color: #cccccc;
}
#keyword{
background-color: lightyellow;
color: black;
font-size: medium;
height: 25px;
width: 300px;
margin-left: 22px;
}
#log_path{
background-color: lightyellow;
color: black;
font-size: medium;
height: 25px;
width: 500px;
margin-left: 22px;
}
#but{
width: 58px;
height: 30px;
margin-left: 22px;
font-size: 15px;
background-color: lightblue
}
#download{
width: 116px;
height: 30px;
margin-left: 22px;
font-size: 15px;
background-color: lightblue
}
label{
display:inline-block;
width:200px;
text-align:right;
}
.key {
display:inline-block;
width:auto;
margin-left: 10px;
text-align:right;
}
.log {
display:inline-block;
width:auto;
margin-left: 30px;
text-align:right;
}
.tmp{
display:inline-block;
width:25px;
text-align:left;
}
.checkSpan {
padding: 10px;
border: 1px solid #004266;
background-color:#d9e8f3;
display: block;
margin:6px 10px 6px 0px;
color:#00527f;
}
.name {
width: 500px;
text-align: left;
}
#file {
color: #4bbdff;
}
.desc {
margin-left: 20px;
width: auto;
}
</style >
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
alert("下载文件出错: {{ . }}重定向到首页")
window.location.href = "index"
</script>
</body>
</html>
\ No newline at end of file
......@@ -17,8 +17,7 @@
var ws = new WebSocket("ws://" + window.location.host + "/ws?file={{ . }}");
ws.onmessage = function (e) {
console.log(filterText)
snsArr = e.data.split(/[(\r\n)\r\n]+/);
snsArr = e.data.split(/[\r\n]/);
snsArr.forEach((item, index) => {
if (!item) {
snsArr.splice(index, 1);//删除空项
......@@ -26,23 +25,26 @@
})
snsArr.forEach((item, index) => {
if (out && (filterText == "" || item.indexOf(filterText) != -1)) {
$('#log').append("<pre style='color: white;font-size: 15px;margin: 3px'>" + item + "</pre>").scrollTop($('#log')[0].scrollHeight)
$('#log').append("<span style='color: white;font-size: 15px;margin: 3px'>" + item + "</span><br><br>").scrollTop($('#log')[0].scrollHeight)
}else {
$('#log').append("<pre style='display:none;color: white;font-size: 15px;margin: 3px'>" + item + "</pre>").scrollTop($('#log')[0].scrollHeight)
$('#log').append("<span style='display:none;color: white;font-size: 15px;margin: 3px'>" + item + "</span><br><br>").scrollTop($('#log')[0].scrollHeight)
}
})
};
ws.onclose = function () {
$('#status').css("background-color", "red").text("链接断开")
console.log("onclose")
reConnect()
}
ws.onopen = function () {
$('#status').css("background-color", "chartreuse").text("连接成功")
$('#clear').click()
// $('#clear').click()
console.log("onopen")
}
ws.onerror = function (e) {
$('#status').css("background-color", "red").text("链接断开")
console.log("onerror")
}
// function send_heart() {
// ws.send("heart")
......@@ -84,11 +86,11 @@
$('#filter').on('input', function () {
filterText = $('#filter').val()
$('#log pre').each(function () {
$('#log span').each(function () {
$(this).attr("style", "display:block;color: white;font-size: 15px;margin: 3px")
})
if (filterText != ""){
$('#log pre').each(function () {
$('#log span').each(function () {
if ($(this).text().indexOf(filterText) == -1) {
$(this).attr("style", "display:none")
}
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查询日志</title>
<link rel="shortcut icon" href="http://www.eiguo.cn/favicon.ico" type="image/x-icon"/>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.min.js"></script>
</head>
<body>
<div id="show" style="overflow-y: scroll;"></div>
<div id="searchResult"></div>
<script>
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] === variable){return pair[1];}
}
return false;
}
const keyword = decodeURI(getQueryVariable("keyword"));
const filePath = getQueryVariable("filePath");
const logs = getQueryVariable("logs");
$('#show').append("<span><label class='key'>过滤关键词</label><input id='keyword' type='text' value='"+keyword+"'><button id='but'>过滤</button></span>")
const ws = new WebSocket("ws://" + window.location.host + "/wsSearchLog?keyword="+keyword+"&filePath="+filePath+"&logs="+logs);
ws.onmessage = function (e) {
let snsArr = e.data.split(/[\r\n]/);
snsArr.forEach((item, index) => {
if (!item) {
snsArr.splice(index, 1);//删除空项
}
})
snsArr.forEach((item, index) => {
$('#searchResult').append("<span style='color: white;font-size: 15px;margin: 3px'>" + item + "</span><br><br>").scrollTop($('#searchResult')[0].scrollHeight)
})
}
$("#but").click(function () {
const butKeyword = encodeURIComponent(document.getElementById("keyword").value)
window.location.href = "/searchLog?keyword=" + butKeyword + "&filePath="+filePath+"&logs=" + logs
});
</script>
</body>
<style>
body {
margin-left: 2%
}
#searchResult {
width: 98%;
height: 100%;
background-color: #181818;
border: 1px #ccc solid;
overflow-y: scroll;
margin-top: 10px;
padding-left: 12px;
float: left;
}
#keyword{
background-color: lightyellow;
color: black;
font-size: medium;
height: 25px;
width: 300px;
margin-left: 22px;
}
#but{
width: 58px;
height: 30px;
margin-left: 22px;
font-size: 15px;
background-color: lightblue
}
label{
display:inline-block;
width:200px;
text-align:right;
}
.key {
display:inline-block;
width:auto;
margin-left: 10px;
text-align:right;
}
.log {
display:inline-block;
width:auto;
margin-left: 30px;
text-align:right;
}
</style>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment