Skip to content

Commit 4870522

Browse files
committed
feat: Create log/ilog package from github.com/kunitsucom/ilog.go
1 parent 09791ab commit 4870522

16 files changed

+2397
-0
lines changed

go.work

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ use (
44
.
55
./grpc
66
./integrationtest
7+
./log/ilog/implementations/zap
8+
./log/ilog/implementations/zerolog
79
)

log/ilog/context.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package ilog
2+
3+
import "context"
4+
5+
type contextKeyLogger struct{}
6+
7+
func FromContext(ctx context.Context) (logger Logger) { //nolint:ireturn
8+
if ctx == nil {
9+
Global().Copy().AddCallerSkip(1).Errorf("ilog: nil context")
10+
return Global().Copy()
11+
}
12+
13+
v := ctx.Value(contextKeyLogger{})
14+
l, ok := v.(Logger)
15+
16+
if !ok {
17+
Global().Copy().AddCallerSkip(1).Errorf("ilog: type assertion failed: expected=ilog.Logger, actual=%T, value=%#v", v, v)
18+
return Global().Copy()
19+
}
20+
21+
return l.Copy()
22+
}
23+
24+
func WithContext(ctx context.Context, logger Logger) context.Context {
25+
return context.WithValue(ctx, contextKeyLogger{}, logger)
26+
}

log/ilog/context_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package ilog //nolint:testpackage
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"regexp"
7+
"testing"
8+
"time"
9+
)
10+
11+
//nolint:paralleltest,tparallel
12+
func TestContext(t *testing.T) {
13+
//nolint:paralleltest,tparallel
14+
t.Run("success", func(t *testing.T) {
15+
buf := bytes.NewBuffer(nil)
16+
ctx := WithContext(context.Background(), NewBuilder(DebugLevel, NewSyncWriter(buf)).SetTimestampZone(time.UTC).Build())
17+
l := FromContext(ctx)
18+
l.Debugf("Debugf")
19+
if expected := regexp.MustCompilePOSIX(`{"severity":"DEBUG","timestamp":"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.?[0-9]*Z","caller":"ilog\.go/[a-z_]+_test\.go:[0-9]+","message":"Debugf"}`); !expected.Match(buf.Bytes()) {
20+
t.Errorf("❌: !expected.Match(buf.Bytes()):\n%s", buf)
21+
}
22+
t.Logf("ℹ️: buf:\n%s", buf)
23+
})
24+
25+
t.Run("failure,nilContext", func(t *testing.T) {
26+
buf := bytes.NewBuffer(nil)
27+
defer SetGlobal(NewBuilder(DebugLevel, NewSyncWriter(buf)).SetTimestampZone(time.UTC).Build())()
28+
_ = FromContext(nil) //nolint:staticcheck
29+
if expected := regexp.MustCompilePOSIX(`{"severity":"ERROR","timestamp":"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.?[0-9]*Z","caller":"ilog\.go/[a-z_]+\.go:[0-9]+","message":"ilog: nil context"}`); !expected.Match(buf.Bytes()) {
30+
t.Errorf("❌: !expected.Match(buf.Bytes()):\n%s", buf)
31+
}
32+
t.Logf("ℹ️: buf:\n%s", buf)
33+
})
34+
35+
t.Run("failure,invalidType", func(t *testing.T) {
36+
buf := bytes.NewBuffer(nil)
37+
defer SetGlobal(NewBuilder(DebugLevel, NewSyncWriter(buf)).SetTimestampZone(time.UTC).Build())()
38+
_ = FromContext(context.WithValue(context.Background(), contextKeyLogger{}, "invalid")) //nolint:staticcheck
39+
if expected := regexp.MustCompilePOSIX(`{"severity":"ERROR","timestamp":"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.?[0-9]*Z","caller":"ilog\.go/[a-z_]+\.go:[0-9]+","message":"ilog: type assertion failed: expected=ilog.Logger, actual=string, value=\\"invalid\\""}`); !expected.Match(buf.Bytes()) {
40+
t.Errorf("❌: !expected.Match(buf.Bytes()):\n%s", buf)
41+
}
42+
t.Logf("ℹ️: buf:\n%s", buf)
43+
})
44+
}

log/ilog/global.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package ilog
2+
3+
import (
4+
"log"
5+
"os"
6+
"sync"
7+
)
8+
9+
//nolint:gochecknoglobals
10+
var (
11+
_globalLogger Logger = NewBuilder(DebugLevel, os.Stdout).Build() //nolint:revive
12+
_globalLoggerMu sync.RWMutex
13+
)
14+
15+
func L() Logger { //nolint:ireturn
16+
_globalLoggerMu.RLock()
17+
l := _globalLogger
18+
_globalLoggerMu.RUnlock()
19+
return l
20+
}
21+
22+
func Global() Logger { //nolint:ireturn
23+
return L()
24+
}
25+
26+
func SetGlobal(logger Logger) (rollback func()) {
27+
_globalLoggerMu.Lock()
28+
backup := _globalLogger
29+
_globalLogger = logger
30+
_globalLoggerMu.Unlock()
31+
return func() {
32+
SetGlobal(backup)
33+
}
34+
}
35+
36+
//nolint:gochecknoglobals
37+
var stdLoggerMu sync.Mutex
38+
39+
func SetStdLogger(l Logger) (rollback func()) {
40+
stdLoggerMu.Lock()
41+
defer stdLoggerMu.Unlock()
42+
43+
backupFlags := log.Flags()
44+
backupPrefix := log.Prefix()
45+
backupWriter := log.Writer()
46+
47+
log.SetFlags(0)
48+
log.SetPrefix("")
49+
const skipForStgLogger = 2
50+
log.SetOutput(l.Copy().AddCallerSkip(skipForStgLogger))
51+
52+
return func() {
53+
log.SetFlags(backupFlags)
54+
log.SetPrefix(backupPrefix)
55+
log.SetOutput(backupWriter)
56+
}
57+
}

log/ilog/global_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package ilog //nolint:testpackage
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"log"
7+
"regexp"
8+
"testing"
9+
"time"
10+
)
11+
12+
//nolint:paralleltest,tparallel
13+
func TestGlobal(t *testing.T) {
14+
//nolint:paralleltest,tparallel
15+
t.Run("success", func(t *testing.T) {
16+
buf := bytes.NewBuffer(nil)
17+
defer SetGlobal(NewBuilder(DebugLevel, NewSyncWriter(buf)).SetTimestampZone(time.UTC).Build())()
18+
expected := regexp.MustCompilePOSIX(`{"severity":"DEBUG","timestamp":"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.?[0-9]*Z","caller":"ilog\.go/[a-z_]+_test\.go:[0-9]+","message":"Logf","any":"any","bool":true,"bytes":"bytes","duration":"1h1m1.001001001s","error":"unexpected EOF","eof":"EOF","float32":1\.234567,"float64":1\.23456789,"int":-1,"int32":123456789,"int64":123456789,"string":"string","time":"2023-08-13T04:38:39\.123456789\+09:00","uint":1,"uint32":123456789,"uint64":123456789}`)
19+
l := Global().Copy().
20+
Any("any", "any").
21+
Bool("bool", true).
22+
Bytes("bytes", []byte("bytes")).
23+
Duration("duration", time.Hour+time.Minute+time.Second+time.Millisecond+time.Microsecond+time.Nanosecond).
24+
Err(io.ErrUnexpectedEOF).
25+
ErrWithKey("eof", io.EOF).
26+
Float32("float32", 1.234567).
27+
Float64("float64", 1.23456789).
28+
Int("int", -1).
29+
Int32("int32", 123456789).
30+
Int64("int64", 123456789).
31+
String("string", "string").
32+
Time("time", time.Date(2023, 8, 13, 4, 38, 39, 123456789, time.FixedZone("Asia/Tokyo", int(9*time.Hour/time.Second)))).
33+
Uint("uint", 1).
34+
Uint32("uint32", 123456789).
35+
Uint64("uint64", 123456789).
36+
Logger()
37+
l.Logf(DebugLevel, "Logf")
38+
if !expected.Match(buf.Bytes()) {
39+
t.Errorf("❌: !expected.Match(buf.Bytes()):\n%s", buf)
40+
}
41+
t.Logf("ℹ️: buf:\n%s", buf)
42+
})
43+
}
44+
45+
//nolint:paralleltest,tparallel
46+
func TestSetStdLogger(t *testing.T) {
47+
//nolint:paralleltest,tparallel
48+
t.Run("success", func(t *testing.T) {
49+
buf := bytes.NewBuffer(nil)
50+
expected := regexp.MustCompilePOSIX(`{"severity":"DEBUG","timestamp":"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.?[0-9]*Z","caller":"ilog.go/global_test.go:[0-9]+","message":"Print\\n","any":"any","bool":true,"bytes":"bytes","duration":"1h1m1.001001001s","error":"unexpected EOF","eof":"EOF","float32":1\.234567,"float64":1\.23456789,"int":-1,"int32":123456789,"int64":123456789,"string":"string","time":"2023-08-13T04:38:39\.123456789\+09:00","uint":1,"uint32":123456789,"uint64":123456789}` + "\n")
51+
l := NewBuilder(DebugLevel, NewSyncWriter(buf)).
52+
SetTimestampZone(time.UTC).
53+
Build().
54+
Any("any", "any").
55+
Bool("bool", true).
56+
Bytes("bytes", []byte("bytes")).
57+
Duration("duration", time.Hour+time.Minute+time.Second+time.Millisecond+time.Microsecond+time.Nanosecond).
58+
Err(io.ErrUnexpectedEOF).
59+
ErrWithKey("eof", io.EOF).
60+
Float32("float32", 1.234567).
61+
Float64("float64", 1.23456789).
62+
Int("int", -1).
63+
Int32("int32", 123456789).
64+
Int64("int64", 123456789).
65+
String("string", "string").
66+
Time("time", time.Date(2023, 8, 13, 4, 38, 39, 123456789, time.FixedZone("Asia/Tokyo", int(9*time.Hour/time.Second)))).
67+
Uint("uint", 1).
68+
Uint32("uint32", 123456789).
69+
Uint64("uint64", 123456789).
70+
Logger()
71+
defer SetStdLogger(l)()
72+
log.Print("Print")
73+
if !expected.Match(buf.Bytes()) {
74+
t.Errorf("❌: !expected.Match(buf.Bytes()):\n%s", buf)
75+
}
76+
t.Logf("ℹ️: buf:\n%s", buf)
77+
})
78+
}

log/ilog/ilog.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// ilog is a simple logging interface library package for Go.
2+
// By defining only the logger interface `ilog.Logger`, users can easily swap out the underlying logging implementation without changing their application code.
3+
package ilog
4+
5+
import (
6+
"errors"
7+
"time"
8+
)
9+
10+
// ErrLogEntryIsNotWritten is the error returned when a log entry is not written.
11+
var ErrLogEntryIsNotWritten = errors.New("ilog: log entry not written")
12+
13+
// Level is the type for logging level.
14+
type Level int8
15+
16+
const (
17+
DebugLevel Level = -8
18+
InfoLevel Level = 0
19+
WarnLevel Level = 8
20+
ErrorLevel Level = 16
21+
)
22+
23+
// Logger is the interface that has the basic logging methods.
24+
type Logger interface {
25+
// Level returns the current logging level of the logger.
26+
Level() (currentLoggerLevel Level)
27+
// SetLevel sets the logging level of the logger.
28+
SetLevel(level Level) (copied Logger)
29+
// AddCallerSkip adds the number of stack frames to skip to the logger.
30+
AddCallerSkip(skip int) (copied Logger)
31+
// Copy returns a copy of the logger.
32+
Copy() (copied Logger)
33+
34+
// common is the interface that has the common logging methods for both ilog.Logger and ilog.LogEntry.
35+
common
36+
}
37+
38+
// common is the interface that has the common logging methods for both ilog.Logger and ilog.LogEntry.
39+
type common interface {
40+
Any(key string, value interface{}) (entry LogEntry)
41+
Bool(key string, value bool) (entry LogEntry)
42+
Bytes(key string, value []byte) (entry LogEntry)
43+
Duration(key string, value time.Duration) (entry LogEntry)
44+
Err(err error) (entry LogEntry)
45+
ErrWithKey(key string, err error) (entry LogEntry)
46+
Float32(key string, value float32) (entry LogEntry)
47+
Float64(key string, value float64) (entry LogEntry)
48+
Int(key string, value int) (entry LogEntry)
49+
Int32(key string, value int32) (entry LogEntry)
50+
Int64(key string, value int64) (entry LogEntry)
51+
String(key, value string) (entry LogEntry)
52+
Time(key string, value time.Time) (entry LogEntry)
53+
Uint(key string, value uint) (entry LogEntry)
54+
Uint32(key string, value uint32) (entry LogEntry)
55+
Uint64(key string, value uint64) (entry LogEntry)
56+
57+
// Debugf logs a message at debug level.
58+
// If the argument is one, it is treated 1st argument as a simple string.
59+
// If the argument is more than one, it is treated 1st argument as a format string.
60+
Debugf(format string, args ...interface{})
61+
// Infof logs a message at info level.
62+
// If the argument is one, it is treated 1st argument as a simple string.
63+
// If the argument is more than one, it is treated 1st argument as a format string.
64+
Infof(format string, args ...interface{})
65+
// Warnf logs a message at warn level.
66+
// If the argument is one, it is treated 1st argument as a simple string.
67+
// If the argument is more than one, it is treated 1st argument as a format string.
68+
Warnf(format string, args ...interface{})
69+
// Errorf logs a message at error level.
70+
// If the argument is one, it is treated 1st argument as a simple string.
71+
// If the argument is more than one, it is treated 1st argument as a format string.
72+
Errorf(format string, args ...interface{})
73+
// Logf logs a message at the specified level.
74+
// If the argument is one, it is treated 1st argument as a simple string.
75+
// If the argument is more than one, it is treated 1st argument as a format string.
76+
Logf(level Level, format string, args ...interface{})
77+
78+
// Write implements io.Writer interface.
79+
// It outputs logs at the logger's log level.
80+
Write(p []byte) (n int, err error)
81+
}
82+
83+
// LogEntry is the interface that has the logging methods for a single log entry.
84+
type LogEntry interface {
85+
// common is the interface that has the common logging methods for both ilog.Logger and ilog.LogEntry.
86+
common
87+
88+
// Logger returns a new logger with the same fields of the log entry.
89+
Logger() (copied Logger)
90+
91+
// error: for considering undispatched LogEntry as error so that they can be detected by Go static analysis.
92+
error
93+
}

0 commit comments

Comments
 (0)