Git Product home page Git Product logo

gookit / slog Goto Github PK

View Code? Open in Web Editor NEW
369.0 6.0 25.0 1.49 MB

📑 Lightweight, configurable, extensible logging library written in Go. Support multi level, multi outputs and built-in multi file logger, buffers, clean, rotate-file handling.一个易于使用的,轻量级、可配置、可扩展的日志库。支持多个级别,输出到多文件;内置文件日志处理、自动切割、清理、压缩等增强功能

Home Page: https://pkg.go.dev/github.com/gookit/slog

License: MIT License

Go 96.92% Makefile 3.08%
logging go-logger monolog file-logging log-processor formatter console-logger rotate-file logger logging-library

slog's Introduction

slog

GitHub go.mod Go version GoDoc Go Report Card Unit-Tests GitHub tag (latest SemVer) Coverage Status

📑 Lightweight, extensible, configurable logging library written in Golang.

Output in console:

console-log-all-level

Features

  • Simple, directly available without configuration
  • Support common log level processing.
    • eg: trace debug info notice warn error fatal panic
  • Support any extension of Handler Formatter as needed
  • Supports adding multiple Handler log processing at the same time, outputting logs to different places
  • Support to custom log message Formatter
    • Built-in json text two log record formatting Formatter
  • Support to custom build log messages Handler
    • The built-in handler.Config handler.Builder can easily and quickly build the desired log handler
  • Has built-in common log write handler program
    • console output logs to the console, supports color output
    • writer output logs to the specified io.Writer
    • file output log to the specified file, optionally enable buffer to buffer writes
    • simple output log to the specified file, write directly to the file without buffering
    • rotate_file outputs logs to the specified file, and supports splitting files by time and size, and buffer buffered writing is enabled by default
    • See ./handler folder for more built-in implementations
  • Benchmark performance test please see Benchmarks

Output logs to file

  • Support enabling buffer for log writing
  • Support splitting log files by time and size
  • Support configuration to compress log files via gzip
  • Support clean old log files by BackupNum BackupTime

rotatefile subpackage

  • The rotatefile subpackage is a stand-alone tool library with file splitting, cleaning, and compressing backups
  • rotatefile.Writer can also be directly wrapped and used in other logging libraries. For example: log, glog, zap, etc.
  • rotatefile.FilesClear is an independent file cleaning backup tool, which can be used in other places (such as other program log cleaning such as PHP)
  • For more usage, please see rotatefile

中文说明请阅读 README.zh-CN

GoDoc

Install

go get github.com/gookit/slog

Quick Start

slog is very simple to use and can be used without any configuration

package main

import (
	"github.com/gookit/slog"
)

func main() {
	slog.Info("info log message")
	slog.Warn("warning log message")
	slog.Infof("info log %s", "message")
	slog.Debugf("debug %s", "message")
}

Output:

[2020/07/16 12:19:33] [application] [INFO] [main.go:7] info log message  
[2020/07/16 12:19:33] [application] [WARNING] [main.go:8] warning log message  
[2020/07/16 12:19:33] [application] [INFO] [main.go:9] info log message  
[2020/07/16 12:19:33] [application] [DEBUG] [main.go:10] debug message  

Console Color

You can enable color on output logs to console. This is default

package main

import (
	"github.com/gookit/slog"
)

func main() {
	slog.Configure(func(logger *slog.SugaredLogger) {
		f := logger.Formatter.(*slog.TextFormatter)
		f.EnableColor = true
	})

	slog.Trace("this is a simple log message")
	slog.Debug("this is a simple log message")
	slog.Info("this is a simple log message")
	slog.Notice("this is a simple log message")
	slog.Warn("this is a simple log message")
	slog.Error("this is a simple log message")
	slog.Fatal("this is a simple log message")
}

Output:

Change log output style

Above is the Formatter setting that changed the default logger.

You can also create your own logger and append ConsoleHandler to support printing logs to the console:

h := handler.NewConsoleHandler(slog.AllLevels)
l := slog.NewWithHandlers(h)

l.Trace("this is a simple log message")
l.Debug("this is a simple log message")

Change the default logger log output style:

h.Formatter().(*slog.TextFormatter).SetTemplate(slog.NamedTemplate)

Output:

Note: slog.TextFormatter uses a template string to format the output log, so the new field output needs to adjust the template at the same time.

Use JSON Format

slog also has a built-in Formatter for JSON format. If not specified, the default is to use TextFormatter to format log records.

package main

import (
	"github.com/gookit/slog"
)

func main() {
	// use JSON formatter
	slog.SetFormatter(slog.NewJSONFormatter())

	slog.Info("info log message")
	slog.Warn("warning log message")
	slog.WithData(slog.M{
		"key0": 134,
		"key1": "abc",
	}).Infof("info log %s", "message")

	r := slog.WithFields(slog.M{
		"category": "service",
		"IP": "127.0.0.1",
	})
	r.Infof("info %s", "message")
	r.Debugf("debug %s", "message")
}

Output:

{"channel":"application","data":{},"datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info log message"}
{"channel":"application","data":{},"datetime":"2020/07/16 13:23:33","extra":{},"level":"WARNING","message":"warning log message"}
{"channel":"application","data":{"key0":134,"key1":"abc"},"datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info log message"}
{"IP":"127.0.0.1","category":"service","channel":"application","datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info message"}
{"IP":"127.0.0.1","category":"service","channel":"application","datetime":"2020/07/16 13:23:33","extra":{},"level":"DEBUG","message":"debug message"}

Introduction

  • Logger - log dispatcher. One logger can register multiple Handler, Processor
  • Record - log records, each log is a Record instance.
  • Processor - enables extended processing of log records. It is called before the log Record is processed by the Handler.
    • You can use it to perform additional operations on Record, such as: adding fields, adding extended information, etc.
  • Handler - log handler, each log will be processed by Handler.Handle().
    • Here you can send logs to console, file, remote server, etc.
  • Formatter - logging data formatting process.
    • Usually set in Handler, it can be used to format log records, convert records into text, JSON, etc., Handler then writes the formatted data to the specified place.
    • Formatter is not required. You can do without it and handle logging directly in Handler.Handle().

Simple structure of log scheduler

          Processors
Logger --{
          Handlers --|- Handler0 With Formatter0
                     |- Handler1 With Formatter1
                     |- Handler2 (can also without Formatter)
                     |- ... more

Note: Be sure to remember to add Handler, Processor to the logger instance and log records will be processed by Handler.

Processor

Processor interface:

// Processor interface definition
type Processor interface {
	// Process record
	Process(record *Record)
}

// ProcessorFunc definition
type ProcessorFunc func(record *Record)

// Process record
func (fn ProcessorFunc) Process(record *Record) {
	fn(record)
}

You can use it to perform additional operations on the Record before the log Record reaches the Handler for processing, such as: adding fields, adding extended information, etc.

Add processor to logger:

slog.AddProcessor(slog.AddHostname())

// or
l := slog.New()
l.AddProcessor(slog.AddHostname())

The built-in processor slog.AddHostname is used here as an example, which can add a new field hostname on each log record.

slog.AddProcessor(slog.AddHostname())
slog.Info("message")

Output, including new fields "hostname":"InhereMac"

{"channel":"application","level":"INFO","datetime":"2020/07/17 12:01:35","hostname":"InhereMac","data":{},"extra":{},"message":"message"}

Handler

Handler interface:

You can customize any Handler you want, just implement the slog.Handler interface.

// Handler interface definition
type Handler interface {
	io.Closer
	Flush() error
	// IsHandling Checks whether the given record will be handled by this handler.
	IsHandling(level Level) bool
	// Handle a log record.
	// all records may be passed to this method, and the handler should discard
	// those that it does not want to handle.
	Handle(*Record) error
}

Formatter

Formatter interface:

// Formatter interface
type Formatter interface {
	Format(record *Record) ([]byte, error)
}

Function wrapper type:

// FormatterFunc wrapper definition
type FormatterFunc func(r *Record) ([]byte, error)

// Format a log record
func (fn FormatterFunc) Format(r *Record) ([]byte, error) {
	return fn(r)
}

JSON formatter

type JSONFormatter struct {
	// Fields exported log fields.
	Fields []string
	// Aliases for output fields. you can change export field name.
	// item: `"field" : "output name"`
	// eg: {"message": "msg"} export field will display "msg"
	Aliases StringMap
	// PrettyPrint will indent all json logs
	PrettyPrint bool
	// TimeFormat the time format layout. default is time.RFC3339
	TimeFormat string
}

Text formatter

Default templates:

const DefaultTemplate = "[{{datetime}}] [{{channel}}] [{{level}}] [{{caller}}] {{message}} {{data}} {{extra}}\n"
const NamedTemplate = "{{datetime}} channel={{channel}} level={{level}} [file={{caller}}] message={{message}} data={{data}}\n"

Change template:

myTemplate := "[{{datetime}}] [{{level}}] {{message}}"

f := slog.NewTextFormatter()
f.SetTemplate(myTemplate)

Custom logger

Custom Processor and Formatter are relatively simple, just implement a corresponding method.

Create new logger

slog.Info, slog.Warn and other methods use the default logger and output logs to the console by default.

You can create a brand-new instance of slog.Logger:

Method 1

l := slog.New()
// add handlers ...
h1 := handler.NewConsoleHandler(slog.AllLevels)
l.AddHandlers(h1)

Method 2

l := slog.NewWithName("myLogger")
// add handlers ...
h1 := handler.NewConsoleHandler(slog.AllLevels)
l.AddHandlers(h1)

Method 3

package main

import (
	"github.com/gookit/slog"
	"github.com/gookit/slog/handler"
)

func main() {
	l := slog.NewWithHandlers(handler.NewConsoleHandler(slog.AllLevels))
	l.Info("message")
}

Create custom Handler

You only need to implement the slog.Handler interface to create a custom Handler.

You can quickly assemble your own Handler through the built-in handler.LevelsWithFormatter handler.LevelWithFormatter and other fragments of slog.

Examples:

Use handler.LevelsWithFormatter, only need to implement Close, Flush, Handle methods

type MyHandler struct {
	handler.LevelsWithFormatter
    Output io.Writer
}

func (h *MyHandler) Handle(r *slog.Record) error {
	// you can write log message to file or send to remote.
}

func (h *MyHandler) Flush() error {}
func (h *MyHandler) Close() error {}

Add Handler to the logger to use:

// add to default logger
slog.AddHander(&MyHandler{})

// or, add to custom logger:
l := slog.New()
l.AddHander(&MyHandler{})

Use the built-in handlers

./handler package has built-in common log handlers, which can basically meet most scenarios.

// Output logs to console, allow render color.
func NewConsoleHandler(levels []slog.Level) *ConsoleHandler
// Send logs to email
func NewEmailHandler(from EmailOption, toAddresses []string) *EmailHandler
// Send logs to syslog
func NewSysLogHandler(priority syslog.Priority, tag string) (*SysLogHandler, error)
// A simple handler implementation that outputs logs to a given io.Writer
func NewSimpleHandler(out io.Writer, level slog.Level) *SimpleHandler

Output log to file:

// Output log to the specified file, without buffering by default
func NewFileHandler(logfile string, fns ...ConfigFn) (h *SyncCloseHandler, err error)
// Output logs to the specified file in JSON format, without buffering by default
func JSONFileHandler(logfile string, fns ...ConfigFn) (*SyncCloseHandler, error)
// Buffered output log to specified file
func NewBuffFileHandler(logfile string, buffSize int, fns ...ConfigFn) (*SyncCloseHandler, error)

TIP: NewFileHandler JSONFileHandler can also enable write buffering by passing in fns handler.WithBuffSize(buffSize)

Output log to file and rotate automatically:

// Automatic rotating according to file size
func NewSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
// Automatic rotating according to time
func NewTimeRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
// It supports configuration to rotate according to size and time. 
// The default setting file size is 20M, and the default automatic splitting time is 1 hour (EveryHour).
func NewRotateFileHandler(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)

TIP: By passing in fns ...ConfigFn, more options can be set, such as log file retention time, log write buffer size, etc. For detailed settings, see the handler.Config structure

Logs to file

Output log to the specified file, buffer buffered writing is not enabled by default. Buffering can also be enabled by passing in a parameter.

package mypkg

import (
	"github.com/gookit/slog"
	"github.com/gookit/slog/handler"
)

func main() {
	defer slog.MustClose()

	// DangerLevels contains: slog.PanicLevel, slog.ErrorLevel, slog.WarnLevel
	h1 := handler.MustFileHandler("/tmp/error.log", handler.WithLogLevels(slog.DangerLevels))
	// custom log format
	// f := h1.Formatter().(*slog.TextFormatter)
	f := slog.AsTextFormatter(h1.Formatter())
	f.SetTemplate("your template format\n")

	// NormalLevels contains: slog.InfoLevel, slog.NoticeLevel, slog.DebugLevel, slog.TraceLevel
	h2 := handler.MustFileHandler("/tmp/info.log", handler.WithLogLevels(slog.NormalLevels))

	// register handlers
	slog.PushHandler(h1)
	slog.PushHandler(h2)

	// add logs
	slog.Info("info message text")
	slog.Error("error message text")
}

Note: If write buffering buffer is enabled, be sure to call logger.Close() at the end of the program to flush the contents of the buffer to the file.

Log to file with automatic rotating

slog/handler also has a built-in output log to a specified file, and supports splitting files by time and size at the same time. By default, buffer buffered writing is enabled

func Example_rotateFileHandler() {
	h1 := handler.MustRotateFile("/tmp/error.log", handler.EveryHour, handler.WithLogLevels(slog.DangerLevels))
	h2 := handler.MustRotateFile("/tmp/info.log", handler.EveryHour, handler.WithLogLevels(slog.NormalLevels))

	slog.PushHandler(h1)
	slog.PushHandler(h2)

	// add logs
	slog.Info("info message")
	slog.Error("error message")
}

Example of file name sliced by time:

time-rotate-file.log
time-rotate-file.log.20201229_155753
time-rotate-file.log.20201229_155754

Example of a filename cut by size, in the format filename.log.HIS_000N. For example:

size-rotate-file.log
size-rotate-file.log.122915_0001
size-rotate-file.log.122915_0002

Use rotatefile on another logger

rotatefile.Writer can also be used with other logging packages, such as: log, glog, etc.

For example, using rotatefile on golang log:

package main

import (
  "log"

  "github.com/gookit/slog/rotatefile"
)

func main() {
	logFile := "testdata/go_logger.log"
	writer, err := rotatefile.NewConfig(logFile).Create()
	if err != nil {
		panic(err) 
	}

	log.SetOutput(writer)
	log.Println("log message")
}

Quickly create a Handler based on config

This is config struct for create a Handler:

// Config struct
type Config struct {
	// Logfile for write logs
	Logfile string `json:"logfile" yaml:"logfile"`
	// LevelMode for filter log record. default LevelModeList
	LevelMode uint8 `json:"level_mode" yaml:"level_mode"`
	// Level value. use on LevelMode = LevelModeValue
	Level slog.Level `json:"level" yaml:"level"`
	// Levels for log record
	Levels []slog.Level `json:"levels" yaml:"levels"`
	// UseJSON for format logs
	UseJSON bool `json:"use_json" yaml:"use_json"`
	// BuffMode type name. allow: line, bite
	BuffMode string `json:"buff_mode" yaml:"buff_mode"`
	// BuffSize for enable buffer, unit is bytes. set 0 to disable buffer
	BuffSize int `json:"buff_size" yaml:"buff_size"`
	// RotateTime for rotate file, unit is seconds.
	RotateTime rotatefile.RotateTime `json:"rotate_time" yaml:"rotate_time"`
	// MaxSize on rotate file by size, unit is bytes.
	MaxSize uint64 `json:"max_size" yaml:"max_size"`
	// Compress determines if the rotated log files should be compressed using gzip.
	// The default is not to perform compression.
	Compress bool `json:"compress" yaml:"compress"`
	// BackupNum max number for keep old files.
	// 0 is not limit, default is 20.
	BackupNum uint `json:"backup_num" yaml:"backup_num"`
	// BackupTime max time for keep old files. unit is hours
	// 0 is not limit, default is a week.
	BackupTime uint `json:"backup_time" yaml:"backup_time"`
	// RenameFunc build filename for rotate file
	RenameFunc func(filepath string, rotateNum uint) string
}

Examples:

	testFile := "testdata/error.log"

	h := handler.NewEmptyConfig(
			handler.WithLogfile(testFile),
			handler.WithBuffSize(1024*8),
			handler.WithLogLevels(slog.DangerLevels),
			handler.WithBuffMode(handler.BuffModeBite),
		).
		CreateHandler()

	l := slog.NewWithHandlers(h)

About BuffMode

Config.BuffMode The name of the BuffMode type to use. Allow: line, bite

  • BuffModeBite: Buffer by bytes, when the number of bytes in the buffer reaches the specified size, write the contents of the buffer to the file
  • BuffModeLine: Buffer by line, when the buffer size is reached, always ensure that a complete line of log content is written to the file (to avoid log content being truncated)

Use Builder to quickly create Handler

Use handler.Builder to easily and quickly create Handler instances.

	testFile := "testdata/info.log"

	h := handler.NewBuilder().
		WithLogfile(testFile).
		WithLogLevels(slog.NormalLevels).
		WithBuffSize(1024*8).
		WithBuffMode(handler.BuffModeBite).
		WithRotateTime(rotatefile.Every30Min).
		WithCompress(true).
		Build()

	l := slog.NewWithHandlers(h)

Extension packages

Package bufwrite:

  • bufwrite.BufIOWriter additionally implements Sync(), Close() methods by wrapping go's bufio.Writer, which is convenient to use
  • bufwrite.LineWriter refer to the implementation of bufio.Writer in go, which can support flushing the buffer by line, which is more useful for writing log files

Package rotatefile:

  • rotatefile.Writer implements automatic cutting of log files according to size and specified time, and also supports automatic cleaning of log files
    • handler/rotate_file is to use it to cut the log file

Use rotatefile on other log package

Of course, the rotatefile.Writer can be use on other log package, such as: log, glog and more.

Examples, use rotatefile on golang log:

package main

import (
  "log"

  "github.com/gookit/slog/rotatefile"
)

func main() {
	logFile := "testdata/another_logger.log"
	writer, err := rotatefile.NewConfig(logFile).Create()
	if err != nil {
		panic(err) 
	}

	log.SetOutput(writer)
	log.Println("log message")
}

Testing and benchmark

Unit tests

run unit tests:

go test ./...

Benchmarks

Benchmark code at _example/bench_loglibs_test.go

make test-bench

Benchmarks for slog and other log packages:

Note: test and record ad 2023.04.13

goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4                   8381674              1429 ns/op             216 B/op          3 allocs/op
BenchmarkZapSugarNegative
BenchmarkZapSugarNegative-4              8655980              1383 ns/op             104 B/op          4 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4              14173719               849.8 ns/op             0 B/op          0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4              27456256               451.2 ns/op             0 B/op          0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4                2550771              4784 ns/op             608 B/op         17 allocs/op
BenchmarkGookitSlogNegative
>>>> BenchmarkGookitSlogNegative-4       8798220              1375 ns/op             120 B/op          3 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4                  10302483              1167 ns/op             192 B/op          1 allocs/op
BenchmarkZapSugarPositive
BenchmarkZapSugarPositive-4              3833311              3154 ns/op             344 B/op          7 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4              14120524               846.7 ns/op             0 B/op          0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4              27152686               434.9 ns/op             0 B/op          0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4                2601892              4691 ns/op             608 B/op         17 allocs/op
BenchmarkGookitSlogPositive
>>>> BenchmarkGookitSlogPositive-4            8997104              1340 ns/op             120 B/op          3 allocs/op
PASS
ok      command-line-arguments  167.095s

Gookit packages

  • gookit/ini Go config management, use INI files
  • gookit/rux Simple and fast request router for golang HTTP
  • gookit/gcli Build CLI application, tool library, running CLI commands
  • gookit/slog Lightweight, extensible, configurable logging library written in Go
  • gookit/color A command-line color library with true color support, universal API methods and Windows support
  • gookit/event Lightweight event manager and dispatcher implements by Go
  • gookit/cache Generic cache use and cache manager for golang. support File, Memory, Redis, Memcached.
  • gookit/config Go config management. support JSON, YAML, TOML, INI, HCL, ENV and Flags
  • gookit/filter Provide filtering, sanitizing, and conversion of golang data
  • gookit/validate Use for data validation and filtering. support Map, Struct, Form data
  • gookit/goutil Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more
  • More, please see https://github.com/gookit

Acknowledgment

The projects is heavily inspired by follow packages:

LICENSE

MIT

slog's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar deryrahman avatar inhere avatar mnabialek avatar moond4rk avatar ybaldus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

slog's Issues

建议把 handler.rotateTime 弄成public

System (please complete the following information):

  • OS: linux [e.g. linux, macOS]
  • GO Version: 1.13 [e.g. 1.13]
  • Pkg Version: 1.1.1 [e.g. 1.1.1]

Describe the bug

A clear and concise description of what the bug is.

To Reproduce

func RotateMap() map[string]uint8 {
	return map[string]uint8{
		"day":  handler.EveryDay,
		"hour": 1,
		"60m":  1,
		"30m":  2,
		"15m":  3,
		"m":    4,
	}
}

handler.NewRotateFileHandler(c.Path, RotateMap()[config])

Expected behavior

func RotateMap() map[string]rotateTime {
	return map[string]rotateTime{
		"day":  handler.EveryDay,
		"hour": 1,
		"60m":  1,
		"30m":  2,
		"15m":  3,
		"m":    4,
	}
}
handler.NewRotateFileHandler(c.Path, RotateMap()[config])

A clear and concise description of what you expected to happen.

Screenshots

If applicable, add screenshots to help explain your problem.

Additional context

我通过配置文件配置多久创建一次日志,但是没办法做成map,很不便利。

日志写入文件问题

  • OS: linux [macOS 12.4]
  • GO Version: 1.18
  • Pkg Version: 0.3.2

Describe the bug

将日志写入文件时每次都要手动调用 Flush() 方法才会将日志写入文件。
可以不用每次都手动调用吗?

To Reproduce
这是封装的一段代码

package logger

import (
	"path/filepath"

	"github.com/gookit/slog"
	"github.com/gookit/slog/handler"
)

var l *slog.SugaredLogger

type Config struct {
	LogLevel   slog.Level
	LogDir     string
	FileName   string
	MaxSize    int
	MaxBackups int
	TimeFormat string
}

func Init(c *Config) error {
	if l != nil {
		return nil
	}

	pth := filepath.Join(c.LogDir, c.FileName)
	h, err := handler.NewSizeRotateFile(
		pth,
		c.MaxSize,
		handler.WithLogLevels(slog.AllLevels),
	)
	if err != nil {
		return err
	}

	logTemplate := "{{datetime}} {{level}} {{message}}"

	l = slog.NewStdLogger()
	l.Config(func(sl *slog.SugaredLogger) {
		f := sl.Formatter.(*slog.TextFormatter)
		f.TimeFormat = "2006-01-02 15:04:05"
		f.SetTemplate(logTemplate)
		f.FullDisplay = true
		f.EnableColor = true
	})
	l.SetName("licd")
	l.Level = c.LogLevel

	l.AddHandler(h)
	l.DoNothingOnPanicFatal()

	return nil
}

func Info(format string, args ...interface{}) {
	l.Infof(format, args...)
}

func Warn(format string, args ...interface{}) {
	l.Warnf(format, args...)
}

func Error(format string, args ...interface{}) {
	l.Errorf(format, args...)
}

func Debug(format string, args ...interface{}) {
	l.Debugf(format, args...)
}

需要一个动态设置日志级别的接口

在程序运行过程,有时候为了定位问题,有动态设置日志级别的需求,但没有找到方便友好的设置接口。
slog.SetLogLevel(lvl)只在std logger上生效。
请问有没有方便友好的动态设置日志级别接口?

package logger

import (
	"github.com/gookit/slog"
	"github.com/gookit/slog/handler"
	"github.com/gookit/slog/rotatefile"
)

var (
	Log1   *slog.Logger
	Log2   *slog.Logger
	AppLog *slog.Record
	CfgLog *slog.Record
)

func init() {
	logTemplate := "[{{datetime}}] [{{level}}] {{message}}\n"

	formatter := slog.NewTextFormatter()
	formatter.TimeFormat = "2006-01-02 15:04:05.000"
	formatter.SetTemplate(logTemplate)
	slog.SetFormatter(formatter)

	h1, _ := handler.NewEmptyConfig(
		handler.WithLogfile("/var/log/rnxt/nas/nas.log"),
		handler.WithRotateTime(rotatefile.EveryDay),
		handler.WithBackupNum(10),
		handler.WithMaxSize(1024*1024*20),
		handler.WithLogLevels(slog.NormalLevels),
	).CreateHandler()
	h1.SetFormatter(formatter)

	h2 := handler.NewConsoleHandler(slog.AllLevels)
	h2.SetFormatter(formatter)

	Log1 = slog.NewWithHandlers(h1, h2)

	AppLog = Log1.WithFields(slog.M{
		"component": "APP",
	})
	CfgLog = Log1.WithFields(slog.M{
		"component": "CFG",
	})
}

func SetLogLevel(level string) {
	lvl, err := slog.Name2Level(level)
	if err != nil {
		AppLog.Fatalln("Failed to parse log level:", err)
	}
	slog.SetLogLevel(lvl)
}

syslog severity mapping problem

return h.writer.Info(string(bts))

All slog Level are set to syslog severity Info.
Suggest slog Level to syslog severity as sub:
PanicLevel -> LOG_EMERG
FatalLevel -> LOG_CRIT
ErrorLevel -> LOG_ERR
WarnLevel -> LOG_WARNING
NoticeLevel -> LOG_NOTICE
InfoLevel -> LOG_INFO
DebugLevel -> LOG_DEBUG
TraceLevel -> LOG_DEBUG

Can not change to customization format Template

System (please complete the following information):

  • OS: Windows 10 X64
  • GO Version: 1.18
  • Pkg Version: don't know, I use the go get to use the code

Describe the bug

Customization template is not taken for printing.

To Reproduce

import 	log "github.com/gookit/slog"

const simplestTemplate = "[{{datetime}}] [{{level}}] {{message}} {{data}} {{extra}}"

func init() {
	log.GetFormatter().(*log.TextFormatter).Template = simplestTemplate
	log.SetLogLevel(log.ErrorLevel)
	log.Errorf("Test\n")
}
func main() {
}

Expected behavior

I think it should output the following log:

[2022/05/18T17:20:39] [ERROR] Test

While, it outputs:

[2022/05/18T17:20:39] [application] [ERROR] [proc.go:6222,doInit] Test

能在 golang 1.10 版本下使用吗

System (please complete the following information):

  • OS: linux [e.g. linux, macOS]
  • GO Version: 1.13 [e.g. 1.13]
  • Pkg Version: 1.1.1 [e.g. 1.1.1]

Describe the bug

A clear and concise description of what the bug is.

To Reproduce

// go code

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots

If applicable, add screenshots to help explain your problem.

Additional context

Add any other context about the problem here.

内建的 rotate 机制有多个bug

System (please complete the following information):

  • OS: linux
  • GO Version: 1.21.5

Describe the bug

内建的 rotate 机制:

  • 在设置了 max backup 为 90 的情况下,只有3个备份。
  • rotate 之后,gzip 压缩的 log 文件内容不完整,有大量文本丢失。

Setting channel name cross multiple loggers

What is the best approach to tracking which "application subsystem" a message is coming from?

Normally I would create one logger per subsystem and configure its "name" or "channel".

But it seems that channel is only referenced in the record and its using the global DefaultCannelName

in probe.go I have

var probeLogger = slog.NewWithName("probe")

func init() {

    slog.DefaultChannelName = "probe"
    slog.DefaultTimeFormat = "2006-01-02 15:04:05.000"

    h1 := handler.NewConsoleHandler(slog.AllLevels)
    f := slog.AsTextFormatter(h1.Formatter())
    f.SetTemplate(utils.LogTemplate)
    probeLogger.AddHandlers(h1)

    probeLogger.Infof("Probing %s", viper.GetString("read"))
...

where as in root.go I have

var rootLogger = slog.NewWithName("root")

func init() {

    slog.DefaultChannelName = "root"
    slog.DefaultTimeFormat = "2006-01-02 15:04:05.000"

    h1 := handler.NewConsoleHandler(slog.AllLevels)
    f := slog.AsTextFormatter(h1.Formatter())
    f.SetTemplate(utils.LogTemplate)
    rootLogger.AddHandlers(h1)

All of probe.go's messages are using root

I have worked around it by using a subsystem specific template that hardcodes the name, but that seems unclean...

自定义模板报 invalid memory address or nil pointer dereference

System (please complete the following information):

  • OS: macos
  • GO Version: 1.21.5
  • Pkg Version: 0.5.5

Describe the bug

自定义模板报空指针 invalid memory address or nil pointer dereference

To Reproduce

func TestLog(t *testing.T) {
	myTemplate := "[{{datetime}}] [{{requestid}}] [{{level}}] {{message}}"
	textFormatter := &slog.TextFormatter{TimeFormat: "2006-01-02 15:04:05.000"}
	textFormatter.SetTemplate(myTemplate)
	h1 := handler.NewConsoleHandler(slog.AllLevels)
	h1.SetFormatter(textFormatter)
	L := slog.New()
	L.AddHandlers(h1)
	L.WithField("requestid", "11111").Info("testst")
}

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots

image

Additional context

Add any other context about the problem here.

如何获取原始参数

slog.PushHandlers(handler...)

func (n *notify) Handle(record *slog.Record) error {

	return nil
}

在record 如何获取slog.Error 传入的args

设置日志颜色,支持只设置 tag,不设置日志内容颜色

设置之前:
${{\color{Green}\Huge{\textsf{ [INFO] }}}}$ |2024-05-17 16:22:43.458| (/root/signal-server/libs/ws_server.go:244) ${{\color{Green}\Huge{\textsf{ New connection }}}}$

设置之后:
${{\color{Green}\Huge{\textsf{ [INFO] }}}}$ |2024-05-17 16:22:43.458| (/root/signal-server/libs/ws_server.go:244) New connection

slog 运行时不会限制备份数量和压缩备份文件

System (please complete the following information):

  • OS: macOS
  • GO Version: 1.21
  • Pkg Version: 0.5.3

Describe the bug

运行时 slog 不会根据 BackupNum 参数限制文件备份文件的数量,开启压缩选项后在运行过程中也不会将备份的文件进行压缩。
当重新启动后 slog 才会移除多余的备份文件,及压缩备份文件

To Reproduce

以下程序将每隔 30 秒产生一个备份文件,持续 10 分钟。允许的最大备份数据为 5 并开启压缩功能。

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/gookit/slog"
	"github.com/gookit/slog/handler"
	"github.com/gookit/slog/rotatefile"
)

const pth = "./logs/main.log"

func main() {
	log := slog.New()

	h, err := handler.NewTimeRotateFileHandler(
		pth,
		rotatefile.RotateTime((time.Second * 30).Seconds()),
		handler.WithBuffSize(0),
		handler.WithBackupNum(5),
		handler.WithCompress(true),
	)

	if err != nil {
		panic(err)
	}

	log.AddHandler(h)

	ctx, _ := context.WithTimeout(context.Background(), time.Minute*10)
	go func() {
		for {
			select {
			case <-ctx.Done():

				return
			case <-time.After(time.Second):
				log.Info("Log " + time.Now().String())
			}
		}
	}()

	<-ctx.Done()

	fmt.Println("Exit.")
}

Expected behavior

应该在运行时就限制相应的备份数量及压缩备份文件,而不是在重新启动后。

Screenshots
截屏2023-07-27 09 21 11

你好作者 请问如何设置最低日志级别?

console可以使用h.LevelFormattable = slog.NewLvFormatter(slog.LevelByName(levelName))
因为使用了slog.AllLevels

// 文件
func fileHandlers() []slog.Handler {
	levelName := common.CONFIG.String("log.level")
	hs := make([]slog.Handler, 0, 2)
	infoHandler, err := handler.NewFileHandler("logs/info.log", func(c *handler.Config) {
		c.Levels = []slog.Level{slog.InfoLevel}
		c.UseJSON = true
	})
        // 这里如果使用LevelFormattable会和上边c.Levels造成冲突 
	infoHandler.LevelFormattable = slog.NewLvFormatter(slog.LevelByName(levelName))
	if err != nil {
		panic("失败")
	}
	errorHandler, err := handler.NewFileHandler("logs/error.log", func(c *handler.Config) {
		c.Levels = []slog.Level{slog.ErrorLevel}
	})
	if err != nil {
		panic("失败")
	}
	hs = append(hs, infoHandler, errorHandler)
	return hs
}

NewFileHandler如果使用LevelFormattable会和上边c.Levels造成冲突,因为配置levels时只配置了单个选项,之后设置全局日志最小等级时会覆盖上边的设置。*slog.Logger下也没有设置全局日志最小等级的方法和属性

Extra newline added by default even if no newline set at Template String

System (please complete the following information):

  • OS: Windows X64
  • GO Version: 1.18
  • Pkg Version: v0.3.1

Describe the bug

The log function always add an extra newline.

I think that the function end with f should have the same behavior as fmt.Printf, so users can drop-in replace it with slog XXXXf serial function.

Except that, with or without the newline settings in template String should have an effect.

To Reproduce

import 	log "github.com/gookit/slog"

const simplestTemplate = "[{{level}}] {{message}} {{data}} {{extra}}"

func init() {
	log.GetFormatter().(*log.TextFormatter).SetTemplate(simplestTemplate)

	log.Errorf("Test:line=%d\t", 1)
	log.Errorf("NoNewLineTest")
}
func main() {
}

Expected behavior

[ERROR] Test:line=1    [ERROR] NoNewLineTest

The actual output:

[ERROR] Test:line=1
[ERROR] NoNewLineTest

Doc: add more description for Template keyword

Any place have the info about the meaning of all kinds of Template keywords?

For example:

const simplestTemplate = "[{{datetime}}] [{{level}}] {{message}} {{data}} {{extra}}"

What's the meaning of data and extra in above template string?

Thanks.

slog: failed to handle log, error: write ./logs/info.log: file already closed

windows 自动创建日志目录异常 slog: failed to handle log, error: write ./logs/info.log: file already closed

import (
	"github.com/gookit/slog"
	"github.com/gookit/slog/handler"
	"github.com/gookit/slog/rotatefile"
)

func Slog() {
	defer slog.MustClose()

	// DangerLevels 包含: slog.PanicLevel, slog.ErrorLevel, slog.WarnLevel
	h1 := handler.MustRotateFile("./logs/error.log", rotatefile.EveryDay,
		handler.WithLogLevels(slog.DangerLevels),
		handler.WithCompress(true),
	)

	// NormalLevels 包含: slog.InfoLevel, slog.NoticeLevel, slog.DebugLevel, slog.TraceLevel
	h2 := handler.MustFileHandler("./logs/info.log", handler.WithLogLevels(slog.NormalLevels))

	// 注册 handler 到 logger(调度器)
	slog.PushHandlers(h1, h2)

	// add logs
	slog.Info("info message text")
	slog.Error("error message text")
}

Isn't `slog.Fatal()` meant to call `os.Exit(1)`?

I'm expecting slog.Fatal to call os.Exit(1) to reflect log.Fatal(), but this is not happening.
Is this expected or am I doing something incorrect?

package main

import (
        "github.com/gookit/slog"
)

func main() {
        slog.Info("info log message")
        slog.Fatal("fatal log message")
        slog.Info("info log message")
}
> go run ./main.go ; echo $?
[2024/04/05T19:13:52.904] [application] [INFO] [main.go:8,main] info log message
[2024/04/05T19:13:52.904] [application] [FATAL] [main.go:9,main] fatal log message
[2024/04/05T19:13:52.904] [application] [INFO] [main.go:10,main] info log message
0

[Bug] RotateTime.level()

System (please complete the following information):

  • OS: macOS
  • GO Version: 1.21.0
  • Pkg Version: 0.5.4

Describe the bug

当我配置按日期的方式来滚动日志时,当大于 1 天时只能按 1 天来滚动日志。

To Reproduce

seconds := 604800 // 7天
handler.NewTimeRotateFileHandler(
		logFile,
		rotatefile.RotateTime(seconds),
		handler.WithLogLevels(parseLevels(log.level)),
		handler.WithBuffSize(log.bufferSize),
		handler.WithBackupNum(log.maxBackups),
		handler.WithCompress(log.compress),
		handler.WithFilePerm(log.filePerm),

请查看一下这个方法 RotateTime.level

Level handling is non-obvious

On every logging system I've used across many languages if you set the log level to be Info, you also get Warning, Error, Fatal etc.

Not so with slog.

If I do handler.NewConsoleHandler(slog.Levels{slog.InfoLevel}) I just get Info messages...

So I'm expected to build by own list ? That's really annoying to do programmatically for no value that I can see...

Trying to understand slog log levels

It looks to me that levels are inverted compared to the usual things, for example :

  • setting DebugLevel will print only Debug message, not errors or warning or info
  • setting ErrorLevel will print Trace,Debug,Info,Warn and Error

That's usually the opposite, when setting Error level, we only want errors. If setting Info we want errors, warning and info messages, an so on.

Is there something I didn't understand ?

rotateTime.GetIntervalAndFormat always return 1, "20060102_150405"

System (please complete the following information):

  • OS: macOS
  • GO Version: 1.17
  • Pkg Version: v.0.1.5

Describe the bug

rotateTime.GetIntervalAndFormat always return 1, "20060102_150405"

if fileHandler, err := handler.NewTimeRotateFileHandler(accessLogFile, handler.EveryDay); err == nil {
		fileHandler.Levels = slog.Levels{slog.DebugLevel, slog.InfoLevel, slog.NoticeLevel, slog.TraceLevel, slog.PrintLevel}
		slog.AddHandlers(fileHandler)
	}

// GetIntervalAndFormat get check interval time and log suffix format
func (rt rotateTime) GetIntervalAndFormat() (checkInterval int64, suffixFormat string) {
	switch rt {
	case EveryDay:
		checkInterval = 3600 * 24
		suffixFormat = "20060102"
	case EveryHour:
		checkInterval = 3600
		suffixFormat = "20060102_1500"
	case Every30Minutes:
		checkInterval = 1800
		suffixFormat = "20060102_1504"
	case Every15Minutes:
		checkInterval = 900
		suffixFormat = "20060102_1504"
	case EveryMinute:
		checkInterval = 60
		suffixFormat = "20060102_1504"
	}

	// Every Second
	return 1, "20060102_150405"
}

Expected behavior

rotateTime.GetIntervalAndFormat return the right result

NewFlushCloseHandler bytes.Buffer does not implement handler.FlushCloseWriter

System (please complete the following information):

  • OS: Windows
  • GO Version: 1.20.7
  • Pkg Version: 0.5.5

Describe the bug

Error in usage example of NewFlushCloseHandler or error in function

To Reproduce

buf := new(bytes.Buffer)
h := handler.NewFlushCloseHandler(&buf, slog.AllLevels)

Expected behavior

new FlushCloseHandler

日志写入问题

System (please complete the following information):

  • OS: windows
  • GO Version: 1.17
  • Pkg Version: master

Describe the bug

  1. 使用默认示例代码,只生成了文件,没有实际内容写入。
  2. 使用TimeRotateFileHandler 同样无法写入。
  3. 使用RotateFileHandler,能成功写入,但是只会写入第一行(第一个输出)。
  4. 示例代码有点问题,在截图里说明。

To Reproduce

// go code

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots
image

If applicable, add screenshots to help explain your problem.

Additional context

Add any other context about the problem here.

如何配置日志清理

作者您好:
默认安装是 github.com/gookit/slog v0.2.1,日志清理是在哪一个版本实现的,如何配置日志自动清理?

NewRotateFile 问题, 无论什么选项都是返回Every Second。

NewRotateFile 问题, 无论什么选项都是返回Every Second。

// GetIntervalAndFormat get check interval time and log suffix format
func (rt rotateTime) GetIntervalAndFormat() (checkInterval int64, suffixFormat string) {
	switch rt {
	case EveryDay:
		checkInterval = 3600 * 24
		suffixFormat = "20060102"
	case EveryHour:
		checkInterval = 3600
		suffixFormat = "20060102_1500"
	case Every30Minutes:
		checkInterval = 1800
		suffixFormat = "20060102_1504"
	case Every15Minutes:
		checkInterval = 900
		suffixFormat = "20060102_1504"
	case EveryMinute:
		checkInterval = 60
		suffixFormat = "20060102_1504"
	}

	// Every Second
	return 1, "20060102_150405"
}

Originally posted by @lkeme in #31 (comment)

日志时间有问题

System (please complete the following information):

  • OS: windows
  • GO Version: 1.16
  • Pkg Version: master

日志时间有问题

A clear and concise description of what the bug is.

To Reproduce

package main

import (
	"github.com/gookit/slog"
	"testing"
	"time"
)

func TestLog(t *testing.T) {
	count := 0
	for {
		slog.Infof("info log %d",count)
		time.Sleep(time.Second)
		count++
	}
}

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots
image

If applicable, add screenshots to help explain your problem.

Additional context

Add any other context about the problem here.

slog performance

我在参照示例写了个slog输出到文件的demo,测试性能的时候发现和benchmark的表现相差很大
即便我放在同一个项目同一台机器上,slog的性能也远不如log4go(项目想替换掉的旧日志库)
是我的使用方法不对吗,求教

demo:

func slogDemo() {
      type Obj struct {
              a int
              b int64
              c string
              d bool
       }
      str1 := "str1"
      str2 := "str222222222222"
      int1 := 1
      int2 := 2

      h1, err := handler.NewEmptyConfig().With(
                  handler.WithLogfile("./info.log"),             //路径
                  handler.WithRotateTime(handler.EveryHour),  //日志分割间隔
                  handler.WithLogLevels(slog.AllLevels),         //日志level
                  handler.WithBuffSize(4 * 1024 * 1024),                //buffer大小
                  handler.WithCompress(true),                    //是否压缩旧日志 zip
                  handler.WithBackupNum(24 * 3),                 //保留旧日志数量
                  handler.WithBuffMode(handler.BuffModeBite),
                  //handler.WithRenameFunc(),                    //RenameFunc build filename for rotate file
          ).CreateHandler()
      if err != nil {
	  fmt.Printf("Create slog handler err: %#v", err)
	  return
      }
      f := slog.AsTextFormatter(h1.Formatter())
      myTplt := "[{{datetime}}] [{{level}}] [{{caller}}] {{message}}\n"
      f.SetTemplate(myTplt)
      logs := slog.NewWithHandlers(h1)
      start = time.Now().UnixNano()
      for n := count;n > 0; n-- {
          logs.Infof("message is %d %d %s %s %#v", int1, int2, str1, str2, obj)
      }
     end = time.Now().UnixNano()
     fmt.Printf("\n slog total cost %d ns\n  avg  cost %d ns \n count %d \n", end - start, (end - start)/int64(count), count)
     logs.MustFlush()
}

result:

slog total cost 784599471 ns
  avg  cost 7845 ns
 count 100000

补充些更多的测试结果

 zap sugar no format
 total cost 213044652 ns
  avg  cost 2130 ns
 count 100000

 zap sugar format
 total cost 397701153 ns
  avg  cost 3977 ns
 count 100000

 zap no format
 total cost 215259103 ns
  avg  cost 2152 ns
 count 100000

 zap format
 total cost 430643119 ns
  avg  cost 4306 ns
 count 100000

 slog no format
 total cost 500610606 ns
  avg  cost 5006 ns
 count 100000

 slog format
 total cost 771954584 ns
  avg  cost 7719 ns
 count 100000

 log4go no format
 total cost 225615627 ns
  avg  cost 2256 ns
 count 100000

 log4go format
 total cost 452465265 ns
  avg  cost 4524 ns
 count 100000

日志格式设置对写入文件的日志无效

Describe the bug

日志格式设置对写入文件的日志无效

To Reproduce

	options := []handler.ConfigFn{
		handler.WithBuffSize(0),
	}
	h, err := handler.NewTimeRotateFile("./test.log", rotatefile.EveryDay, options...)
  
   if err == nil {

        slog.SetFormatter(slog.NewJSONFormatter())
        slog.Std().CallerSkip = 1
        slog.Std().LowerLevelName = false
        slog.Std().ReportCaller = false
        slog.AddHandler(h)
        slog.Info("test----------------")
    }

console 输出已经是 json 格式

Run rotate

Is it any example to use cron for rotation logs at custom moment?
I am trying to make new log file daily at midnight.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.