Skip to content

Commit

Permalink
Merge pull request #11 from zerodha/opts
Browse files Browse the repository at this point in the history
specify options in New
  • Loading branch information
mr-karan authored Jul 7, 2022
2 parents 08ebb87 + edd3c10 commit bacb284
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 117 deletions.
28 changes: 11 additions & 17 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func BenchmarkNoField(b *testing.B) {
logger := logf.New(io.Discard)
logger := logf.New(logf.Opts{Writer: io.Discard})
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(p *testing.PB) {
Expand All @@ -20,7 +20,7 @@ func BenchmarkNoField(b *testing.B) {
}

func BenchmarkOneField(b *testing.B) {
logger := logf.New(io.Discard)
logger := logf.New(logf.Opts{Writer: io.Discard})
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(p *testing.PB) {
Expand All @@ -31,7 +31,7 @@ func BenchmarkOneField(b *testing.B) {
}

func BenchmarkThreeFields(b *testing.B) {
logger := logf.New(io.Discard)
logger := logf.New(logf.Opts{Writer: io.Discard})
b.ReportAllocs()
b.ResetTimer()

Expand All @@ -45,7 +45,7 @@ func BenchmarkThreeFields(b *testing.B) {
}

func BenchmarkErrorField(b *testing.B) {
logger := logf.New(io.Discard)
logger := logf.New(logf.Opts{Writer: io.Discard})
b.ReportAllocs()
b.ResetTimer()

Expand All @@ -59,7 +59,7 @@ func BenchmarkErrorField(b *testing.B) {
}

func BenchmarkHugePayload(b *testing.B) {
logger := logf.New(io.Discard)
logger := logf.New(logf.Opts{Writer: io.Discard})
b.ReportAllocs()
b.ResetTimer()

Expand All @@ -82,8 +82,7 @@ func BenchmarkHugePayload(b *testing.B) {
}

func BenchmarkThreeFields_WithCaller(b *testing.B) {
logger := logf.New(io.Discard)
logger.SetCallerFrame(true, 3)
logger := logf.New(logf.Opts{Writer: io.Discard, CallerSkipFrameCount: 3, EnableCaller: true})
b.ReportAllocs()
b.ResetTimer()

Expand All @@ -97,8 +96,7 @@ func BenchmarkThreeFields_WithCaller(b *testing.B) {
}

func BenchmarkNoField_WithColor(b *testing.B) {
logger := logf.New(io.Discard)
logger.SetColorOutput(true)
logger := logf.New(logf.Opts{Writer: io.Discard, EnableColor: true})
b.ReportAllocs()
b.ResetTimer()

Expand All @@ -110,8 +108,7 @@ func BenchmarkNoField_WithColor(b *testing.B) {
}

func BenchmarkOneField_WithColor(b *testing.B) {
logger := logf.New(io.Discard)
logger.SetColorOutput(true)
logger := logf.New(logf.Opts{Writer: io.Discard, EnableColor: true})
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(p *testing.PB) {
Expand All @@ -122,8 +119,7 @@ func BenchmarkOneField_WithColor(b *testing.B) {
}

func BenchmarkThreeFields_WithColor(b *testing.B) {
logger := logf.New(io.Discard)
logger.SetColorOutput(true)
logger := logf.New(logf.Opts{Writer: io.Discard, EnableColor: true})
b.ReportAllocs()
b.ResetTimer()

Expand All @@ -137,8 +133,7 @@ func BenchmarkThreeFields_WithColor(b *testing.B) {
}

func BenchmarkErrorField_WithColor(b *testing.B) {
logger := logf.New(io.Discard)
logger.SetColorOutput(true)
logger := logf.New(logf.Opts{Writer: io.Discard, EnableColor: true})
b.ReportAllocs()
b.ResetTimer()

Expand All @@ -152,8 +147,7 @@ func BenchmarkErrorField_WithColor(b *testing.B) {
}

func BenchmarkHugePayload_WithColor(b *testing.B) {
logger := logf.New(io.Discard)
logger.SetColorOutput(true)
logger := logf.New(logf.Opts{Writer: io.Discard, EnableColor: true})
b.ReportAllocs()
b.ResetTimer()

Expand Down
24 changes: 9 additions & 15 deletions examples/main.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
package main

import (
"os"
"time"

"github.com/zerodha/logf"
)

func main() {
logger := logf.New(os.Stderr)

// Basic log.
logger := logf.New(logf.Opts{
EnableColor: true,
Level: logf.DebugLevel,
CallerSkipFrameCount: 3,
EnableCaller: true,
TimestampFormat: time.RFC3339Nano,
})

// Basic logs.
logger.Info("starting app")

// Enable colored output.
logger.SetColorOutput(true)

// Change verbosity on the fly.
logger.SetLevel(logf.DebugLevel)
logger.Debug("meant for debugging app")

// Add extra keys to the log.
Expand All @@ -26,11 +25,6 @@ func main() {
// Log with error key.
logger.Error("error fetching details", "error", "this is a dummy error")

// Enable `caller` field in the log and specify the number of frames to skip to get the caller.
logger.SetCallerFrame(true, 3)
// Change the default timestamp format.
logger.SetTimestampFormat(time.RFC3339Nano)

// Log the error and set exit code as 1.
logger.Fatal("goodbye world")
}
127 changes: 55 additions & 72 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
stdlog "log"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
Expand All @@ -23,26 +22,31 @@ var (
bufPool byteBufferPool
)

type Opts struct {
Writer io.Writer
Level Level
TimestampFormat string
EnableColor bool
EnableCaller bool
CallerSkipFrameCount int
}

// Logger is the interface for all log operations
// related to emitting logs.
type Logger struct {
out io.Writer // Output destination.
level Level // Verbosity of logs.
tsFormat string // Timestamp format.
enableColor bool // Colored output.
enableCaller bool // Print caller information.
callerSkipFrameCount int // Number of frames to skip when detecting caller
out io.Writer // Output destination.
Opts
}

// Severity level of the log.
type Level int

const (
DebugLevel Level = iota // 0
InfoLevel // 1
WarnLevel // 2
ErrorLevel // 3
FatalLevel // 4
DebugLevel Level = iota + 1 // 1
InfoLevel // 2
WarnLevel // 3
ErrorLevel // 4
FatalLevel // 5
)

// ANSI escape codes for coloring text in console.
Expand All @@ -64,20 +68,21 @@ var colorLvlMap = [...]string{
}

// New instantiates a logger object.
// It writes to `stderr` as the default and it's non configurable.
func New(out io.Writer) Logger {
// Initialise logger with sane defaults.
if out == nil {
out = os.Stderr
func New(opts Opts) Logger {
// Initialise fallbacks if unspecified by user.
if opts.Writer == nil {
opts.Writer = os.Stderr
}
if opts.TimestampFormat == "" {
opts.TimestampFormat = defaultTSFormat
}
if opts.Level == 0 {
opts.Level = InfoLevel
}

return Logger{
out: newSyncWriter(out),
level: InfoLevel,
tsFormat: defaultTSFormat,
enableColor: false,
enableCaller: false,
callerSkipFrameCount: 0,
out: newSyncWriter(opts.Writer),
Opts: opts,
}
}

Expand Down Expand Up @@ -124,38 +129,6 @@ func (l Level) String() string {
}
}

// SetLevel sets the verbosity for logger.
// Verbosity can be dynamically changed by the caller.
func (l Logger) SetLevel(lvl Level) Logger {
l.level = lvl
return l
}

// SetWriter sets the output writer for the logger
func (l Logger) SetWriter(w io.Writer) Logger {
l.out = &syncWriter{w: w}
return l
}

// SetTimestampFormat sets the timestamp format for the `timestamp` key.
func (l Logger) SetTimestampFormat(f string) Logger {
l.tsFormat = f
return l
}

// SetColorOutput enables/disables colored output.
func (l Logger) SetColorOutput(color bool) Logger {
l.enableColor = color
return l
}

// SetCallerFrame enables/disables the caller source in the log line.
func (l Logger) SetCallerFrame(caller bool, depth int) Logger {
l.enableCaller = caller
l.callerSkipFrameCount = depth
return l
}

// Debug emits a debug log line.
func (l Logger) Debug(msg string, fields ...any) {
l.handleLog(msg, DebugLevel, fields...)
Expand Down Expand Up @@ -188,20 +161,20 @@ func (l Logger) Fatal(msg string, fields ...any) {
func (l Logger) handleLog(msg string, lvl Level, fields ...any) {
// Discard the log if the verbosity is higher.
// For eg, if the lvl is `3` (error), but the incoming message is `0` (debug), skip it.
if lvl < l.level {
if lvl < l.Opts.Level {
return
}

// Get a buffer from the pool.
buf := bufPool.Get()

// Write fixed keys to the buffer before writing user provided ones.
writeTimeToBuf(buf, l.tsFormat, lvl, l.enableColor)
writeToBuf(buf, "level", lvl, lvl, l.enableColor, true)
writeStringToBuf(buf, "message", msg, lvl, l.enableColor, true)
writeTimeToBuf(buf, l.Opts.TimestampFormat, lvl, l.Opts.EnableColor)
writeToBuf(buf, "level", lvl, lvl, l.Opts.EnableColor, true)
writeStringToBuf(buf, "message", msg, lvl, l.Opts.EnableColor, true)

if l.enableCaller {
writeToBuf(buf, "caller", caller(l.callerSkipFrameCount), lvl, l.enableColor, true)
if l.Opts.EnableCaller {
writeCallerToBuf(buf, "caller", l.Opts.CallerSkipFrameCount, lvl, l.EnableColor, true)
}

// Format the line as logfmt.
Expand Down Expand Up @@ -230,7 +203,7 @@ func (l Logger) handleLog(msg string, lvl Level, fields ...any) {
val = fields[i]
}

writeToBuf(buf, key, val, lvl, l.enableColor, space)
writeToBuf(buf, key, val, lvl, l.Opts.EnableColor, space)
count++
}
buf.AppendString("\n")
Expand Down Expand Up @@ -273,6 +246,26 @@ func writeStringToBuf(buf *byteBuffer, key string, val string, lvl Level, color,
}
}

func writeCallerToBuf(buf *byteBuffer, key string, depth int, lvl Level, color, space bool) {
_, file, line, ok := runtime.Caller(depth)
if !ok {
file = "???"
line = 0
}
if color {
buf.AppendString(getColoredKey(key, lvl))
} else {
buf.AppendString(key)
}
buf.AppendByte('=')
escapeAndWriteString(buf, file)
buf.AppendByte(':')
buf.AppendInt(int64(line))
if space {
buf.AppendByte(' ')
}
}

// writeToBuf takes key, value and additional options to write to the buffer in logfmt.
func writeToBuf(buf *byteBuffer, key string, val any, lvl Level, color, space bool) {
if color {
Expand Down Expand Up @@ -323,16 +316,6 @@ func getColoredKey(k string, lvl Level) string {
return colorLvlMap[lvl] + k + reset
}

// caller returns the file:line of the caller.
func caller(depth int) string {
_, file, line, ok := runtime.Caller(depth)
if !ok {
file = "???"
line = 0
}
return file + ":" + strconv.Itoa(line)
}

// checkEscapingRune returns true if the rune is to be escaped.
func checkEscapingRune(r rune) bool {
return r == '=' || r == ' ' || r == '"' || r == utf8.RuneError
Expand Down
Loading

0 comments on commit bacb284

Please sign in to comment.