Проект Go реализует обрезку и сжатие журналов в зависимости от времени и размера файла.
Проект Go реализует обрезку и сжатие журналов в зависимости от времени и размера файла.

Несколько вопросов по журналам:

Если один файл слишком велик, это повлияет на эффективность записи, поэтому он будет разделен, но в какой степени его следует разделить? Сколько максимум файлов журналов следует хранить? Сколько дней можно хранить максимум? Следует ли его сжимать?

Обычно используется lumberjack[1]Эта библиотека выполняет вышеуказанные операции

lumberjack

Язык кода:javascript
копировать
 //информационный файл 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, и будут продолжать сегментироваться.

Что произойдет после того, как журнал тестирования достигнет указанного максимального количества сохраняемых файлов журнала?

Очистите папку журнала и измените конфигурацию журнала ошибок:

Язык кода:javascript
копировать
 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

Полная схема изменений:

Проверьте эффект обработки сжатия

Очистите папку журнала и измените конфигурацию журнала ошибок:

Язык кода:javascript
копировать
 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]Эта библиотека(На данный момент нет обслуживания)


file-rotatelogs реализует обрезку по времени

Уведомление:

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

Язык кода:javascript
копировать
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)

Отключить сжатие:

Включите сжатие, и эффект будет значительным:

Связанный код:

Язык кода:javascript
копировать
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

boy illustration
Учебное пособие по Jetpack Compose для начинающих, базовые элементы управления и макет
boy illustration
Код js веб-страницы, фон частицы, код спецэффектов
boy illustration
【новый! Суперподробное】Полное руководство по свойствам компонентов Figma.
boy illustration
🎉Обязательно к прочтению новичкам: полное руководство по написанию мини-программ WeChat с использованием программного обеспечения Cursor.
boy illustration
[Забавный проект Docker] VoceChat — еще одно приложение для мгновенного чата (IM)! Может быть встроен в любую веб-страницу!
boy illustration
Как реализовать переход по странице в HTML (html переходит на указанную страницу)
boy illustration
Как решить проблему зависания и низкой скорости при установке зависимостей с помощью npm. Существуют ли доступные источники npm, которые могут решить эту проблему?
boy illustration
Серия From Zero to Fun: Uni-App WeChat Payment Practice WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
boy illustration
Серия uni-app: uni.navigateЧтобы передать скачок значения
boy illustration
Апплет WeChat настраивает верхнюю панель навигации и адаптируется к различным моделям.
boy illustration
JS-время конвертации
boy illustration
Обеспечьте бесперебойную работу ChromeDriver 125: советы по решению проблемы chromedriver.exe не найдены
boy illustration
Поле комментария, щелчок мышью, специальные эффекты, js-код
boy illustration
Объект массива перемещения объекта JS
boy illustration
Как открыть разрешение на позиционирование апплета WeChat_Как использовать WeChat для определения местонахождения друзей
boy illustration
Я даю вам два набора из 18 простых в использовании фонов холста Power BI, так что вам больше не придется возиться с цветами!
boy illustration
Получить текущее время в js_Как динамически отображать дату и время в js
boy illustration
Вам необходимо изучить сочетания клавиш vsCode для форматирования и организации кода, чтобы вам больше не приходилось настраивать формат вручную.
boy illustration
У ChatGPT большое обновление. Всего за 45 минут пресс-конференция показывает, что OpenAI сделал еще один шаг вперед.
boy illustration
Copilot облачной разработки — упрощение разработки
boy illustration
Микросборка xChatGPT с низким кодом, создание апплета чат-бота с искусственным интеллектом за пять шагов
boy illustration
CUDA Out of Memory: идеальное решение проблемы нехватки памяти CUDA
boy illustration
Анализ кластеризации отдельных ячеек, который должен освоить каждый&MarkerгенетическийВизуализация
boy illustration
vLLM: мощный инструмент для ускорения вывода ИИ
boy illustration
CodeGeeX: мощный инструмент генерации кода искусственного интеллекта, который можно использовать бесплатно в дополнение к второму пилоту.
boy illustration
Машинное обучение Реальный бой LightGBM + настройка параметров случайного поиска: точность 96,67%
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция без кодирования и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
LM Studio для создания локальных больших моделей
boy illustration
Как определить количество слоев и нейронов скрытых слоев нейронной сети?
boy illustration
[Отслеживание целей] Подробное объяснение ByteTrack и детали кода