| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 | package nginx_logimport (	"encoding/json"	"io"	"net/http"	"os"	"runtime"	"github.com/0xJacky/Nginx-UI/internal/helper"	"github.com/0xJacky/Nginx-UI/internal/nginx"	"github.com/0xJacky/Nginx-UI/internal/nginx_log"	"github.com/gin-gonic/gin"	"github.com/gorilla/websocket"	"github.com/nxadm/tail"	"github.com/pkg/errors"	"github.com/uozi-tech/cosy/logger")// getLogPath resolves the log file path based on the provided control parameters// It checks if the path is under the whitelist directoriesfunc getLogPath(control *controlStruct) (logPath string, err error) {	// If direct log path is provided, use it	if control.LogPath != "" {		logPath = control.LogPath		// Check if logPath is under one of the paths in LogDirWhiteList		if !nginx_log.IsLogPathUnderWhiteList(logPath) {			return "", nginx_log.ErrLogPathIsNotUnderTheLogDirWhiteList		}		return	}	// Otherwise, use default log path based on type	switch control.Type {	case "error":		path := nginx.GetErrorLogPath()		if path == "" {			err = nginx_log.ErrErrorLogPathIsEmpty			return		}		logPath = path	case "access":		fallthrough	default:		path := nginx.GetAccessLogPath()		if path == "" {			err = nginx_log.ErrAccessLogPathIsEmpty			return		}		logPath = path	}	// check if logPath is under one of the paths in LogDirWhiteList	if !nginx_log.IsLogPathUnderWhiteList(logPath) {		return "", nginx_log.ErrLogPathIsNotUnderTheLogDirWhiteList	}	return}// tailNginxLog tails the specified log file and sends each line to the websocketfunc tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {	defer func() {		if err := recover(); err != nil {			buf := make([]byte, 1024)			runtime.Stack(buf, false)			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,		}		stat, err := os.Stat(logPath)		if os.IsNotExist(err) {			errChan <- errors.New("[error] Log path does not exist: " + logPath)			return		}		if !stat.Mode().IsRegular() {			errChan <- errors.Errorf("[error] %s is not a regular file. If you are using nginx-ui in docker container, please refer to https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information.", 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 {					if helper.IsUnexpectedWebsocketError(err) {						errChan <- errors.Wrap(err, "error tailNginxLog write message")					}					return				}			case control = <-controlChan:				next = true				break			}			if next {				break			}		}	}}// handleLogControl processes websocket control messagesfunc handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {	defer func() {		if err := recover(); err != nil {			buf := make([]byte, 1024)			runtime.Stack(buf, false)			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	}}// Log handles websocket connection for real-time log viewingfunc 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	}}
 |