-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
30 changed files
with
28,840 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
name: Continuous Integration | ||
|
||
on: | ||
push: | ||
tags: | ||
- v* | ||
branches: | ||
- master | ||
- main | ||
pull_request: | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
linters: | ||
name: Run linters | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-go@v5 | ||
with: | ||
go-version: '1.21' | ||
cache-dependency-path: | | ||
go.sum | ||
test/go.sum | ||
- name: Lint . | ||
uses: golangci/golangci-lint-action@v3 | ||
with: | ||
version: v1.56 | ||
# Disable caching as a workaround for https://github.com/golangci/golangci-lint-action/issues/135. | ||
# The line can be removed once the golangci-lint issue is resolved. | ||
skip-pkg-cache: true | ||
- name: Lint other modules | ||
run: go list -m -f '{{.Dir}}/...' | golangci-lint run | ||
|
||
unit-tests: | ||
name: Run unit tests | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
go: ['1.21'] | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-go@v5 | ||
with: | ||
go-version: ${{ matrix.go }} | ||
- name: Test | ||
run: go list -m -f '{{.Dir}}/...' | xargs go test -race -coverprofile=cover.out -coverpkg=./... | ||
|
||
- name: Collect coverage | ||
run: go tool cover -html=cover.out -o cover.html | ||
|
||
- name: Upload coverage to codecov.io | ||
uses: codecov/codecov-action@v4 | ||
env: | ||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,3 +19,6 @@ | |
|
||
# Go workspace file | ||
go.work | ||
|
||
# VS Code files | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
linters-settings: | ||
dupl: | ||
threshold: 150 | ||
exhaustive: | ||
default-signifies-exhaustive: false | ||
funlen: | ||
lines: 100 | ||
statements: 50 | ||
goconst: | ||
min-len: 2 | ||
min-occurrences: 2 | ||
gocyclo: | ||
min-complexity: 32 | ||
goimports: | ||
local-prefixes: github.com/pamburus | ||
govet: | ||
check-shadowing: false | ||
lll: | ||
line-length: 140 | ||
maligned: | ||
suggest-new: true | ||
nolintlint: | ||
allow-unused: false # report any unused nolint directives | ||
require-explanation: true # require an explanation for nolint directives | ||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped | ||
|
||
linters: | ||
# Please, do not use `enable-all`: it's deprecated and will be removed soon. | ||
# Inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint. | ||
disable-all: true | ||
enable: | ||
- asciicheck | ||
- dupl | ||
- errcheck | ||
- exportloopref | ||
- gocritic | ||
- gocyclo | ||
- godot | ||
- gofmt | ||
- goimports | ||
- gosec | ||
- gosimple | ||
- govet | ||
- ineffassign | ||
- misspell | ||
- nakedret | ||
- nlreturn | ||
- prealloc | ||
- revive | ||
- staticcheck | ||
- stylecheck | ||
- typecheck | ||
- unconvert | ||
- unparam | ||
- unused | ||
- vet | ||
- vetshadow | ||
- whitespace | ||
|
||
issues: | ||
# Disable this option because it causes golint to hide almost all issues. | ||
exclude-use-default: false | ||
# Excluding configuration per-path, per-linter, per-text and per-source. | ||
exclude-rules: | ||
# The dot-imports linter is disabled for the test files because it is convenient to dot-import third-party testing frameworks. | ||
- linters: [revive] | ||
text: '^dot-imports: ' | ||
paths: | ||
- '*_test.go' | ||
# The staticcheck linter reports that `nil` context is passed, but it is intentionally done for the sake of the tests. | ||
- linters: [staticcheck] | ||
text: '^SA1012:' | ||
paths: | ||
- '*_test.go' | ||
# The duplicate lines in the benchmark_test.go file are expected because any attempt to deduplicate them would affect the performance of the benchmark. | ||
- linters: [dupl] | ||
text: 'lines are duplicate' | ||
paths: | ||
- 'benchmark_test.go' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,26 @@ | ||
# slogx | ||
Extensions and helpers for log/slog package. | ||
# slogx [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] | ||
|
||
Module [slogx](https://pkg.go.dev/github.com/pamburus/slogx) provides extensions and helpers for the [log/slog](https://pkg.go.dev/log/slog) package. | ||
|
||
## Packages | ||
* [slogx](./README.md) | ||
* [slogc](slogc/README.md) | ||
|
||
|
||
### Package slogx | ||
|
||
Package [slogx](https://pkg.go.dev/github.com/pamburus/slogx) provides [Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger) as an alternative to [slog.Logger](https://pkg.go.dev/log/slog#Logger), which focuses on performance and makes several design changes to improve it: | ||
* It does not provide convenience methods for attributes that may affect performance. All methods accept attributes only as [slog.Attr](https://pkg.go.dev/log/slog#Attr). | ||
* It provides an [option](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithSource) to disable the inclusion of [source](https://pkg.go.dev/log/slog#Source) information in the log [Record](https://pkg.go.dev/log/slog#Record). This can improve performance by up to 100% in cases where the source information is not included by the [Handler](https://pkg.go.dev/log/slog#Handler) anyway. | ||
* Its [With](https://pkg.go.dev/github.com/pamburus/slogx#Logger.With) method does not immediately call the handler's [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) method, instead it buffers up to 4 attributes which are then added to each log [Record](https://pkg.go.dev/log/slog#Record). This improves performance when you need to define a temporary set of attributes in a function and log a few messages with those attributes a few times. It also greatly improves performance when the logger is disabled. This is because calling [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) is quite an expensive operation, especially if the [Handler](https://pkg.go.dev/log/slog#Handler) is wrapped multiple times. That is, each layer will call the underlying handler's [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) method and cause a lot of allocations. But what if the message is discarded because the logger is disabled? Yes, it will be a waste of CPU time. So for temporary [With](https://pkg.go.dev/github.com/pamburus/slogx#Logger.With) attribute sets, it is usually more efficient to keep them on the [Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger). If any of the [WithGroup](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithGroup), [Handler](https://pkg.go.dev/github.com/pamburus/slogx#Logger.Handler), or [LongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.LongTerm) methods are called later, the temporary attributes will be flushed using the [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) method. | ||
* It provides the [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) method, which acts as a sequence of [With](https://pkg.go.dev/github.com/pamburus/slogx#Logger.With) and [LongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.LongTerm) method calls and is needed for cases where the resulting logger is intended to be reused multiple times and may reside in a rather long-lived context. | ||
|
||
## Performance | ||
* See [benchmark results](doc/benchmark/README.md) for details. | ||
|
||
[doc-img]: https://pkg.go.dev/badge/github.com/pamburus/slogx | ||
[doc]: https://pkg.go.dev/github.com/pamburus/slogx | ||
[ci-img]: https://github.com/pamburus/slogx/actions/workflows/ci.yml/badge.svg | ||
[ci]: https://github.com/pamburus/slogx/actions/workflows/ci.yml | ||
[cov-img]: https://codecov.io/gh/pamburus/slogx/graph/badge.svg?token=0TF6JD4KDU | ||
[cov]: https://codecov.io/gh/pamburus/slogx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package slogx | ||
|
||
import ( | ||
"log/slog" | ||
"slices" | ||
) | ||
|
||
// ErrorAttr returns an attribute with the error. | ||
func ErrorAttr(err error) slog.Attr { | ||
return slog.Any(ErrorKey, err) | ||
} | ||
|
||
const ( | ||
// ErrorKey is the key used for the error attribute. | ||
ErrorKey = "error" | ||
) | ||
|
||
// AttrPack is a an optimized pack of attributes. | ||
type AttrPack struct { | ||
front [4]slog.Attr | ||
nFront int | ||
back []slog.Attr | ||
} | ||
|
||
// Clone returns a copy of the AttrPack without a shared state. | ||
func (r AttrPack) Clone() AttrPack { | ||
r.back = slices.Clip(r.back) | ||
|
||
return r | ||
} | ||
|
||
// Len returns the number of attributes in the AttrPack. | ||
func (r AttrPack) Len() int { | ||
return r.nFront + len(r.back) | ||
} | ||
|
||
// Enumerate calls f on each Attr in the AttrPack. | ||
func (r AttrPack) Enumerate(f func(slog.Attr) bool) { | ||
for i := 0; i < r.nFront; i++ { | ||
if !f(r.front[i]) { | ||
return | ||
} | ||
} | ||
for _, a := range r.back { | ||
if !f(a) { | ||
return | ||
} | ||
} | ||
} | ||
|
||
// Add appends the given Attrs to the AttrPack's list of Attrs. | ||
func (r *AttrPack) Add(attrs ...slog.Attr) { | ||
var i int | ||
for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ { | ||
a := attrs[i] | ||
if isEmptyGroup(a.Value) { | ||
continue | ||
} | ||
r.front[r.nFront] = a | ||
r.nFront++ | ||
} | ||
if cap(r.back) > len(r.back) { | ||
end := r.back[:len(r.back)+1][len(r.back)] | ||
if !end.Equal(slog.Attr{}) { | ||
panic("multiple copies of a slogx.AttrPack modified the shared state simultaneously, use Clone to avoid this") | ||
} | ||
} | ||
ne := countEmptyGroups(attrs[i:]) | ||
r.back = slices.Grow(r.back, len(attrs[i:])-ne) | ||
for _, a := range attrs[i:] { | ||
if !isEmptyGroup(a.Value) { | ||
r.back = append(r.back, a) | ||
} | ||
} | ||
} | ||
|
||
// Collect returns all the attributes in the AttrPack as a slice. | ||
func (r *AttrPack) Collect() []slog.Attr { | ||
attrs := make([]slog.Attr, 0, r.Len()) | ||
r.Enumerate(func(a slog.Attr) bool { | ||
attrs = append(attrs, a) | ||
|
||
return true | ||
}) | ||
|
||
return attrs | ||
} | ||
|
||
// --- | ||
|
||
func countEmptyGroups(as []slog.Attr) int { | ||
n := 0 | ||
for _, a := range as { | ||
if isEmptyGroup(a.Value) { | ||
n++ | ||
} | ||
} | ||
|
||
return n | ||
} | ||
|
||
func isEmptyGroup(v slog.Value) bool { | ||
if v.Kind() != slog.KindGroup { | ||
return false | ||
} | ||
|
||
return len(v.Group()) == 0 | ||
} |
Oops, something went wrong.