| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 | package nginximport (	"encoding/json"	"github.com/0xJacky/Nginx-UI/api"	"github.com/0xJacky/Nginx-UI/internal/helper"	"github.com/0xJacky/Nginx-UI/internal/logger"	"github.com/0xJacky/Nginx-UI/internal/nginx"	"github.com/gin-gonic/gin"	"github.com/gorilla/websocket"	"github.com/hpcloud/tail"	"github.com/pkg/errors"	"github.com/spf13/cast"	"io"	"net/http"	"os")const (	PageSize = 128 * 1024)type controlStruct struct {	Type         string `json:"type"`	ConfName     string `json:"conf_name"`	ServerIdx    int    `json:"server_idx"`	DirectiveIdx int    `json:"directive_idx"`}type nginxLogPageResp struct {	Content string `json:"content"`	Page    int64  `json:"page"`}func GetNginxLogPage(c *gin.Context) {	page := cast.ToInt64(c.Query("page"))	if page < 0 {		page = 0	}	var control controlStruct	if !api.BindAndValid(c, &control) {		return	}	logPath, err := getLogPath(&control)	if err != nil {		logger.Error(err)		return	}	f, err := os.Open(logPath)	if err != nil {		c.JSON(http.StatusOK, nginxLogPageResp{})		logger.Error(err)		return	}	logFileStat, err := os.Stat(logPath)	if err != nil {		c.JSON(http.StatusOK, nginxLogPageResp{})		logger.Error(err)		return	}	totalPage := logFileStat.Size() / PageSize	if logFileStat.Size()%PageSize > 0 {		totalPage++	}	var buf []byte	var offset int64	if page == 0 {		page = totalPage	}	buf = make([]byte, PageSize)	offset = (page - 1) * PageSize	// seek	_, err = f.Seek(offset, io.SeekStart)	if err != nil && err != io.EOF {		c.JSON(http.StatusOK, nginxLogPageResp{})		logger.Error(err)		return	}	n, err := f.Read(buf)	if err != nil && err != io.EOF {		c.JSON(http.StatusOK, nginxLogPageResp{})		logger.Error(err)		return	}	c.JSON(http.StatusOK, nginxLogPageResp{		Page:    page,		Content: string(buf[:n]),	})}func getLogPath(control *controlStruct) (logPath string, err error) {	switch control.Type {	case "site":		var config *nginx.NgxConfig		path := nginx.GetConfPath("sites-available", control.ConfName)		config, err = nginx.ParseNgxConfig(path)		if err != nil {			err = errors.Wrap(err, "error parsing ngx config")			return		}		if control.ServerIdx >= len(config.Servers) {			err = errors.New("serverIdx out of range")			return		}		if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {			err = errors.New("DirectiveIdx out of range")			return		}		directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]		switch directive.Directive {		case "access_log", "error_log":			// ok		default:			err = errors.New("directive.Params neither access_log nor error_log")			return		}		if directive.Params == "" {			err = errors.New("directive.Params is empty")			return		}		logPath = directive.Params	case "error":		path := nginx.GetErrorLogPath()		if path == "" {			err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +				" refer to https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information")			return		}		logPath = path	default:		path := nginx.GetAccessLogPath()		if path == "" {			err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," +				" refer to https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information")			return		}		logPath = path	}	return}func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {	defer func() {		if err := recover(); err != nil {			logger.Error(err)			return		}	}()	control := <-controlChan	for {		logPath, err := getLogPath(&control)		if err != nil {			errChan <- err			return		}		seek := tail.SeekInfo{			Offset: 0,			Whence: io.SeekEnd,		}		if !helper.FileExists(logPath) {			errChan <- errors.New("error log path not exists " + logPath)			return		}		// Create a tail		t, err := tail.TailFile(logPath, tail.Config{Follow: true,			ReOpen: true, Location: &seek})		if err != nil {			errChan <- errors.Wrap(err, "error tailing log")			return		}		for {			var next = false			select {			case line := <-t.Lines:				// Print the text of each received line				if line == nil {					continue				}				err = ws.WriteMessage(websocket.TextMessage, []byte(line.Text))				if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {					errChan <- errors.Wrap(err, "error tailNginxLog write message")					return				}			case control = <-controlChan:				next = true				break			}			if next {				break			}		}	}}func handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {	defer func() {		if err := recover(); err != nil {			logger.Error(err)			return		}	}()	for {		msgType, payload, err := ws.ReadMessage()		if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {			errChan <- errors.Wrap(err, "error handleLogControl read message")			return		}		if msgType != websocket.TextMessage {			errChan <- errors.New("error handleLogControl message type")			return		}		var msg controlStruct		err = json.Unmarshal(payload, &msg)		if err != nil {			errChan <- errors.Wrap(err, "error ReadWsAndWritePty json.Unmarshal")			return		}		controlChan <- msg	}}func Log(c *gin.Context) {	var upGrader = websocket.Upgrader{		CheckOrigin: func(r *http.Request) bool {			return true		},	}	// upgrade http to websocket	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)	if err != nil {		logger.Error(err)		return	}	defer ws.Close()	errChan := make(chan error, 1)	controlChan := make(chan controlStruct, 1)	go tailNginxLog(ws, controlChan, errChan)	go handleLogControl(ws, controlChan, errChan)	if err = <-errChan; err != nil {		logger.Error(err)		_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))		return	}}
 |