Несколько вопросов по журналам:
Если один файл слишком велик, это повлияет на эффективность записи, поэтому он будет разделен, но в какой степени его следует разделить? Сколько максимум файлов журналов следует хранить? Сколько дней можно хранить максимум? Следует ли его сжимать?
Обычно используется lumberjack[1]Эта библиотека выполняет вышеуказанные операции
//информационный файл writeSyncer
infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/info.log", //бревно каталог хранения файлов, если папка не существует, то она будет создана автоматически
MaxSize: 2, //Ограничение размера файла, единица измерения МБ
MaxBackups: 100, //Максимальное количество сохраняемых файлов
MaxAge: 30, //Количество дней хранения файлов
Compress: false, //Обрабатывается ли сжатие
})
infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //Третий и последующие параметры — это уровень бревности записываемого файла. Режим ErrorLevel записывает только уровень бревности ошибки.
//файл ошибки writeSyncer
errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/error.log", //кратковременный каталог хранения файлов
MaxSize: 1, //Ограничение размера файла, единица измерения МБ
MaxBackups: 5, //Максимальное количество сохраняемых файлов
MaxAge: 30, //Количество дней хранения файлов
Compress: false, //Обрабатывается ли сжатие
})
Например,Когда уровень информации в файле достигает 2M,будет основано на текущей временной метке,вырезать одинinfo-2023-04-13T05-27-18.296.log
。 Впоследствии вновь записанные журналы информационного уровня будут записываться в info.log до тех пор, пока он снова не достигнет 2M, и будут продолжать сегментироваться.
Очистите папку журнала и измените конфигурацию журнала ошибок:
errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/error.log", //кратковременный каталог хранения файлов
MaxSize: 1, //Ограничение размера файла, единица измерения МБ
MaxBackups: 3, //Максимальное количество сохраняемых файлов
MaxAge: 30, //Количество дней хранения файлов
Compress: false, //Обрабатывается ли сжатие
})
В коде печатается только журнал ошибок, и код выполняется для наблюдения.
Продолжить выполнение
Продолжить выполнение
Видно, что самый ранний расколerror-2023-04-13T05-40-48.715.log
Файл отсутствует~
Продолжить выполнение,Количество разделенных файлов,всегда будет поддерживать 3
Полная схема изменений:
Очистите папку журнала и измените конфигурацию журнала ошибок:
errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/error.log", //кратковременный каталог хранения файлов
MaxSize: 5, //Ограничение размера файла, единица измерения МБ
MaxBackups: 10, //Максимальное количество сохраняемых файлов
MaxAge: 30, //Количество дней хранения файлов
Compress: false, //Обрабатывается ли сжатие
})
В коде печатается только журнал ошибок, выполните код, выполните цикл 10000000 раз и наблюдайте.
Без сжатия он занимает в общей сложности 814 МБ дискового пространства.
Очистите папку журнала, измените поле «Сжать» на значение true и выполните код:
После включения сжатия на диске занято всего 30М!
Плохо то, что просматривать напрямую неудобно и для просмотра приходится распаковывать. Зато это существенно экономит занимаемое пространство
golang zapбревно Использование библиотеки[2]
Библиотека лесоруба в настоящее время поддерживает обрезку только по размеру файла (резка по времени неэффективна и не может гарантировать, что данные журнала не будут уничтожены. Подробности см. https://github.com/natefinch/lumberjack/issues/54).
Если вы хотите сократить по дате, вы можете использоватьgithub.com/lestrrat-go/file-rotatelogs[3]Эта библиотека(На данный момент нет обслуживания)
Уведомление:
github.com/lestrrat-go/file-rotatelogs[4](2021После этого года обновлений больше не будет) и github.com/lestrrat/go-file-rotatelogs[5](2018Не будет обновляться через год) Эти двое разные. . Предыдущий обновлен, а автор один человек...
(В системах Linux существует инструмент журналирования, также называемый logrotate)
logrotate Используется ли один для ротации файлов журналов? Go Языковая библиотека,Поддержка ротации по времени、Поворот по размеру файла и Поворот по количеству строк. Также поддерживает сжатие файлов при вращении.、Удалить старые файлы、Добавляйте временные метки к файлам и другим функциям.
использоватьzapиgo-file-rotatelogsвыполнитьбревнозаписиибревно Разделить по времени[6]
Два параметра WithRotationCount и WithMaxAge не могут сосуществовать, и можно установить только один (оба параметра не будут вызывать ошибки при компиляции, но будут сообщать об ошибках во время выполнения. Это также необходимо для предотвращения влияния на логику обработки сегментации).:
panic: options MaxAge and RotationCount cannot be both set
package main
import (
"fmt"
"io"
"net/http"
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Используйте журналы ротации файлов для сегментации
var sugarLogger *zap.SugaredLogger
func main() {
fmt.Println("подсказка Шуан: начать main")
InitLogger()
defer sugarLogger.Sync()
for i := 0; i < 100000; i++ {
simpleHttpGet("www.cnblogs.com")
simpleHttpGet("https://www.baidu.com")
}
}
// Пример: URL-адрес доступа HTTP, статус возврата
func simpleHttpGet(url string) {
fmt.Println("begin simpleHttpGet:" + url)
sugarLogger.Debugf("Trying to hit GET request for %s", url)
resp, err := http.Get(url)
if err != nil {
sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
} else {
sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
resp.Body.Close()
}
}
func InitLogger() {
encoder := getEncoder()
//Два интерфейса для определения уровня бревно
//уровень предупреждения и ниже возвращаемся к информациибревно
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.WarnLevel
})
//уровень предупреждения и выше принадлежат alertbrevno
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.WarnLevel
})
infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")
//Создаем zap.Core,для logger
core := zapcore.NewTee(
zapcore.NewCore(encoder, infoWriter, infoLevel),
zapcore.NewCore(encoder, warnWriter, warnLevel),
)
//Создать регистратор
logger := zap.New(core, zap.AddCaller())
sugarLogger = logger.Sugar()
}
// getEncoder
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
// GetLogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
warnIoWriter := getWriter(filePath)
return zapcore.AddSync(warnIoWriter)
}
// бревно напильник
func getWriter(filename string) io.Writer {
//Сохраняем бревно на 30 дней, разделяем бревно каждую 1 минуту
hook, err := rotatelogs.New(
filename+"_%Y-%m-%d %H:%M:%S.log",
// Создайте мягкую ссылку для последней версии бревно, указывающую на последний файл бревно.
rotatelogs.WithLinkName(filename),
// Условия уборки: Непосредственное удаление вырезанных бревно файлов в соответствии с условиями (количество или время)
//--- MaxAge and RotationCount cannot be both set Оба не могут быть установлены одновременно
//--- RotationCount используется для установки максимального количества файлов, подлежащих вырезанию (любое превышение будет от старого к новому уборка)
//--- MaxAge Максимальное время хранения перед настройкой очистки файла Минимальная единица минут
//--- if both are 0, give maxAge a default 7 * 24 * time.Hour
// Два параметра WithRotationCount и WithMaxAge не могут сосуществовать, и можно установить только один (оба параметра не будут вызывать ошибки при компиляции, но будут сообщать об ошибках во время выполнения. Это также необходимо для предотвращения влияния на логику обработки сегментации).
//rotatelogs.WithRotationCount(10), // Файлы, превышающие это число, будут удалены.
rotatelogs.WithMaxAge(time.Hour*24*30), // Как долго хранить (установите максимальное время хранения перед очисткой файла) Минимальная единица минут)
// Условия разделения (вырезать файл бревно; WithRotationTime and WithRotationSize ~~Он будет обрезан, если выполнено любое из условий~~)
// После личного тестирования было обнаружено, что если бревно не продолжает увеличиваться и для WithRotationTime установлено небольшое значение (например, 10 с), файл не будет разделен в соответствии с частотой WithRotationTime. Когда бревно продолжает увеличиваться, оно будет разделено в соответствии с настройкой WithRotationTime (даже если настройка WithRotationTime очень мала)
rotatelogs.WithRotationTime(time.Second*10), // Делить каждые 10 секунд (установлен временной интервал резкой резки, по умолчанию). 24 * time.Hour)
rotatelogs.WithRotationSize(int64(1*1024*1024*1024)), // Файл будет обрезан, когда достигнет максимального размера. bytes;
)
if err != nil {
panic(err)
}
return hook
}
Установите большой размер файла, который запускает сегментацию (110241024*1024 байт, что составляет 1 ГБ), установите небольшое время сегментации (разделение каждые 10 секунд), выполните код, и вы сможете наблюдать изменения в файл журнала:
Затем установите очень маленький размер файла, запускающего сегментацию (1102450 байт или 50 КБ), установите большое время сегментации (разделение каждые 24 часа), выполните код, очистите предыдущий журнал и затем наблюдайте за изменения в файле журнала:
Установите очень маленький размер файла, который запускает сегментацию (1102435 байт или 35 КБ), и установите очень маленькое время сегментации (разделение каждые 10 секунд). Выполните код, очистите предыдущий журнал, а затем наблюдайте за изменениями в файле. файл журнала:
Когда текущая емкость журнала превышает настроенную емкость, будет создан новый файл журнала. Если время одинаковое, после суффикса времени будет автоматически добавлен числовой суффикс, чтобы одновременно различать разные файлы журнала. время отличается, будет создан новый файл журнала с суффиксом времени. (golangвыполнить分割бревно[7])
В файле журнала будут некоторые правила времени попадания и некоторые правила размера файла совпадения. Форматы имен этих двух файлов различны. См. рисунок выше.
По умолчанию нет, в отличие от лесоруба, который предоставляет опцию «Сжать».
Как упоминалось ранее, такие функции, как сжатие файлов при ротации, удаление старых файлов и добавление временных меток к файлам, необходимо реализовать самостоятельно. Предоставляет функцию обратного вызова WithHandler, которая будет запущена после сегментации, и в ней можно выполнять такие операции, как сжатие элементов.
Измените код (больше не запрашивайте сайт, так как скорость слишком низкая, записывайте лог прямо в for)
Отключить сжатие:
Включите сжатие, и эффект будет значительным:
Связанный код:
package main
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"reflect"
"time"
"github.com/davecgh/go-spew/spew"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Используйте журналы ротации файлов для сегментации
var sugarLogger *zap.SugaredLogger
func main() {
fmt.Println("подсказка Шуан: начать main")
InitLogger()
defer sugarLogger.Sync()
for i := 0; i < 10000000; i++ {
SugarLogger.Infof("Тест на сжатие занимает меньше места. Это текст заливки. Это текст заливки. Это текст заливки. Это текст заливки. Это текст заливки. Это текст заливки. Это текст-заполнитель Это текст-заполнитель Это текст-заполнитель Это текст-заполнитель Это текст-заполнитель Это текст-заполнитель Это текст-заполнитель текст, это текст-заполнитель, это текст-заполнитель, я is %d", i)
//simpleHttpGet("www.cnblogs.com", i)
//simpleHttpGet("https://www.baidu.com", i)
}
time.Sleep(10000e9)
}
// Пример: URL-адрес доступа HTTP, статус возврата
func simpleHttpGet(url string, i int) {
//fmt.Println("begin simpleHttpGet:" + url)
sugarLogger.Debugf("Trying to hit GET request for %s, i is %d", url, i)
resp, err := http.Get(url)
if err != nil {
sugarLogger.Errorf("Error fetching URL %s : Error = %s, i is %d", url, err, i)
} else {
sugarLogger.Infof("Success! statusCode = %s for URL %s,i is %d", resp.Status, url, i)
resp.Body.Close()
}
}
func InitLogger() {
encoder := getEncoder()
//Два интерфейса для определения уровня бревно
//уровень предупреждения и ниже возвращаемся к информациибревно
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.WarnLevel
})
//уровень предупреждения и выше принадлежат alertbrevno
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.WarnLevel
})
infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")
//Создаем zap.Core,для logger
core := zapcore.NewTee(
zapcore.NewCore(encoder, infoWriter, infoLevel),
zapcore.NewCore(encoder, warnWriter, warnLevel),
)
//Создать регистратор
logger := zap.New(core, zap.AddCaller())
sugarLogger = logger.Sugar()
}
// getEncoder
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
// GetLogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
warnIoWriter := getWriter(filePath)
return zapcore.AddSync(warnIoWriter)
}
// бревно напильник
func getWriter(filename string) io.Writer {
//Сохраняем бревно на 30 дней, разделяем бревно каждую 1 минуту
hook, err := rotatelogs.New(
filename+"_%Y-%m-%d %H:%M:%S.log",
// Создайте мягкую ссылку для последней версии бревно, указывающую на последний файл бревно.
rotatelogs.WithLinkName(filename),
// Условия уборки: Непосредственное удаление вырезанных бревно файлов в соответствии с условиями (количество или время)
//--- MaxAge and RotationCount cannot be both set Оба не могут быть установлены одновременно
//--- RotationCount используется для установки максимального количества файлов, подлежащих вырезанию (любое превышение будет от старого к новому уборка)
//--- MaxAge Максимальное время хранения перед настройкой очистки файла Минимальная единица минут
//--- if both are 0, give maxAge a default 7 * 24 * time.Hour
// Два параметра WithRotationCount и WithMaxAge не могут сосуществовать, и можно установить только один (оба параметра не будут вызывать ошибки при компиляции, но будут сообщать об ошибках во время выполнения. Это также необходимо для предотвращения влияния на логику обработки сегментации).
//rotatelogs.WithRotationCount(10), // Файлы, превышающие это число, будут удалены.
rotatelogs.WithMaxAge(time.Hour*24*30), // Как долго хранить (установите максимальное время хранения перед очисткой файла) Минимальная единица минут)
// Условия разделения (вырезать файл бревно; WithRotationTime and WithRotationSize ~~Он будет обрезан, если выполнено любое из условий~~)
// После личного тестирования было обнаружено, что если бревно не продолжает увеличиваться и для WithRotationTime установлено небольшое значение (например, 10 с), файл не будет разделен в соответствии с частотой WithRotationTime. Когда бревно продолжает увеличиваться, оно будет разделено в соответствии с настройкой WithRotationTime (даже если настройка WithRotationTime очень мала)
rotatelogs.WithRotationTime(time.Second*10), // Делить каждые 10 секунд (установлен временной интервал резкой резки, по умолчанию). 24 * time.Hour)
rotatelogs.WithRotationSize(int64(1*1024*35000*1024)), // Файл будет обрезан, когда достигнет максимального размера. bytes;
// Другие варианты Конфигурация
//default: rotatelogs.Local ,you can set rotatelogs.UTC
//rotatelogs.WithClock(rotatelogs.UTC),
//rotatelogs.WithLocation(time.Local),
//--- Когда файл, созданный Rotatelogs.New(), существует, принудительно создайте новый файл. Назовите его как имя исходного файла + серийный номер. Если файл .log существует, создайте его. a.log.1
//rotatelogs.ForceNewFile(),
rotatelogs.WithHandler(rotatelogs.Handler(rotatelogs.HandlerFunc(func(e rotatelogs.Event) {
if e.Type() != rotatelogs.FileRotatedEventType {
return
}
fmt.Println("Вырезка завершена, приступайте к операции упаковки")
spew.Dump("e is:", e)
prevFile := e.(*rotatelogs.FileRotatedEvent).PreviousFile()
if prevFile != "" {
// выполнять
paths, fileName := filepath.Split(prevFile)
//_ = paths
//err := Zip("archive.zip", paths, prevFile)
err := ZipFiles(paths+fileName+".zip", []string{prevFile})
fmt.Println("err is", err)
if err == nil {
os.RemoveAll(prevFile)
}
}
fmt.Println("Тип e:", reflect.TypeOf(e))
fmt.Println("------------------")
fmt.Println()
fmt.Println()
fmt.Println()
//ctx := CleanContext{
// Dir: LogsConfig.LogOutputDir,
// DirMaxSizeG: LogsConfig.LogDirMaxSizeG,
// DirMaxCount: LogsConfig.LogDirMaxFileCount,
//}
//strategyOne := CleanStrategyOne{}
//result, err := NewCleanStrategy(&ctx, &strategyOne).
// Clean().
// Result()
//Warn("Выполнена первая политика удаления и очистки файлов; Результат:%v; Ошибка: %v", result, err)
}))),
)
if err != nil {
panic(err)
}
return hook
}
// ZipFiles compresses one or many files into a single zip archive file.
// Param 1: filename is the output zip file's name.
// Param 2: files is a list of files to add to the zip.
func ZipFiles(filename string, files []string) error {
newZipFile, err := os.Create(filename)
if err != nil {
return err
}
defer newZipFile.Close()
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
// Add files to zip
for _, file := range files {
if err = AddFileToZip(zipWriter, file); err != nil {
return err
}
}
return nil
}
func AddFileToZip(zipWriter *zip.Writer, filename string) error {
fileToZip, err := os.Open(filename)
if err != nil {
return err
}
defer fileToZip.Close()
// Get the file information
info, err := fileToZip.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// Using FileInfoHeader() above only uses the basename of the file. If we want
// to preserve the folder structure we can overwrite this with the full path.
header.Name = filename
// Change to deflate to gain better compression
// see http://golang.org/pkg/archive/zip/#pkg-constants
header.Method = zip.Deflate
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, fileToZip)
return err
}
//
//// Zip compresses the specified files or dirs to zip archive.
//// If a path is a dir don't need to specify the trailing path separator.
//// For example calling Zip("archive.zip", "dir", "csv/baz.csv") will get archive.zip and the content of which is
//// baz.csv
//// dir
//// ├── bar.txt
//// └── foo.txt
//// Note that if a file is a symbolic link it will be skipped.
//
//// https://blog.csdn.net/K346K346/article/details/122441250
//func Zip(zipPath string, paths ...string) error {
// // Create zip file and it's parent dir.
// if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
// return err
// }
// archive, err := os.Create(zipPath)
// if err != nil {
// return err
// }
// defer archive.Close()
//
// // New zip writer.
// zipWriter := zip.NewWriter(archive)
// defer zipWriter.Close()
//
// // Traverse the file or directory.
// for _, rootPath := range paths {
// // Remove the trailing path separator if path is a directory.
// rootPath = strings.TrimSuffix(rootPath, string(os.PathSeparator))
//
// // Visit all the files or directories in the tree.
// err = filepath.Walk(rootPath, walkFunc(rootPath, zipWriter))
// if err != nil {
// return err
// }
// }
// return nil
//}
//
//func walkFunc(rootPath string, zipWriter *zip.Writer) filepath.WalkFunc {
// return func(path string, info fs.FileInfo, err error) error {
// if err != nil {
// return err
// }
//
// // If a file is a symbolic link it will be skipped.
// if info.Mode()&os.ModeSymlink != 0 {
// return nil
// }
//
// // Create a local file header.
// header, err := zip.FileInfoHeader(info)
// if err != nil {
// return err
// }
//
// // Set compression method.
// header.Method = zip.Deflate
//
// // Set relative path of a file as the header name.
// header.Name, err = filepath.Rel(filepath.Dir(rootPath), path)
// if err != nil {
// return err
// }
// if info.IsDir() {
// header.Name += string(os.PathSeparator)
// }
//
// // Create writer for the file header and save content of the file.
// headerWriter, err := zipWriter.CreateHeader(header)
// if err != nil {
// return err
// }
// if info.IsDir() {
// return nil
// }
// f, err := os.Open(path)
// if err != nil {
// return err
// }
// defer f.Close()
// _, err = io.Copy(headerWriter, f)
// return err
// }
//}
Полный код демо-проекта Возьмите zap в качестве примера, чтобы показать, как вырезать файлы журналов. 使использоватьGo生态两个使использовать最高的切分库[8]
[1]
lumberjack: https://github.com/natefinch/lumberjack
[2]
Использование библиотеки журналов golang zap: https://segmentfault.com/a/1190000040443996.
[3]
github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs
[4]
github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat-go/file-rotatelogs
[5]
github.com/lestrrat/go-file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs
[6]
Используйте zapиgo-file-rotatelogs для получения бревно записей, разделенных по времени: https://blog.csdn.net/weixin_43881017/article/details/110200176
[7]
Golang реализует разделенные журналы: https://blog.csdn.net/qq_42119514/article/details/121372416.
[8]
Возьмите zap в качестве примера, чтобы показать, как вырезать файлы журналов. Используйте две наиболее часто используемые библиотеки шардинга в экосистеме Go: https://github.com/cuishuang/zap-demo/tree/main
[9]
Сжатие и распаковка файлов: https://www.topgoer.com/%E5%85%B6%E4%BB%96/%E5%8E%8B%E7%BC%A9%E8%A7%A3%E5%8E % 8B%E6%96%87%E4%BB%B6.html
[10]
Учебные заметки по Golang (пять) — архивируйте/zip для сжатия и распаковки: https://learnku.com/articles/23434/golang-learning-notes-five-archivezip-to-achieve-compression-and-decompression
[11]
Сжатие и распаковка zip-архива Golang: https://blog.csdn.net/K346K346/article/details/122441250.
[12]
Резка журнала zap также поддерживает разделение журнала по дате и фиксированному размеру, а также очистку по расписанию: https://blog.csdn.net/qq_22186119/article/details/122003691
[13]
Использование инкапсуляции структуры журналов Go-logrus: https://www.jianshu.com/p/722250f0b609
[14]
Зайдите в журнал: https://blog.csdn.net/qq_41004932/article/details/119760061
[15]
Создайте свой собственный модуль журнала golang: https://studygolang.com/articles/12537
[16]
golang log rotate file: https://juejin.cn/s/golang%20log%20rotate%20file
[17]
Использование высокопроизводительной библиотеки журналов golang zap: https://www.jianshu.com/p/910b626f67d9