Structured Logging Made Easy

Structured Logging Made Easy

go.dev goreport build coverage stability-stable

Features

  • Dependency Free
  • Simple and Clean Interface
  • Consistent Writer
    • IOWriter, io.Writer wrapper
    • FileWriter, rotating & effective
    • ConsoleWriter, colorful & formatting
    • MultiWriter, multiple level dispatch
    • SyslogWriter, syslog server logging
    • JournalWriter, linux systemd logging
    • EventlogWriter, windows system event
    • AsyncWriter, asynchronously writing
  • Third-party Logger Interceptor
    • Logger.Std, (std)log
    • Logger.Grpc, grpclog.LoggerV2
    • Logger.Logr, logr.Logger
  • Useful utility function
    • Goid(), the goroutine id matches stack trace
    • NewXID(), create a tracing id
    • Fastrandn(n uint32), fast pseudorandom uint32 in [0,n)
    • IsTerminal(fd uintptr), isatty for golang
    • Printf(fmt string, a ...interface{}), printf logging
  • High Performance

Interfaces

Logger

// DefaultLogger is the global logger.
var DefaultLogger = Logger{
	Level:      DebugLevel,
	Caller:     0,
	TimeField:  "",
	TimeFormat: "",
	Writer:     &IOWriter{os.Stderr},
}

// A Logger represents an active logging object that generates lines of JSON output to an io.Writer.
type Logger struct {
	// Level defines log levels.
	Level Level

	// Caller determines if adds the file:line of the "caller" key.
	Caller int

	// TimeField defines the time filed name in output.  It uses "time" in if empty.
	TimeField string

	// TimeFormat specifies the time format in output. It uses RFC3339 with millisecond if empty.
	// If set with `TimeFormatUnix`, `TimeFormatUnixMs`, times are formated as UNIX timestamp.
	TimeFormat string

	// Writer specifies the writer of output. It uses a wrapped os.Stderr Writer in if empty.
	Writer Writer
}

// Writer defines an entry writer interface.
type Writer interface {
	WriteEntry(*Entry) (int, error)
}

FileWriter & ConsoleWriter

// FileWriter is an Writer that writes to the specified filename.
type FileWriter struct {
	// Filename is the file to write logs to.  Backup log files will be retained
	// in the same directory.
	Filename string

	// FileMode represents the file's mode and permission bits.  The default
	// mode is 0644
	FileMode os.FileMode

	// MaxSize is the maximum size in bytes of the log file before it gets rotated.
	MaxSize int64

	// MaxBackups is the maximum number of old log files to retain.  The default
	// is to retain all old log files
	MaxBackups int

	// TimeFormat specifies the time format of filename, uses `2006-01-02T15-04-05` as default format.
	// If set with `TimeFormatUnix`, `TimeFormatUnixMs`, times are formated as UNIX timestamp.
	TimeFormat string

	// LocalTime determines if the time used for formatting the timestamps in
	// log files is the computer's local time.  The default is to use UTC time.
	LocalTime bool

	// HostName determines if the hostname used for formatting in log files.
	HostName bool

	// ProcessID determines if the pid used for formatting in log files.
	ProcessID bool

	// EnsureFolder ensures the file directory creation before writing.
	EnsureFolder bool

	// Cleaner specifies an optional cleanup function of log backups after rotation,
	// if not set, the default behavior is to delete more than MaxBackups log files.
	Cleaner func(filename string, maxBackups int, matches []os.FileInfo)
}

// ConsoleWriter parses the JSON input and writes it in a colorized, human-friendly format to Writer.
// IMPORTANT: Don't use ConsoleWriter on critical path of a high concurrency and low latency application.
//
// Default output format:
//     {Time} {Level} {Goid} {Caller} > {Message} {Key}={Value} {Key}={Value}
type ConsoleWriter struct {
	// ColorOutput determines if used colorized output.
	ColorOutput bool

	// QuoteString determines if quoting string values.
	QuoteString bool

	// EndWithMessage determines if output message in the end of line.
	EndWithMessage bool

	// Writer is the output destination. using os.Stderr if empty.
	Writer io.Writer

	// Formatter specifies an optional text formatter for creating a customized output,
	// If it is set, ColorOutput, QuoteString and EndWithMessage will be ignored.
	Formatter func(w io.Writer, args *FormatterArgs) (n int, err error)
}

Note: FileWriter/ConsoleWriter implements log.Writer and io.Writer interfaces both.

Getting Started

Simple Logging Example

A out of box example. playground

package main

import (
	"github.com/phuslu/log"
)

func main() {
	log.Info().Str("foo", "bar").Int("number", 42).Msg("hi, phuslog")
	log.Info().Msgf("foo=%s number=%d error=%+v", "bar", 42, "an error")
}

// Output:
//   {"time":"2020-03-22T09:58:41.828Z","level":"info","foo":"bar","number":42,"message":"hi, phuslog"}
//   {"time":"2020-03-22T09:58:41.828Z","level":"info","message":"foo=bar number=42 error=an error"}

Note: By default log writes to os.Stderr

Customize the configuration and formatting:

To customize logger filed name and format. playground

log.DefaultLogger = log.Logger{
	Level:      log.InfoLevel,
	Caller:     1,
	TimeField:  "date",
	TimeFormat: "2006-01-02",
	Writer:     &log.IOWriter{os.Stdout},
}
log.Info().Str("foo", "bar").Msgf("hello %s", "world")

// Output: {"date":"2019-07-04","level":"info","caller":"prog.go:16","foo":"bar","message":"hello world"}

Rotating File Writer

To log to a rotating file, use FileWriter. playground

package main

import (
	"os"
	"path/filepath"
	"time"

	"github.com/phuslu/log"
	"github.com/robfig/cron/v3"
)

func main() {
	logger := log.Logger{
		Level: log.ParseLevel("info"),
		Writer: &log.FileWriter{
			Filename: "logs/main.log",
			FileMode: 0600,
			MaxSize:  50 * 1024 * 1024,
			Cleaner:  func(filename string, maxBackups int, matches []os.FileInfo) {
				var dir = filepath.Dir(filename)
				var total int64
				for i := len(matches) - 1; i >= 0; i-- {
					total += matches[i].Size()
					if total > 10*1024*1024*1024 {
						os.Remove(filepath.Join(dir, matches[i].Name()))
					}
				}
			},
			EnsureFolder: true,
			LocalTime:    false,
		},
	}

	runner := cron.New(cron.WithSeconds(), cron.WithLocation(time.UTC))
	runner.AddFunc("0 0 * * * *", func() { logger.Writer.(*log.FileWriter).Rotate() })
	go runner.Run()

	for {
		time.Sleep(time.Second)
		logger.Info().Msg("hello world")
	}
}

Pretty Console Writer

To log a human-friendly, colorized output, use ConsoleWriter. playground

if log.IsTerminal(os.Stderr.Fd()) {
	log.DefaultLogger = log.Logger{
		TimeFormat: "15:04:05",
		Caller:     1,
		Writer: &log.ConsoleWriter{
			ColorOutput:    true,
			QuoteString:    true,
			EndWithMessage: true,
		},
	}
}

log.Debug().Int("everything", 42).Str("foo", "bar").Msg("hello world")
log.Info().Int("everything", 42).Str("foo", "bar").Msg("hello world")
log.Warn().Int("everything", 42).Str("foo", "bar").Msg("hello world")
log.Error().Err(errors.New("an error")).Msg("hello world")

Pretty logging

Note: pretty logging also works on windows console

Formatting Console Writer

To log with user-defined format(e.g. glog), using ConsoleWriter.Formatter. playground

log.DefaultLogger = log.Logger{
	Level:      log.InfoLevel,
	Caller:     1,
	TimeFormat: "0102 15:04:05.999999",
	Writer: &log.ConsoleWriter{
		Formatter: func (w io.Writer, a *log.FormatterArgs) (int, error) {
			return fmt.Fprintf(w, "%c%s %s %s] %s\n%s", strings.ToUpper(a.Level)[0],
				a.Time, a.Goid, a.Caller, a.Message, a.Stack)
		},
	},
}

log.Info().Msgf("hello glog %s", "Info")
log.Warn().Msgf("hello glog %s", "Warn")
log.Error().Msgf("hello glog %s", "Error")

// Output:
// I0725 09:59:57.503246 19 console_test.go:183] hello glog Info
// W0725 09:59:57.504247 19 console_test.go:184] hello glog Warn
// E0725 09:59:57.504247 19 console_test.go:185] hello glog Error

Multiple Dispatching Writer

To log to different writers by different levels, use MultiWriter.

log.DefaultLogger.Writer = &log.MultiWriter{
	InfoWriter:    &log.FileWriter{Filename: "main.INFO", MaxSize: 100<<20},
	WarnWriter:    &log.FileWriter{Filename: "main.WARNING", MaxSize: 100<<20},
	ErrorWriter:   &log.FileWriter{Filename: "main.ERROR", MaxSize: 100<<20},
	ConsoleWriter: &log.ConsoleWriter{ColorOutput: true},
	ConsoleLevel:  log.ErrorLevel,
}
log.Info().Int("number", 42).Str("foo", "bar").Msg("a info log")
log.Warn().Int("number", 42).Str("foo", "bar").Msg("a warn log")
log.Error().Int("number", 42).Str("foo", "bar").Msg("a error log")

Multiple Combined Logger:

To logging to different logger as you want, use below idiom. playground

package main

import (
	"github.com/phuslu/log"
)

var logger = struct {
	Console log.Logger
	Access  log.Logger
	Data    log.Logger
}{
	Console: log.Logger{
		TimeFormat: "15:04:05",
		Caller:     1,
		Writer: &log.ConsoleWriter{
			ColorOutput:    true,
			EndWithMessage: true,
		},
	},
	Access: log.Logger{
		Level: log.InfoLevel,
		Writer: &log.FileWriter{
			Filename:   "access.log",
			MaxSize:    50 * 1024 * 1024,
			MaxBackups: 7,
			LocalTime:  false,
		},
	},
	Data: log.Logger{
		Level: log.InfoLevel,
		Writer: &log.FileWriter{
			Filename:   "data.log",
			MaxSize:    50 * 1024 * 1024,
			MaxBackups: 7,
			LocalTime:  false,
		},
	},
}

func main() {
	logger.Console.Info().Msgf("hello world")
	logger.Access.Log().Msgf("handle request")
	logger.Data.Log().Msgf("some data")
}

SyslogWriter & JournalWriter & EventlogWriter

To log to a syslog server, using SyslogWriter.

log.DefaultLogger.Writer = &log.SyslogWriter{
	Network : "unixgram",                     // "tcp"
	Address : "/run/systemd/journal/syslog",  // "192.168.0.2:601"
	Tag     : "",
	Marker  : "@cee:",
	Dial    : net.Dial,
}
log.Info().Msg("hi")

// Output: <6>Oct 5 16:25:38 [237]: @cee:{"time":"2020-10-05T16:25:38.026Z","level":"info","message":"hi"}

To log to linux systemd journald, using JournalWriter.

log.DefaultLogger.Writer = &log.JournalWriter{}
log.Info().Int("number", 42).Str("foo", "bar").Msg("hello world")

To log to windows system event, using EventlogWriter.

log.DefaultLogger.Writer = &log.EventlogWriter{
	Source: ".NET Runtime",
	ID:     1000,
}
log.Info().Int("number", 42).Str("foo", "bar").Msg("hello world")

AsyncWriter

To logging asynchronously for performance stability, use AsyncWriter.

logger := log.Logger{
	Level:  log.InfoLevel,
	Writer: &log.AsyncWriter{
		ChannelSize: 100,
		Writer:      &log.FileWriter{
			Filename:   "main.log",
			FileMode:   0600,
			MaxSize:    50*1024*1024,
			MaxBackups: 7,
			LocalTime:  false,
		},
	},
}

logger.Info().Int("number", 42).Str("foo", "bar").Msg("a async info log")
logger.Warn().Int("number", 42).Str("foo", "bar").Msg("a async warn log")
logger.Writer.(io.Closer).Close()

Note: To flush data and quit safely, call AsyncWriter.Close() explicitly.

StdLog & Logr & Grpc Interceptor

Using wrapped loggers for stdlog/grpc/logr. playground

package main

import (
	stdLog "log"
	"github.com/go-logr/logr"
	"github.com/phuslu/log"
	"google.golang.org/grpc/grpclog"
)

func main() {
	ctx := log.NewContext(nil).Str("tag", "hi log").Value()

	var stdlog *stdLog.Logger = log.DefaultLogger.Std(log.InfoLevel, ctx, "prefix ", stdLog.LstdFlags)
	stdlog.Print("hello from stdlog Print")
	stdlog.Println("hello from stdlog Println")
	stdlog.Printf("hello from stdlog %s", "Printf")

	var grpclog grpclog.LoggerV2 = log.DefaultLogger.Grpc(ctx)
	grpclog.Infof("hello %s", "grpclog Infof message")
	grpclog.Errorf("hello %s", "grpclog Errorf message")

	var logrLog logr.Logger = log.DefaultLogger.Logr(ctx)
	logrLog = logrLog.WithName("a_named_logger").WithValues("a_key", "a_value")
	logrLog.Info("hello", "foo", "bar", "number", 42)
	logrLog.Error(errors.New("this is a error"), "hello", "foo", "bar", "number", 42)
}

User-defined Data Structure

To log with user-defined struct effectively, implements MarshalLogObject. playground

package main

import (
	"github.com/phuslu/log"
)

type User struct {
	ID   int
	Name string
	Pass string
}

func (u *User) MarshalLogObject(e *log.Entry) {
	e.Int("id", u.ID).Str("name", u.Name).Str("password", "***")
}

func main() {
	log.Info().Object("user", &User{1, "neo", "123456"}).Msg("")
	log.Info().EmbedObject(&User{2, "john", "abc"}).Msg("")
}

// Output:
//   {"time":"2020-07-12T05:03:43.949Z","level":"info","user":{"id":1,"name":"neo","password":"***"}}
//   {"time":"2020-07-12T05:03:43.949Z","level":"info","id":2,"name":"john","password":"***"}

Contextual Fields

To add preserved key:value pairs to each entry, use NewContext. playground

ctx := log.NewContext(nil).Str("ctx_str", "a ctx str").Value()

logger := log.Logger{Level: log.InfoLevel}
logger.Debug().Context(ctx).Int("no0", 0).Msg("zero")
logger.Info().Context(ctx).Int("no1", 1).Msg("first")
logger.Info().Context(ctx).Int("no2", 2).Msg("second")

// Output:
//   {"time":"2020-07-12T05:03:43.949Z","level":"info","ctx_str":"a ctx str","no1":1,"message":"first"}
//   {"time":"2020-07-12T05:03:43.949Z","level":"info","ctx_str":"a ctx str","no2":2,"message":"second"}

High Performance

A quick and simple benchmark with zap/zerolog, which runs on github actions:

// go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem log_test.go
package main

import (
	"io/ioutil"
	"testing"

	"github.com/phuslu/log"
	"github.com/rs/zerolog"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var fakeMessage = "Test logging, but use a somewhat realistic message length. "

func BenchmarkZap(b *testing.B) {
	logger := zap.New(zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		zapcore.AddSync(ioutil.Discard),
		zapcore.InfoLevel,
	))
	for i := 0; i < b.N; i++ {
		logger.Info(fakeMessage, zap.String("foo", "bar"), zap.Int("int", 42))
	}
}

func BenchmarkZeroLog(b *testing.B) {
	logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger()
	for i := 0; i < b.N; i++ {
		logger.Info().Str("foo", "bar").Int("int", 42).Msg(fakeMessage)
	}
}

func BenchmarkPhusLog(b *testing.B) {
	logger := log.Logger{
		TimeFormat: "", // uses rfc3339 by default
		Writer:     log.IOWriter{ioutil.Discard},
	}
	for i := 0; i < b.N; i++ {
		logger.Info().Str("foo", "bar").Int("int", 42).Msg(fakeMessage)
	}
}

A Performance result as below, for daily benchmark results see github actions

BenchmarkZap-4       	12432787	       996 ns/op	     128 B/op	       1 allocs/op
BenchmarkZeroLog-4   	24231926	       496 ns/op	       0 B/op	       0 allocs/op
BenchmarkPhusLog-4   	62495569	       194 ns/op	       0 B/op	       0 allocs/op

This library uses the following special techniques to achieve better performance,

  1. handwriting time formatting
  2. manual inlining
  3. unrolled functions

A Real World Example

The example starts a geoip http server which supports change log level dynamically

package main

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"

	"github.com/phuslu/iploc"
	"github.com/phuslu/log"
)

type Config struct {
	Listen struct {
		Tcp string
	}
	Log struct {
		Level   string
		Maxsize int64
		Backups int
	}
}

type Handler struct {
	Config       *Config
	AccessLogger log.Logger
}

func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	reqID := log.NewXID()
	remoteIP, _, _ := net.SplitHostPort(req.RemoteAddr)
	geo := iploc.Country(net.ParseIP(remoteIP))

	h.AccessLogger.Log().
		Xid("req_id", reqID).
		Str("host", req.Host).
		Bytes("geo", geo).
		Str("remote_ip", remoteIP).
		Str("request_uri", req.RequestURI).
		Str("user_agent", req.UserAgent()).
		Str("referer", req.Referer()).
		Msg("access log")

	switch req.RequestURI {
	case "/debug", "/info", "/warn", "/error":
		log.DefaultLogger.SetLevel(log.ParseLevel(req.RequestURI[1:]))
	default:
		fmt.Fprintf(rw, `{"req_id":"%s","ip":"%s","geo":"%s"}`, reqID, remoteIP, geo)
	}
}

func main() {
	config := new(Config)
	err := json.Unmarshal([]byte(`{
		"listen": {
			"tcp": ":8080"
		},
		"log": {
			"level": "debug",
			"maxsize": 1073741824,
			"backups": 5
		}
	}`), config)
	if err != nil {
		log.Fatal().Msgf("json.Unmarshal error: %+v", err)
	}

	handler := &Handler{
		Config: config,
		AccessLogger: log.Logger{
			Writer: &log.FileWriter{
				Filename:   "access.log",
				MaxSize:    config.Log.Maxsize,
				MaxBackups: config.Log.Backups,
				LocalTime:  true,
			},
		},
	}

	if log.IsTerminal(os.Stderr.Fd()) {
		log.DefaultLogger = log.Logger{
			Level:      log.ParseLevel(config.Log.Level),
			Caller:     1,
			TimeFormat: "15:04:05",
			Writer: &log.ConsoleWriter{
				ColorOutput:    true,
				EndWithMessage: true,
			},
		}
		handler.AccessLogger = log.DefaultLogger
	} else {
		log.DefaultLogger = log.Logger{
			Level: log.ParseLevel(config.Log.Level),
			Writer: &log.FileWriter{
				Filename:   "main.log",
				MaxSize:    config.Log.Maxsize,
				MaxBackups: config.Log.Backups,
				LocalTime:  true,
			},
		}
	}

	server := &http.Server{
		Addr:     config.Listen.Tcp,
		ErrorLog: log.DefaultLogger.Std(log.ErrorLevel, nil, "", 0),
		Handler:  handler,
	}

	log.Fatal().Err(server.ListenAndServe()).Msg("listen failed")
}

Acknowledgment

This log is heavily inspired by zerolog, glog, gjson and lumberjack.

Owner
Comments
  • Disable JSON in log.FileWriter

    Disable JSON in log.FileWriter

    I have trying to implement logger in my app, have few things to be cleared as those are not present in readme or didn't get it how to do. Those are:

    1. Disable JSON format and log like nginx log in log.FileWriter or like its there in ConsoleWriter basically formating in FileWriter
    2. Write JSON to console, can this be achieved by ConsoleWriter.Formatter
    3. It has been mentioned for log.MultiWriter that It can write level to different file, can we have custom/default log level and lock file to level only in that file or console.
    4. readme mentioned this log lib is inspired by lumberjack also but can lumberjack be used to rotate log file in log.FileWriter. Not sure about performance with cron for rotating logs.
  • Compatibility with rs/logbench

    Compatibility with rs/logbench

    I like this logger a lot because it has zero dependencies and an intuitive API. Therefore, I hope that it can gain more popularity among the Go community.

    I have created a draft pull request rs/logbench#4 in the rs/logbench repository to include this logger. However, this logger currently does not pass the validation of logbench, more details can be found in rs/logbench#4.

    To pass the logbench validation, this logger must

    1. Allow timestamp to be disabled
    2. Support different encoder for Time and Duration
  • A new MultiWriters design that doesn't require parsing levels

    A new MultiWriters design that doesn't require parsing levels

    In the current implementation, MultiWriter is used as Logger.Writer. Each time a message is written, we need to first parse the level from the input.

    I can't help wondering if this can be avoided. In this pull request (still WIP), I want to propose a new design to avoid the level parsing step.

    Instead of making MultiWriter an io.Writer, we just make it an optional field for Logger. If it's set, we use it instead of Writer. When creating an Event, we use the level to decide what writers to use by calling MultiWriter.CombineWriters, which collects the matched writers into one io.Writer implementation called CombinedWriter:

    type CombineWriter []io.Writer
    
    func (cw CombineWriter) Write(p []byte) (n int, firstErr error) {
    	for _, w := range cw {
    		var err error
    		n, err = w.Write(p)
    		if err != nil && firstErr == nil {
    			firstErr = err
    		}
    	}
    	return n, firstErr
    }
    
    var _ io.Writer = CombineWriter{}
    

    Please let me know what you think about this design. Thanks.

  • Phuslu Wrapper for Fiber

    Phuslu Wrapper for Fiber

    Hi, I've created phuslu log wrapper for Fiber. Please suggest if you have any opinions or suggestion to improve

    https://github.com/sujit-baniya/sblogger

  • FileWriter Filename

    FileWriter Filename

    This could be considered as an improvement? maybe?

    When you output to file, you have name.timestamp.ext that's ok... but when there are many cases when this is a little bit unconfutable. I think I should be name.ext and when rotated name.timestamp.ext

    • If you are testing
    • If you have a console command options
    • If you need to stop and start the project a couple of times
    • ...

    When rotation happens, because of whatever condition, having timestamp is ok, but if there are no conditions that have been meet it should be just the name and append data.

    For compatibility issues we could implement an AutoRotateOnInitialize (bool), or OnlyTimestampOnRotate (bool) a simpler name...

  • log With()

    log With()

    Great logger, simple and easy to use. Are you looking to add a with() function, as this is most important when logging web transactions and correlation of single web requests, through the core application.

    Thanks, Bob

  • Gzip examples writes truncated file

    Gzip examples writes truncated file

    Following the example in #19

    async := &log.AsyncWriter{
        ChannelSize: 1,
        Writer: log.IOWriter{
            Writer: gzip.NewWriter(&log.FileWriter{
                Filename:   "foo.log.gz",
                MaxSize:    50 * 1024 * 1024,
                MaxBackups: 7,
            }),
        },
    }
    logger := log.Logger{
        Writer: async,
    }
    logger.Info().Msg("hello phuslu")
    async.Close()
    
    ls -la
    total 40
    -rw-r--r--  1 cgrindst  staff    10B May 20 11:07 foo.log.2022-05-20T15-07-25.gz
    lrwxr-xr-x  1 cgrindst  staff    30B May 20 11:07 foo.log.gz@ -> foo.log.2022-05-20T15-07-25.gz
    
    file foo.log.2022-05-20T15-07-25.gz 
    foo.log.2022-05-20T15-07-25.gz: gzip compressed data, truncated
    
    gunzip foo.log.2022-05-20T15-07-25.gz 
    gunzip: foo.log.2022-05-20T15-07-25.gz: unexpected end of file
    gunzip: foo.log.2022-05-20T15-07-25.gz: uncompress failed
    
  • W3C extended log file format fromatter

    W3C extended log file format fromatter

    With very little effort I was able to implement a custom Formatter to make the ConsoleWriter write log lines in a W3C Extended Log File Format compliant way (see: https://www.w3.org/TR/WD-logfile.html)

    Although JSON structured logging is all the rage today (and, in fact, I personally prefer JSON big time) there's a lot of log analyzers out there that are unable to read JSON, but are perfectly capable of importing W3C log files.

    The problem is that there is no Formatter in the FileWriter writer, which is exactly where it would need to be, if we are to be able to save log files in W3C format.

    The way I see it, this could be achieved 2 ways:

    • add a Formatter to the existing FileWriter interface
    • create a whole new W3CLogFileWriter interface (probably not a good idea)

    Also, W3C log files require a "header" to be written at the beginning of every file, so ideally each time the file is rotated this header should be automatically written to the file, before we can actually add log lines to it.

    Here's a W3C log file header example (taken from Microsoft IIS):

    #Software: Internet Information Services 8.0 
    #Version: 1.0 
    #Date: 2021-05-02 17:42:15 
    #Fields: time c-ip cs-method cs-uri-stem sc-status cs-version
    

    This could probably be achieved with some custom wizardry in the Cleaner function of the FileWriter interface, but while we're at it, why not make everyone life's easier, and provide a string property for it, and have the Writer automagically add it at the beginning of each new file, right when the log its rotated?

    JSON logging is still far far better, I think we can all agree on that. But W3C log file format is not dead, and is required by more users than I originally thought possible. Any feedback on this would be greatly appreciated. Thank you in advance.

  • MultiWriter based on io.Writer

    MultiWriter based on io.Writer

    Sometimes I need to write to different outputs with a selected level. For example, given a Debug level I would like to be able to write to console, log file and syslog.

    Have tried io.MultiWriter but ConsoleWriter does not implement io.Writer method.

    Alternatively we could implement something like https://github.com/rs/zerolog/blob/117cb53bc66413d9a810ebed32383e53416347e3/writer.go#L88

    I have tried several things and the only option I see is MultiWriter but it's level based not io.Writer based.

    Any suggestions?

  • Single Quotation Mark

    Single Quotation Mark

    Given the following example:

    var logger = struct {
    	Console plog.Logger
    	Pretty  plog.Logger
    	File    plog.Logger
    }{
    	Console: plog.Logger{
    		Level:  plog.TraceLevel,
    		Caller: 1,
    		Writer: &plog.IOWriter{os.Stdout},
    	},
    	Pretty: plog.Logger{
    		Level:  plog.TraceLevel,
    		Caller: 1,
    		Writer: &plog.ConsoleWriter{
    			ColorOutput:    true,
    			QuoteString:    true,
    			EndWithMessage: true,
    		},
    	},
    	File: plog.Logger{
    		Level:  plog.TraceLevel,
    		Caller: 1,
    		Writer: &plog.FileWriter{
    			Filename:     "plog_test.log",
    			FileMode:     0600,
    			MaxSize:      100 * 1024 * 1024,
    			MaxBackups:   7,
    			EnsureFolder: true,
    			LocalTime:    true,
    		},
    	},
    }
    
    func testSpecial() {
    	logger.Console.Info().Msg("can't handle single quotation mark")
    	logger.Pretty.Info().Msg("can't handle single quotation mark")
    	logger.File.Info().Msg("can't handle single quotation mark")
    }
    

    I have the following outputs: Console: {"time":"2021-06-12T18:10:42.379+02:00","level":"info","caller":"main.go:233","goid":1,"message":"can\u0027t handle single quotation mark"} Pretty: 2021-06-12T18:10:42.379+02:00 INF 1 main.go:234 > can't handle single quotation mark File: {"time":"2021-06-12T18:10:42.379+02:00","level":"info","caller":"main.go:235","goid":1,"message":"can\u0027t handle single quotation mark"}

    It seems it can\u0027t handle it correctly... lol

  • [Question] How can I implement this library with other libraries like Gorm v2?

    [Question] How can I implement this library with other libraries like Gorm v2?

    Currently Gorm v2 is using it's default logger. I want to use this library for Gorm v2.

    How can I implement this library?

    Here's the doc they are suggesting to implement but can't figure out how I could implement this log

Gomol is a library for structured, multiple-output logging for Go with extensible logging outputs

gomol Gomol (Go Multi-Output Logger) is an MIT-licensed structured logging library for Go. Gomol grew from a desire to have a structured logging libra

Sep 26, 2022
Structured logging package for Go.
Structured logging package for Go.

Package log implements a simple structured logging API inspired by Logrus, designed with centralization in mind. Read more on Medium. Handlers apexlog

Dec 24, 2022
Simple, configurable and scalable Structured Logging for Go.

log Log is a simple, highly configurable, Structured Logging library Why another logging library? There's allot of great stuff out there, but also tho

Sep 26, 2022
Structured, composable logging for Go
Structured, composable logging for Go

log15 Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is mo

Dec 18, 2022
Structured, pluggable logging for Go.
Structured, pluggable logging for Go.

Logrus Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. Logrus is in maintenance-mode. We wi

Jan 9, 2023
Blazing fast, structured, leveled logging in Go.

⚡ zap Blazing fast, structured, leveled logging in Go. Installation go get -u go.uber.org/zap Note that zap only supports the two most recent minor ve

Jan 7, 2023
Hierarchical, leveled, and structured logging library for Go

spacelog Please see http://godoc.org/github.com/spacemonkeygo/spacelog for info License Copyright (C) 2014 Space Monkey, Inc. Licensed under the Apach

Apr 27, 2021
Logrus is a structured, pluggable logging for Go.
Logrus is a structured, pluggable logging for Go.

Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger.

May 25, 2021
Minimal structured logging library for Go
Minimal structured logging library for Go

slog slog is a minimal structured logging library for Go. Install go get cdr.dev/slog Features Minimal API First class context.Context support First c

Dec 29, 2022
Fully asynchronous, structured, pluggable logging for Go.

logr Logr is a fully asynchronous, contextual logger for Go. It is very much inspired by Logrus but addresses two issues: Logr is fully asynchronous,

Dec 28, 2022
structured logging helper

Logart Logart is a structured logging tool that aims to simplify logging to a database It is not yet in stable state, but is used in production and ac

Apr 24, 2021
Go-metalog - Standard API for structured logging

Metalog is a standard API for structured logging and adapters for its implementa

Jan 20, 2022
A simple logging module for go, with a rotating file feature and console logging.

A simple logging module for go, with a rotating file feature and console logging. Installation go get github.com/jbrodriguez/mlog Usage Sample usage W

Dec 14, 2022
FactorLog is a logging infrastructure for Go that provides numerous logging functions for whatever your style may be
FactorLog is a logging infrastructure for Go that provides numerous logging functions for whatever your style may be

FactorLog FactorLog is a fast logging infrastructure for Go that provides numerous logging functions for whatever your style may be. It could easily b

Aug 3, 2022
Package logging implements a logging infrastructure for Go
Package logging implements a logging infrastructure for Go

Golang logging library Package logging implements a logging infrastructure for Go. Its output format is customizable and supports different logging ba

Nov 10, 2021
Package for easy logging to logstash http input from microservices

Micro Logger package for easy logging to logstash http input from microservices

Dec 28, 2021
Structured log interface

Structured log interface Package log provides the separation of the logging interface from its implementation and decouples the logger backend from yo

Sep 26, 2022
A minimal and extensible structured logger

⚠️ PRE-RELEASE ⚠️ DO NOT IMPORT THIS MODULE YOUR PROJECT WILL BREAK package log package log provides a minimal interface for structured logging in ser

Jan 7, 2023
Search and analysis tooling for structured logs

Zed The Zed system provides an open-source, cloud-native, and searchable data lake for semi-structured and structured data. Zed lakes utilize a supers

Jan 5, 2023