mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-26 00:36:12 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			252 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package log
 | |
| 
 | |
| import (
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"sync/atomic"
 | |
| 	"time"
 | |
| 	"unsafe"
 | |
| 
 | |
| 	"github.com/1Panel-dev/1Panel/core/constant"
 | |
| 
 | |
| 	"github.com/1Panel-dev/1Panel/core/global"
 | |
| )
 | |
| 
 | |
| type Writer struct {
 | |
| 	m             Manager
 | |
| 	file          *os.File
 | |
| 	absPath       string
 | |
| 	fire          chan string
 | |
| 	cf            *Config
 | |
| 	rollingfilech chan string
 | |
| }
 | |
| 
 | |
| type AsynchronousWriter struct {
 | |
| 	Writer
 | |
| 	ctx     chan int
 | |
| 	queue   chan []byte
 | |
| 	errChan chan error
 | |
| 	closed  int32
 | |
| 	wg      sync.WaitGroup
 | |
| }
 | |
| 
 | |
| func (w *AsynchronousWriter) Close() error {
 | |
| 	if atomic.CompareAndSwapInt32(&w.closed, 0, 1) {
 | |
| 		close(w.ctx)
 | |
| 		w.onClose()
 | |
| 
 | |
| 		func() {
 | |
| 			defer func() {
 | |
| 				if r := recover(); r != nil {
 | |
| 					global.LOG.Error(r)
 | |
| 				}
 | |
| 			}()
 | |
| 			w.m.Close()
 | |
| 		}()
 | |
| 		return w.file.Close()
 | |
| 	}
 | |
| 	return ErrClosed
 | |
| }
 | |
| 
 | |
| func (w *AsynchronousWriter) onClose() {
 | |
| 	var err error
 | |
| 	for {
 | |
| 		select {
 | |
| 		case b := <-w.queue:
 | |
| 			if _, err = w.file.Write(b); err != nil {
 | |
| 				select {
 | |
| 				case w.errChan <- err:
 | |
| 				default:
 | |
| 					_asyncBufferPool.Put(&b)
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 			_asyncBufferPool.Put(&b)
 | |
| 		default:
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var _asyncBufferPool = sync.Pool{
 | |
| 	New: func() interface{} {
 | |
| 		return make([]byte, BufferSize)
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func NewWriterFromConfig(c *Config) (RollingWriter, error) {
 | |
| 	if c.LogPath == "" || c.FileName == "" {
 | |
| 		return nil, ErrInvalidArgument
 | |
| 	}
 | |
| 	if err := os.MkdirAll(c.LogPath, 0700); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	filepath := FilePath(c)
 | |
| 	file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, constant.FilePerm)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if err := dupWrite(file); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	mng, err := NewManager(c)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var rollingWriter RollingWriter
 | |
| 	writer := Writer{
 | |
| 		m:       mng,
 | |
| 		file:    file,
 | |
| 		absPath: filepath,
 | |
| 		fire:    mng.Fire(),
 | |
| 		cf:      c,
 | |
| 	}
 | |
| 	if c.MaxRemain > 0 {
 | |
| 		writer.rollingfilech = make(chan string, c.MaxRemain)
 | |
| 		dir, err := os.ReadDir(c.LogPath)
 | |
| 		if err != nil {
 | |
| 			mng.Close()
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		files := make([]string, 0, 10)
 | |
| 		for _, fi := range dir {
 | |
| 			if fi.IsDir() {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			fileName := c.FileName
 | |
| 			if strings.Contains(fi.Name(), fileName) && strings.Contains(fi.Name(), c.LogSuffix) {
 | |
| 				start := strings.Index(fi.Name(), "-")
 | |
| 				end := strings.Index(fi.Name(), c.LogSuffix)
 | |
| 				name := fi.Name()
 | |
| 				if start > 0 && end > 0 {
 | |
| 					_, err := time.Parse(c.TimeTagFormat, name[start+1:end])
 | |
| 					if err == nil {
 | |
| 						files = append(files, fi.Name())
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		sort.Slice(files, func(i, j int) bool {
 | |
| 			t1Start := strings.Index(files[i], "-")
 | |
| 			t1End := strings.Index(files[i], c.LogSuffix)
 | |
| 			t2Start := strings.Index(files[i], "-")
 | |
| 			t2End := strings.Index(files[i], c.LogSuffix)
 | |
| 			t1, _ := time.Parse(c.TimeTagFormat, files[i][t1Start+1:t1End])
 | |
| 			t2, _ := time.Parse(c.TimeTagFormat, files[j][t2Start+1:t2End])
 | |
| 			return t1.Before(t2)
 | |
| 		})
 | |
| 
 | |
| 		for _, file := range files {
 | |
| 		retry:
 | |
| 			select {
 | |
| 			case writer.rollingfilech <- path.Join(c.LogPath, file):
 | |
| 			default:
 | |
| 				writer.DoRemove()
 | |
| 				goto retry
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	wr := &AsynchronousWriter{
 | |
| 		ctx:     make(chan int),
 | |
| 		queue:   make(chan []byte, QueueSize),
 | |
| 		errChan: make(chan error, QueueSize),
 | |
| 		wg:      sync.WaitGroup{},
 | |
| 		closed:  0,
 | |
| 		Writer:  writer,
 | |
| 	}
 | |
| 
 | |
| 	wr.wg.Add(1)
 | |
| 	go wr.writer()
 | |
| 	wr.wg.Wait()
 | |
| 	rollingWriter = wr
 | |
| 
 | |
| 	return rollingWriter, nil
 | |
| }
 | |
| 
 | |
| func (w *AsynchronousWriter) writer() {
 | |
| 	var err error
 | |
| 	w.wg.Done()
 | |
| 	for {
 | |
| 		select {
 | |
| 		case filename := <-w.fire:
 | |
| 			if err = w.Reopen(filename); err != nil && len(w.errChan) < cap(w.errChan) {
 | |
| 				w.errChan <- err
 | |
| 			}
 | |
| 		case b := <-w.queue:
 | |
| 			if _, err = w.file.Write(b); err != nil && len(w.errChan) < cap(w.errChan) {
 | |
| 				w.errChan <- err
 | |
| 			}
 | |
| 			_asyncBufferPool.Put(&b)
 | |
| 		case <-w.ctx:
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (w *Writer) DoRemove() {
 | |
| 	file := <-w.rollingfilech
 | |
| 	if err := os.Remove(file); err != nil {
 | |
| 		log.Println("error in remove log file", file, err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (w *Writer) Write(b []byte) (int, error) {
 | |
| 	var ok = false
 | |
| 	for !ok {
 | |
| 		select {
 | |
| 		case filename := <-w.fire:
 | |
| 			if err := w.Reopen(filename); err != nil {
 | |
| 				return 0, err
 | |
| 			}
 | |
| 		default:
 | |
| 			ok = true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fp := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&w.file)))
 | |
| 	file := (*os.File)(fp)
 | |
| 	return file.Write(b)
 | |
| }
 | |
| 
 | |
| func (w *Writer) Reopen(file string) error {
 | |
| 	fileInfo, err := w.file.Stat()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if fileInfo.Size() == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	w.file.Close()
 | |
| 	if err := os.Rename(w.absPath, file); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	newFile, err := os.OpenFile(w.absPath, DefaultFileFlag, DefaultFileMode)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	w.file = newFile
 | |
| 
 | |
| 	go func() {
 | |
| 		if w.cf.MaxRemain > 0 {
 | |
| 		retry:
 | |
| 			select {
 | |
| 			case w.rollingfilech <- file:
 | |
| 			default:
 | |
| 				w.DoRemove()
 | |
| 				goto retry
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 	return nil
 | |
| }
 |