Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: phuslu/log
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.113
Choose a base ref
...
head repository: phuslu/log
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.0.114
Choose a head ref
  • 7 commits
  • 11 files changed
  • 3 contributors

Commits on Oct 21, 2024

  1. parse callfunc in formatter

    phuslu committed Oct 21, 2024
    Copy the full SHA
    f029ce5 View commit details
  2. tweak readme

    phuslu committed Oct 21, 2024
    Copy the full SHA
    9dd0404 View commit details

Commits on Oct 25, 2024

  1. fix zapslog panic

    phuslu committed Oct 25, 2024
    Copy the full SHA
    27c11d1 View commit details

Commits on Oct 26, 2024

  1. add NetIPAddrs to logger method

    phuslu committed Oct 26, 2024
    Copy the full SHA
    364d959 View commit details

Commits on Nov 21, 2024

  1. Update actions/setup-go and setup Dependabot

    Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
    Juneezee authored and phuslu committed Nov 21, 2024

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    b750508 View commit details

Commits on Feb 20, 2025

  1. upgrade actions to go 1.24

    phuslu committed Feb 20, 2025
    Copy the full SHA
    e40cb49 View commit details

Commits on Mar 6, 2025

  1. Add categorized loggers (#94)

    * Add categorized loggers
    
    * Always add "category" to context for categorized loggers
    
    * Refactor categoried logger into own file, update tests
    
    * Revert change for fluent *Logger
    
    * Refrain from using fluent interface for SetLevel
    
    * Add benchmark for worst-case usage of categorized logger
    lammel authored Mar 6, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    65a78a6 View commit details
Showing with 220 additions and 38 deletions.
  1. +7 −0 .github/dependabot.yml
  2. +8 −6 .github/workflows/benchmark.yml
  3. +8 −9 .github/workflows/build.yml
  4. +4 −3 .github/workflows/go-slog.yml
  5. +12 −9 README.md
  6. +17 −11 formatter.go
  7. +23 −0 formatter_test.go
  8. +21 −0 logger.go
  9. +35 −0 logger_categorized.go
  10. +83 −0 logger_categorized_test.go
  11. +2 −0 logger_test.go
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
14 changes: 8 additions & 6 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -11,11 +11,12 @@ jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v4
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
go-version: '1.24'
check-latest: true
- uses: actions/checkout@v4
cache-dependency-path: go.mod
- name: go mod tidy
run: |
mkdir bench
@@ -31,11 +32,12 @@ jobs:
sloghanders:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v4
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
go-version: '1.24'
check-latest: true
- uses: actions/checkout@v4
cache-dependency-path: go.mod
- name: go mod tidy
run: |
mkdir bench
17 changes: 8 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -10,8 +10,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['tip', '1.22', '1.21', '1.20', '1.19', '1.18']
go-version: ['tip', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19', '1.18']
steps:
- uses: actions/checkout@v4
- name: Setup Go
if: matrix.go-version != 'tip'
uses: actions/setup-go@master
@@ -28,7 +29,6 @@ jobs:
sudo tar xJf gotip.linux-amd64.tar.xz -C /usr/local
sudo ln -sf /usr/local/go/bin/go /usr/bin/go
go version
- uses: actions/checkout@v4
- name: Build
run: go build -v -race
- name: Test
@@ -38,12 +38,11 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v4
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
check-latest: true
- uses: actions/checkout@v4
- name: Lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.57.2
./bin/golangci-lint run
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.60
7 changes: 4 additions & 3 deletions .github/workflows/go-slog.yml
Original file line number Diff line number Diff line change
@@ -11,11 +11,12 @@ jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v4
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
go-version: '1.24'
check-latest: true
- uses: actions/checkout@v4
cache-dependency-path: go.mod
- name: go mod tidy
run: |
mkdir bench
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -96,15 +96,17 @@ type ConsoleWriter struct {

// FormatterArgs is a parsed sturct from json input
type FormatterArgs struct {
Time string // "2019-07-10T05:35:54.277Z"
Message string // "a structure message"
Level string // "info"
Caller string // "main.go:123"
Goid string // "1"
Stack string // "<stack string>"
KeyValues []struct {
Time string // "2019-07-10T05:35:54.277Z"
Level string // "info"
Caller string // "prog.go:42"
CallerFunc string // "main.main"
Goid string // "123"
Stack string // "<stack string>"
Message string // "a structure message"
KeyValues []struct {
Key string // "foo"
Value string // "bar"
ValueType byte // 's'
}
}
```
@@ -480,6 +482,7 @@ logger := log.Logger{
Level: log.InfoLevel,
Writer: &log.AsyncWriter{
ChannelSize: 4096,
DiscardOnFull: false,
Writer: &log.FileWriter{
Filename: "main.log",
FileMode: 0600,
@@ -1143,7 +1146,7 @@ func BenchmarkSlogSimpleZap(b *testing.B) {
zapcore.AddSync(io.Discard),
zapcore.InfoLevel,
)
logger := slog.New(zapslog.NewHandler(logcore, nil))
logger := slog.New(zapslog.NewHandler(logcore))
for i := 0; i < b.N; i++ {
logger.Info(msg, "rate", "15", "low", 16, "high", 123.2)
}
@@ -1155,7 +1158,7 @@ func BenchmarkSlogGroupsZap(b *testing.B) {
zapcore.AddSync(io.Discard),
zapcore.InfoLevel,
)
logger := slog.New(zapslog.NewHandler(logcore, nil)).With("a", 1).WithGroup("g").With("b", 2)
logger := slog.New(zapslog.NewHandler(logcore)).With("a", 1).WithGroup("g").With("b", 2)
for i := 0; i < b.N; i++ {
logger.Info(msg, "rate", "15", "low", 16, "high", 123.2)
}
28 changes: 17 additions & 11 deletions formatter.go
Original file line number Diff line number Diff line change
@@ -8,15 +8,17 @@ import (
"unsafe"
)

// FormatterArgs is a parsed sturct from json input
// FormatterArgs is a parsed struct from json input
type FormatterArgs struct {
Time string // "2019-07-10T05:35:54.277Z"
Level string // "info"
Caller string // "prog.go:42"
Goid string // "123"
Stack string // "<stack string>"
Message string // "a structure message"
KeyValues []struct {
Time string // "2019-07-10T05:35:54.277Z"
Level string // "info"
Caller string // "prog.go:42"
CallerFunc string // "main.main"
Goid string // "123"
Stack string // "<stack string>"
Message string // "a structure message"
Category string // "cat1"
KeyValues []struct {
Key string // "foo"
Value string // "bar"
ValueType byte // 's'
@@ -43,12 +45,16 @@ func formatterArgsPos(key string) (pos int) {
pos = 2
case "caller":
pos = 3
case "goid":
case "callerfunc":
pos = 4
case "stack":
case "goid":
pos = 5
case "message", "msg":
case "stack":
pos = 6
case "message", "msg":
pos = 7
case "category":
pos = 8
}
return
}
23 changes: 23 additions & 0 deletions formatter_test.go
Original file line number Diff line number Diff line change
@@ -20,6 +20,29 @@ func TestFormatterParse(t *testing.T) {
}
}

func TestFormatterArgsParse(t *testing.T) {
timestamp := "2019-07-10T05:35:54.277Z"
level := "debug"
msg := "hello json console color writer\t123"
category := "cat1"
var json = `{"time":"` + timestamp + `","level":"` + level + `","category":"` + category + `","message":"` + msg + `"}`

var args FormatterArgs
parseFormatterArgs([]byte(json), &args)
if args.Time != timestamp {
t.Fatalf("Failed to parse timestamp: %s != %s", args.Time, timestamp)
}
if args.Level != level {
t.Fatalf("Failed to parse level: %s != %s", args.Level, level)
}
if args.Category != category {
t.Fatalf("Failed to parse category: %s != %s", args.Category, category)
}
if args.Message != msg {
t.Fatalf("Failed to parse messae: %s != %s", args.Message, msg)
}
}

func TestFormatterDefault(t *testing.T) {
DefaultLogger.Writer = &ConsoleWriter{
Formatter: func(w io.Writer, a *FormatterArgs) (int, error) {
21 changes: 21 additions & 0 deletions logger.go
Original file line number Diff line number Diff line change
@@ -1695,6 +1695,27 @@ func (e *Entry) NetIPAddr(key string, ip netip.Addr) *Entry {
return e
}

// NetIPAddrs adds IPv4 or IPv6 Addresses to the entry.
func (e *Entry) NetIPAddrs(key string, ips []netip.Addr) *Entry {
if e == nil {
return nil
}

e.buf = append(e.buf, ',', '"')
e.buf = append(e.buf, key...)
e.buf = append(e.buf, '"', ':', '[')
for i, ip := range ips {
if i > 0 {
e.buf = append(e.buf, ',')
}
e.buf = append(e.buf, '"')
e.buf = ip.AppendTo(e.buf)
e.buf = append(e.buf, '"')
}
e.buf = append(e.buf, ']')
return e
}

// NetIPAddrPort adds IPv4 or IPv6 with Port Address to the entry.
func (e *Entry) NetIPAddrPort(key string, ipPort netip.AddrPort) *Entry {
if e == nil {
35 changes: 35 additions & 0 deletions logger_categorized.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package log

import (
"sync"
)

var categorizedLoggers sync.Map // key: string, value: *Logger

type CategorizedLogger struct {
Logger
Category string
}

// Categorized returns a cloned logger for category `name`.
func (l *Logger) Categorized(name string) *CategorizedLogger {
// Inherit logger with added context
v, ok := categorizedLoggers.Load(name)
if ok {
return v.(*CategorizedLogger)
}
n := &CategorizedLogger{
Logger{
Level: l.Level,
Caller: l.Caller,
TimeField: l.TimeField,
TimeFormat: l.TimeFormat,
TimeLocation: l.TimeLocation,
Context: NewContext(l.Context).Str("category", name).Value(),
Writer: l.Writer,
},
name,
}
categorizedLoggers.Store(name, n)
return n
}
83 changes: 83 additions & 0 deletions logger_categorized_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package log

import (
"bytes"
"io"
"strings"
"testing"
)

func TestLoggerCategorizedLogLevels(t *testing.T) {
var b bytes.Buffer
logger := Logger{Level: DebugLevel, Writer: &IOWriter{Writer: &b}}

// Categorized logger must be indepented of base logger
cat1Logger := logger.Categorized("cat1")
cat1Logger.SetLevel(TraceLevel)
logger.Debug().Msg("logger debug here")
cat1Logger.Debug().Msg("cat1Logger debug here")
if !strings.Contains(b.String(), `"logger debug here"`) {
t.Fatal("logger.Debug must be logged")
}
if !strings.Contains(b.String(), `"cat1Logger debug here"`) {
t.Fatal("cat1Logger.Debug must be logged for ")
}
if !strings.Contains(b.String(), `"category":"cat1"`) {
t.Fatal("cat1Logger.Debug is missing category")
}

// Changing loglevel on category must not change base logger
b.Reset()
cat1Logger.SetLevel(InfoLevel)
logger.Debug().Msg("logger debug here")
cat1Logger.Debug().Msg("cat1Logger debug here")
if !strings.Contains(b.String(), `"logger debug here"`) {
t.Fatal("logger.Debug must be logged")
}
if strings.Contains(b.String(), `"cat1Logger debug here"`) {
t.Fatal("cat1Logger.Debug must be logged")
}

// Loglevel on other categorized logger has own loglevel
b.Reset()
cat2Logger := logger.Categorized("cat2")
cat2Logger.SetLevel(TraceLevel)
cat2Logger.Trace().Msg("cat2Logger trace here")
if !strings.Contains(b.String(), `"cat2Logger trace here"`) {
t.Fatal("logger.Debug for category cat2 must be logged")
}
if !strings.Contains(b.String(), `"category":"cat2"`) {
t.Fatal("cat2Logger.Debug is missing category")
}
}

func BenchmarkCategorizedLogger(b *testing.B) {
logger := Logger{
TimeFormat: TimeFormatUnix,
Level: DebugLevel,
Writer: IOWriter{io.Discard},
}

catLogger := logger.Categorized("one")
catLogger.SetLevel(DebugLevel)

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
catLogger.Debug().Str("foo", "bar").Msgf("hello %s", "world")
}
}

func BenchmarkCategorizedLoggerBadUsage(b *testing.B) {
logger := Logger{
TimeFormat: TimeFormatUnix,
Level: DebugLevel,
Writer: IOWriter{io.Discard},
}

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Categorized("one").Info().Str("foo", "bar").Msgf("hello %s", "world")
}
}
2 changes: 2 additions & 0 deletions logger_test.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net"
"net/netip"
"os"
"strings"
"testing"
@@ -107,6 +108,7 @@ func TestLoggerInfo(t *testing.T) {
IPAddr("ip6", net.ParseIP("2001:4860:4860::8888")).
IPAddr("ip4", ipv4Addr).
IPPrefix("ip_prefix", *ipv4Net).
NetIPAddrs("netip_addr", []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2001:4860:4860::8888")}).
MACAddr("mac", net.HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}).
Xid("xid", NewXID()).
Errs("errors", []error{errors.New("error1"), nil, errors.New("error3")}).