Как я могу войти в golang в файл с вращением журнала?

Я пытаюсь написать веб-приложение, которое будет работать на удаленном сервере. Мне нужно регистрироваться для захвата ошибок/отладки/аудита. Я обнаружил, что для голанга доступны несколько пакетов журналов, включая стандартный пакет "log". Однако мне нужно выполнить три требования:

  • Файлы журналов необходимо повернуть
  • Это относится к включенным пакетам которые используют "журнал"
  • Он должен быть кросс-платформенным. Окружение Dev Linux и должен быть развернут в Windows.

Ответ 1

Лучший способ выполнить все ваши три требования вместо создания альтернативной структуры logger, если вы были удовлетворены с помощью log.log на базовом уровне, вместо этого следует настроить вывод журнала на свой собственный экземпляр io.Writer.

Итак, в основном, что я собираюсь сделать здесь, это пример, когда я создаю свой собственный io.Writer:

import (
    "os"
    "sync"
    "time"
)

type RotateWriter struct {
    lock     sync.Mutex
    filename string // should be set to the actual filename
    fp       *os.File
}

// Make a new RotateWriter. Return nil if error occurs during setup.
func New(filename string) *RotateWriter {
    w := &RotateWriter{filename: filename}
    err := w.Rotate()
    if err != nil {
        return nil
    }
    return w
}

// Write satisfies the io.Writer interface.
func (w *RotateWriter) Write(output []byte) (int, error) {
    w.lock.Lock()
    defer w.lock.Unlock()
    return w.fp.Write(output)
}

// Perform the actual act of rotating and reopening file.
func (w *RotateWriter) Rotate() (err error) {
    w.lock.Lock()
    defer w.lock.Unlock()

    // Close existing file if open
    if w.fp != nil {
        err = w.fp.Close()
        w.fp = nil
        if err != nil {
            return
        }
    }
    // Rename dest file if it already exists
    _, err = os.Stat(w.filename)
    if err == nil {
        err = os.Rename(w.filename, w.filename+"."+time.Now().Format(time.RFC3339))
        if err != nil {
            return
        }
    }

    // Create a file.
    w.fp, err = os.Create(w.filename)
    return
}

Затем вы создаете RotateWriter и используете log.SetOutput для установки этого сценария (если другие пакеты используют стандартный экземпляр журнала) или поочередно создайте свои собственные экземпляры, используя log.New для прохождения.

Я не решил ситуацию, когда звонить повернуть, я оставлю это вам, чтобы решить. Было бы довольно просто вызвать его на основе времени или поочередно делать это после некоторого количества записей или некоторого количества байтов.

Ответ 2

Хотя @Crast дал очень хороший ответ, я хочу также обратить внимание на это - лесоруб регистратора Нейта Финча, который я в итоге и использовал.

Вот как это можно использовать:

  1. Сначала клонируйте хранилище дровосека ИЛИ каким-нибудь образом получите его.
  2. Запустите команду go install для папки.
  3. Теперь импортируйте go "log" package и "lumberjack package".

Импортировать (   "журнал"   "Github.com/natefinch/lumberjack"  )

  1. Теперь используйте его в своем коде так:

За пределами main объявите переменную журнала.

var errLog *log.Logger

Внутри основной:

e, err := os.OpenFile("./foo.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)

if err != nil {
    fmt.Printf("error opening file: %v", err)
    os.Exit(1)
}
errLog = log.New(e, "", log.Ldate|log.Ltime)
errLog.SetOutput(&lumberjack.Logger{
    Filename:   "./foo.log",
    MaxSize:    1,  // megabytes after which new file is created
    MaxBackups: 3,  // number of backups
    MaxAge:     28, //days
})

Теперь, как только размер файла составит 1 МБ, создается новый файл для сохранения предыдущих журналов с текущими временными метками, и новые журналы будут продолжать регистрироваться в файле foo.log. Кроме того, я создал файл, используя os.OpenFile, но он вам может не понадобиться, поскольку это делает внутренне дровосек, но я предпочел его таким образом. Спасибо, надеюсь, это поможет. Еще раз спасибо @Crast и NateFinch.

Ответ 3

Вот легкий взвешенный пакет регистрации, который поддерживает поворот журнала и автоматическую очистку

https://github.com/antigloss/go/tree/master/logger

// logger.Init must be called first to setup logger
logger.Init("./log", // specify the directory to save the logfiles
            400, // maximum logfiles allowed under the specified log directory
            20, // number of logfiles to delete when number of logfiles exceeds the configured limit
            100, // maximum size of a logfile in MB
            false) // whether logs with Trace level are written down
logger.Info("Failed to find player! uid=%d plid=%d cmd=%s xxx=%d", 1234, 678942, "getplayer", 102020101)
logger.Warn("Failed to parse protocol! uid=%d plid=%d cmd=%s", 1234, 678942, "getplayer")

Ответ 4

Один из вариантов, который приходит на ум, состоит в том, чтобы обернуть ведение журнала в вашем собственном типе и предоставить функцию перезагрузки, например:

type Logger struct {
    l *log.Logger
    f *os.File
    m sync.RWMutex
}

func NewLogger(fn string) (*Logger, error) {
    f, err := os.Create(fn)
    if err != nil {
        return nil, err
    }
    l := &Logger{
        l: log.New(f, "your-app", log.Lshortfile),
        f: f,
    }
    return l, nil
}

func (l *Logger) Logf(f string, args ...interface{}) {
    l.m.RLock()
    l.l.Printf(f, args...)
    l.m.RUnlock()
}

func (l *Logger) Reload() (err error) {
    l.m.Lock()
    defer l.m.Unlock()
    l.f.Close()
    if l.f, err = os.Create(l.f.Name()); err != nil {
        return
    }
    l.l = log.New(l.f, "your-app", log.Lshortfile)
    return
}

Затем либо прослушивайте сигнал (обычно -HUP on * nix), либо добавьте конечную точку в свое приложение, которое вызовет Logger.Reload().

Ответ 5

https://github.com/jame2981/log Мой пакет может вам помочь.

l1 := log.Pool.New("l1", "file:///tmp/test1.log")
l2 := log.Pool.New("l2", "file:///tmp/test2.log")
l3 := log.Pool.New("l3", "file:///tmp/test3.log")
l4 := log.Pool.New("l4", "file:///tmp/test4.log")

l1.Rotate() // rotate l1 only
log.Pool.Rotate() // was rotate all instances.

// rotate with signal
reopen := make(chan os.Signal, 1)
signal.Notify(reopen, syscall.SIGUSR1)
go func() {
    for{
       <-reopen
       l.Pool.Rotate()
    }
}()

установите std logger writer, чтобы повернуть работу еще.

// std logger writer
import "log"
logger := log.New("test", "", 0)
logger.SetOutput(l1.Writer())

Ответ 6

func (l *Logger) Logf(f string, args ...interface{}) {
    l.m.RLock()
    l.l.Printf(f, args...)
    l.m.RUnlock()
}

logger = log.New(l.f, "your-app", log.Lshortfile)

Когда этот регистратор вызывается из других файлов, файл log.Lshortfile всегда называет файл, в котором реализован регистратор. Как регистратор может взять имя файла, из которого вызывается Logf?