Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6dc6e18
Adds a new "Dim" theme
ansel1 Jan 8, 2025
3a2a416
Use fmt for errors, like slog.TextHandler
ansel1 Jan 7, 2025
fdbbfe5
Multiline support
ansel1 Jan 7, 2025
5aa8071
ReplaceAttr support
ansel1 Jan 10, 2025
8a64069
Optimized out the allocation
ansel1 Jan 10, 2025
3bc92bc
Merge branch 'dimtheme' into alltogether
ansel1 Jan 10, 2025
7cda51f
optimize for errors which don't implement fmt.Formatter
ansel1 Jan 10, 2025
d00750f
Merge branch 'formaterrors' into alltogether
ansel1 Jan 10, 2025
6a1e91f
style should always be passed to writeColoredValue()
ansel1 Jan 10, 2025
cd141b9
WIP
ansel1 Jan 10, 2025
7b59dbe
Avoid an allocation by adding a re-usable headers buffer to encoder
ansel1 Jan 10, 2025
e4ff20e
Merge branch 'multiline' into alltogether
ansel1 Jan 10, 2025
9033a5e
Reverted attr key color in dim theme to cyan
ansel1 Jan 11, 2025
31eb4aa
changing module name
ansel1 Jan 17, 2025
d97ead4
Better trimming of the source file path
ansel1 Jan 17, 2025
d33ca0c
WIP: fixed header section width
ansel1 Jan 22, 2025
b9af03c
Add option for customizing how the source attribute is truncated
ansel1 Feb 5, 2025
f1d09cd
Always write out the header seperator, even when there aren't headers
ansel1 Feb 5, 2025
0be17f8
Write the entire log line out with a single Write() call
ansel1 Feb 5, 2025
da4ed6e
Introduced new formatting option
ansel1 Feb 11, 2025
037df06
Removed Headers and HeaderWidth options
ansel1 Feb 11, 2025
5144273
Eliminate the last memory allocation
ansel1 Feb 12, 2025
c46037f
Establish a pattern for encoder methods
ansel1 Feb 12, 2025
c462227
Header capturing works with attrs in groups
ansel1 Feb 12, 2025
774477c
More tests
ansel1 Feb 13, 2025
519fe7b
WIP: elastic spaces and field groups
ansel1 Feb 14, 2025
9650388
Close but still not quite right
ansel1 Feb 15, 2025
2642549
got the whitespace handling the way I want it
ansel1 Feb 16, 2025
085e001
optimization of whitespace handling
ansel1 Feb 16, 2025
cc02fde
Change source theme color to more generic "header" color
ansel1 Feb 17, 2025
a388888
Eliminated the Dim theme...
ansel1 Feb 17, 2025
72dc4d7
Simplify buffer
ansel1 Feb 17, 2025
38e9b59
More test coverage
ansel1 Feb 17, 2025
5730c10
Trim space around the message and the entire log line
ansel1 Feb 28, 2025
3fa3024
Tweaked the default theme again
ansel1 Feb 28, 2025
07d1f6a
Added verbs for attrs and source
ansel1 Mar 1, 2025
4a1087a
refactoring
ansel1 Mar 1, 2025
9448e7d
Added a style modifier to group open
ansel1 Mar 1, 2025
755041b
If a group only contains strings, and no fields, then never elide it
ansel1 Mar 1, 2025
b51a493
Improve some of the error messages
ansel1 Mar 1, 2025
63df22b
Removed the non-capturing modifier
ansel1 Mar 1, 2025
59d9d29
Refactoring
ansel1 Mar 1, 2025
933716b
Made theme easier to use
ansel1 Mar 2, 2025
22580cc
Experimental support for an alternate way to print multiline attributes
ansel1 Mar 6, 2025
cc0cdc4
Added CI build (using github actions)
ansel1 Mar 6, 2025
45ad607
Synchronize writes
ansel1 Mar 13, 2025
bcedc3e
Update the README
ansel1 Mar 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go

name: Build

on:
push:
branches: [ "main" ]
branches: [ master ]
pull_request:
branches: [ "main" ]
branches: [ master ]
workflow_dispatch:

jobs:

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- uses: golangci/golangci-lint-action@v6
build:
runs-on: ubuntu-latest
strategy:
matrix:
go: [ '^1.21', 'oldstable', 'stable' ]
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: '1.21'

go-version: ${{ matrix.go }}
- uses: actions/checkout@v4
- name: Build
run: go build -v ./...

run: |
go build "./..."
- name: Test
run: go test -race -coverprofile=coverage.txt -covermode=atomic -v ./...

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
run: go test -v -json ./...
33 changes: 8 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# console-slog

[![Go Reference](https://pkg.go.dev/badge/github.com/phsym/console-slog.svg)](https://pkg.go.dev/github.com/phsym/console-slog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/phsym/console-slog/master/LICENSE) [![Build](https://github.com/phsym/console-slog/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/phsym/slog-console/actions/workflows/go.yml) [![codecov](https://codecov.io/gh/phsym/console-slog/graph/badge.svg?token=ZIJT9L79QP)](https://codecov.io/gh/phsym/console-slog) [![Go Report Card](https://goreportcard.com/badge/github.com/phsym/console-slog)](https://goreportcard.com/report/github.com/phsym/console-slog)
[![Go Reference](https://pkg.go.dev/badge/github.com/ansel1/console-slog.svg)](https://pkg.go.dev/github.com/ansel1/console-slog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/ansel1/console-slog/master/LICENSE) [![Build](https://github.com/ansel1/console-slog/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/ansel1/slog-console/actions/workflows/go.yml) [![codecov](https://codecov.io/gh/ansel1/console-slog/graph/badge.svg?token=ZIJT9L79QP)](https://codecov.io/gh/ansel1/console-slog) [![Go Report Card](https://goreportcard.com/badge/github.com/ansel1/console-slog)](https://goreportcard.com/report/github.com/ansel1/console-slog)

A handler for slog that prints colorized logs, similar to zerolog's console writer output without sacrificing performances.

## Installation
```bash
go get github.com/phsym/console-slog@latest
go get github.com/ansel1/console-slog@latest
```

## Example
Expand All @@ -18,7 +18,7 @@ import (
"log/slog"
"os"

"github.com/phsym/console-slog"
"github.com/ansel1/console-slog"
)

func main() {
Expand Down Expand Up @@ -50,26 +50,9 @@ console.NewHandler(os.Stderr, &console.HandlerOptions{Level: slog.LevelDebug, Ad
## Performances
See [benchmark file](./bench_test.go) for details.

The handler itself performs quite well compared to std-lib's handlers. It does no allocation:
```
goos: linux
goarch: amd64
pkg: github.com/phsym/console-slog
cpu: Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz
BenchmarkHandlers/dummy-4 128931026 8.732 ns/op 0 B/op 0 allocs/op
BenchmarkHandlers/console-4 849837 1294 ns/op 0 B/op 0 allocs/op
BenchmarkHandlers/std-text-4 542583 2097 ns/op 4 B/op 2 allocs/op
BenchmarkHandlers/std-json-4 583784 1911 ns/op 120 B/op 3 allocs/op
```
The handler itself performs quite well compared to std-lib's handlers. It does no allocation. It is generally faster
then slog.TextHandler, and a little slower than slog.JSONHandler.

However, the go 1.21.0 `slog.Logger` adds some overhead:
```
goos: linux
goarch: amd64
pkg: github.com/phsym/console-slog
cpu: Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz
BenchmarkLoggers/dummy-4 1239873 893.2 ns/op 128 B/op 1 allocs/op
BenchmarkLoggers/console-4 483354 2338 ns/op 128 B/op 1 allocs/op
BenchmarkLoggers/std-text-4 368828 3141 ns/op 132 B/op 3 allocs/op
BenchmarkLoggers/std-json-4 393322 2909 ns/op 248 B/op 4 allocs/op
```
## Credit

This is a forked and heavily modified variant of github.com/phsym/console-slog.
5 changes: 5 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ var handlers = []struct {
}{
{"dummy", &DummyHandler{}},
{"console", NewHandler(io.Discard, &HandlerOptions{Level: slog.LevelDebug, AddSource: false})},
{"console-headers", NewHandler(io.Discard, &HandlerOptions{HeaderFormat: "%t %{%[foo]h > %}%l %m %a", Level: slog.LevelDebug, AddSource: false})},
{"console-replaceattr", NewHandler(io.Discard, &HandlerOptions{Level: slog.LevelDebug, AddSource: false, ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { return a }})},
{"console-headers-replaceattr", NewHandler(io.Discard, &HandlerOptions{HeaderFormat: "%t %{%[foo]h > %} %l %m %a", Level: slog.LevelDebug, AddSource: false, ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { return a }})},
{"std-text", slog.NewTextHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: false})},
{"std-text-replaceattr", slog.NewTextHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: false, ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { return a }})},
{"std-json", slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: false})},
}

Expand All @@ -36,6 +40,7 @@ var attrs = []slog.Attr{
slog.Any("err", errors.New("yo")),
slog.Group("empty"),
slog.Group("group", slog.String("bar", "baz")),
slog.String("multi", "foo\nbar"),
}

var attrsAny = func() (a []any) {
Expand Down
46 changes: 14 additions & 32 deletions buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,20 @@ package console

import (
"io"
"slices"
"strconv"
"time"
)

type buffer []byte

func (b *buffer) Grow(n int) {
*b = slices.Grow(*b, n)
}

func (b *buffer) Bytes() []byte {
return *b
}

func (b *buffer) String() string {
return string(*b)
}

func (b *buffer) Len() int {
return len(*b)
}

func (b *buffer) Cap() int {
return cap(*b)
func (b *buffer) Pad(n int, c byte) {
for ; n > 0; n-- {
b.AppendByte(byte(c))
}
}

func (b *buffer) WriteTo(dst io.Writer) (int64, error) {
Expand All @@ -45,22 +34,19 @@ func (b *buffer) WriteTo(dst io.Writer) (int64, error) {
return int64(n), nil
}

func (b *buffer) Reset() {
*b = (*b)[:0]
}

func (b *buffer) Clone() buffer {
return append(buffer(nil), *b...)
}

func (b *buffer) Clip() {
*b = slices.Clip(*b)
func (b *buffer) Write(bt []byte) (int, error) {
*b = append(*b, bt...)
return len(bt), nil
}

func (b *buffer) copy(src *buffer) {
if src.Len() > 0 {
b.Append(src.Bytes())
func (b *buffer) Reset() {
// To reduce peak allocation, return only smaller buffers to the pool.
const maxBufferSize = 16 << 10
if cap(*b) > maxBufferSize {
*b = (*b)[:0:maxBufferSize]
return
}
*b = (*b)[:0]
}

func (b *buffer) Append(data []byte) {
Expand All @@ -71,10 +57,6 @@ func (b *buffer) AppendString(s string) {
*b = append(*b, s...)
}

// func (b *buffer) AppendQuotedString(s string) {
// b.buff = strconv.AppendQuote(b.buff, s)
// }

func (b *buffer) AppendByte(byt byte) {
*b = append(*b, byt)
}
Expand Down
64 changes: 13 additions & 51 deletions buffer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import (
)

func TestBuffer_Append(t *testing.T) {
b := new(buffer)
AssertZero(t, b.Len())
var b buffer
AssertZero(t, len(b))
b.AppendString("foobar")
AssertEqual(t, 6, b.Len())
AssertEqual(t, 6, len(b))
b.AppendString("baz")
AssertEqual(t, 9, b.Len())
AssertEqual(t, 9, len(b))
AssertEqual(t, "foobarbaz", b.String())

b.AppendByte('.')
AssertEqual(t, 10, b.Len())
AssertEqual(t, 10, len(b))
AssertEqual(t, "foobarbaz.", b.String())

b.AppendBool(true)
Expand All @@ -36,7 +36,7 @@ func TestBuffer_Append(t *testing.T) {

func TestBuffer_WriteTo(t *testing.T) {
dest := bytes.Buffer{}
b := new(buffer)
var b buffer
n, err := b.WriteTo(&dest)
AssertNoError(t, err)
AssertZero(t, n)
Expand All @@ -45,61 +45,23 @@ func TestBuffer_WriteTo(t *testing.T) {
AssertEqual(t, len("foobar"), int(n))
AssertNoError(t, err)
AssertEqual(t, "foobar", dest.String())
AssertZero(t, b.Len())
}

func TestBuffer_Clone(t *testing.T) {
b := new(buffer)
b.AppendString("foobar")
b2 := b.Clone()
AssertEqual(t, b.String(), b2.String())
AssertNotEqual(t, &b.Bytes()[0], &b2.Bytes()[0])
}

func TestBuffer_Copy(t *testing.T) {
b := new(buffer)
b.AppendString("foobar")
b2 := new(buffer)
b2.copy(b)
AssertEqual(t, b.String(), b2.String())
AssertNotEqual(t, &b.Bytes()[0], &b2.Bytes()[0])
AssertZero(t, len(b))
}

func TestBuffer_Reset(t *testing.T) {
b := new(buffer)
var b buffer
b.AppendString("foobar")
AssertEqual(t, "foobar", b.String())
AssertEqual(t, len("foobar"), b.Len())
bufCap := b.Cap()
AssertEqual(t, len("foobar"), len(b))
bufCap := cap(b)
b.Reset()
AssertZero(t, b.Len())
AssertEqual(t, bufCap, b.Cap())
}

func TestBuffer_Grow(t *testing.T) {
b := new(buffer)
AssertZero(t, b.Cap())
b.Grow(12)
AssertGreaterOrEqual(t, 12, b.Cap())
b.Grow(6)
AssertGreaterOrEqual(t, 12, b.Cap())
b.Grow(24)
AssertGreaterOrEqual(t, 24, b.Cap())
}

func TestBuffer_Clip(t *testing.T) {
b := new(buffer)
b.AppendString("foobar")
b.Grow(12)
AssertGreaterOrEqual(t, 12, b.Cap())
b.Clip()
AssertEqual(t, "foobar", b.String())
AssertEqual(t, len("foobar"), b.Cap())
AssertZero(t, len(b))
AssertEqual(t, bufCap, cap(b))
}

func TestBuffer_WriteTo_Err(t *testing.T) {
w := writerFunc(func(b []byte) (int, error) { return 0, errors.New("nope") })
b := new(buffer)
var b buffer
b.AppendString("foobar")
_, err := b.WriteTo(w)
AssertError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions duration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package console

import (
"bytes"
"slices"
"testing"
"time"
)
Expand Down Expand Up @@ -43,8 +44,7 @@ func BenchmarkDuration(b *testing.B) {
})

b.Run("append", func(b *testing.B) {
w := new(buffer)
w.Grow(2048)
w := slices.Grow(buffer{}, 2048)
b.ResetTimer()
for i := 0; i < b.N; i++ {
w.AppendDuration(d)
Expand Down
Loading