Showing preview only (395K chars total). Download the full file or copy to clipboard to get everything.
Repository: gookit/slog
Branch: master
Commit: f12a8b6e1523
Files: 90
Total size: 366.7 KB
Directory structure:
gitextract_ub2bczst/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ ├── changelog.yml
│ ├── dependabot.yml
│ ├── revive.toml
│ └── workflows/
│ ├── go.yml
│ └── release.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── README.zh-CN.md
├── _example/
│ ├── bench_loglibs.md
│ ├── bench_loglibs_test.go
│ ├── demos/
│ │ ├── demo1.go
│ │ ├── simple.go
│ │ └── slog_all_level.go
│ ├── diff-with-zap-zerolog.md
│ ├── go.mod
│ ├── handler/
│ │ └── grouped.go
│ ├── issue100/
│ │ └── issue100_test.go
│ ├── issue111/
│ │ └── main.go
│ ├── issue137/
│ │ └── main.go
│ ├── pprof/
│ │ └── main.go
│ └── refer/
│ └── main.go
├── benchmark2_test.go
├── benchmark_test.go
├── bufwrite/
│ ├── bufio_writer.go
│ ├── bufwrite_test.go
│ └── line_writer.go
├── common.go
├── common_test.go
├── example_test.go
├── formatter.go
├── formatter_json.go
├── formatter_test.go
├── formatter_text.go
├── go.mod
├── go.sum
├── handler/
│ ├── README.md
│ ├── buffer.go
│ ├── buffer_test.go
│ ├── builder.go
│ ├── config.go
│ ├── config_test.go
│ ├── console.go
│ ├── console_test.go
│ ├── email.go
│ ├── example_test.go
│ ├── file.go
│ ├── file_test.go
│ ├── handler.go
│ ├── handler_test.go
│ ├── rotatefile.go
│ ├── rotatefile_test.go
│ ├── syslog.go
│ ├── syslog_test.go
│ ├── write_close_flusher.go
│ ├── write_close_syncer.go
│ ├── write_closer.go
│ ├── writer.go
│ └── writer_test.go
├── handler.go
├── handler_test.go
├── internal/
│ └── util.go
├── issues_test.go
├── logger.go
├── logger_sub.go
├── logger_test.go
├── logger_write.go
├── processor.go
├── processor_test.go
├── record.go
├── record_test.go
├── rotatefile/
│ ├── README.md
│ ├── cleanup.go
│ ├── cleanup_test.go
│ ├── config.go
│ ├── config_test.go
│ ├── issues_test.go
│ ├── rotatefile.go
│ ├── rotatefile_test.go
│ ├── util.go
│ ├── util_test.go
│ ├── writer.go
│ └── writer_test.go
├── slog.go
├── slog_test.go
├── sugared.go
├── util.go
└── util_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: inhere
---
**System (please complete the following information):**
- OS: `linux` [e.g. linux, macOS]
- GO Version: `1.13` [e.g. `1.13`]
- Pkg Version: `1.1.1` [e.g. `1.1.1`]
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
```go
// go code
```
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/changelog.yml
================================================
title: '## Change Log'
# style allow: simple, markdown(mkdown), ghr(gh-release)
style: gh-release
# group names
names: [Refactor, Fixed, Feature, Update, Other]
repo_url: https://github.com/gookit/slog
filters:
# message length should >= 12
- name: msg_len
min_len: 12
# message words should >= 3
- name: words_len
min_len: 3
- name: keyword
keyword: format code
exclude: true
- name: keywords
keywords: format code, action test
exclude: true
# group match rules
# not matched will use 'Other' group.
rules:
- name: Refactor
start_withs: [refactor, break]
contains: ['refactor:']
- name: Fixed
start_withs: [fix]
contains: ['fix:']
- name: Feature
start_withs: [feat, new]
contains: [feature]
- name: Update
start_withs: [update, 'up:']
contains: []
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every weekday
interval: "daily"
================================================
FILE: .github/revive.toml
================================================
ignoreGeneratedHeader = false
# Sets the default severity to "warning"
#severity = "error"
severity = "warning"
confidence = 0.8
errorCode = 0
warningCode = 0
[rule.blank-imports]
[rule.context-as-argument]
[rule.context-keys-type]
[rule.dot-imports]
[rule.error-return]
[rule.error-strings]
[rule.error-naming]
[rule.exported]
severity = "warning"
[rule.if-return]
[rule.increment-decrement]
[rule.var-naming]
[rule.var-declaration]
[rule.package-comments]
[rule.range]
[rule.receiver-naming]
[rule.time-naming]
[rule.unexported-return]
[rule.indent-error-flow]
[rule.errorf]
[rule.argument-limit]
arguments = [4]
[rule.function-result-limit]
arguments = [3]
[rule.empty-block]
[rule.confusing-naming]
[rule.superfluous-else]
[rule.unused-parameter]
[rule.unreachable-code]
[rule.unnecessary-stmt]
[rule.struct-tag]
[rule.atomic]
[rule.empty-lines]
[rule.duplicated-imports]
[rule.import-shadowing]
[rule.confusing-results]
[rule.modifies-parameter]
[rule.redefines-builtin-id]
================================================
FILE: .github/workflows/go.yml
================================================
name: Unit-Tests
on:
pull_request:
paths:
- 'go.mod'
- '**.go'
- '**.yml'
push:
paths:
- 'go.mod'
- '**.go'
- '**.yml'
jobs:
test:
name: Test on go ${{ matrix.go_version }} and ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
go_version: [1.24, 1.23, 1.22, 1.21, 1.19, stable]
os: [ubuntu-latest, windows-latest] # , macOS-latest
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Go SDK
uses: actions/setup-go@v6
timeout-minutes: 3
with:
go-version: ${{ matrix.go_version }}
- name: Tidy go mod
run: go mod tidy
# https://github.com/actions/setup-go
# - name: Use Go ${{ matrix.go_version }}
# timeout-minutes: 3
# uses: actions/setup-go@v3
# with:
# go-version: ${{ matrix.go_version }}
# - name: Revive check
# uses: docker://morphy/revive-action:v2
# if: ${{ matrix.os == 'ubuntu-latest' && matrix.go_version == 'stable' }}
# with:
# config: .github/revive.toml
# # Exclude patterns, separated by semicolons (optional)
# exclude: "./internal/..."
- name: Run staticcheck
uses: reviewdog/action-staticcheck@v1
if: ${{ github.event_name == 'pull_request'}}
with:
github_token: ${{ secrets.github_token }}
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
reporter: github-pr-check
# Report all results. [added,diff_context,file,nofilter].
filter_mode: added
# Exit with 1 when it find at least one finding.
fail_on_error: true
- name: Run unit tests
# run: go test -v -cover ./...
run: go test -coverprofile="profile.cov" ./...
- name: Send coverage
uses: shogo82148/actions-goveralls@v1
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
path-to-profile: profile.cov
flag-name: Go-${{ matrix.go_version }}
parallel: true
shallow: true
# notifies that all test jobs are finished.
# https://github.com/shogo82148/actions-goveralls
finish:
needs: test
runs-on: ubuntu-latest
steps:
- uses: shogo82148/actions-goveralls@v1
with:
shallow: true
parallel-finished: true
================================================
FILE: .github/workflows/release.yml
================================================
name: Tag-release
on:
push:
tags:
- v*
jobs:
release:
name: Release new version
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup ENV
# https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
run: |
echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV
echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV
- name: Generate changelog
run: |
curl https://github.com/gookit/gitw/releases/latest/download/chlog-linux-amd64 -L -o /usr/local/bin/chlog
chmod a+x /usr/local/bin/chlog
chlog -c .github/changelog.yml -o changelog.md prev last
# https://github.com/softprops/action-gh-release
- name: Create release and upload assets
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ env.RELEASE_TAG }}
tag_name: ${{ env.RELEASE_TAG }}
body_path: changelog.md
token: ${{ secrets.GITHUB_TOKEN }}
# files: macos-chlog.exe
================================================
FILE: .gitignore
================================================
*.log
*.swp
.idea
*.patch
*.tmp
# Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
*.log.*
*~
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.DS_Store
*.prof
# shell script
/*.bash
/*.sh
/*.zsh
/*.pid
go.work
changelog.md
testdata
_example/go.sum
.xenv.*
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 inhere
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Makefile
================================================
# Make does not offer a recursive wildcard function, so here's one:
# from https://github.com/jenkins-x-plugins/jx-gitops/blob/main/Makefile
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
SHELL := /bin/bash
NAME := slog
BUILD_TARGET = testdata
MAIN_SRC_FILE=cmd/main.go
GO :=go
ORG := gookit
REV := $(shell git rev-parse --short HEAD 2> /dev/null || echo 'unknown')
ORG_REPO := $(ORG)/$(NAME)
RELEASE_ORG_REPO := $(ORG_REPO)
ROOT_PACKAGE := github.com/$(ORG_REPO)
GO_VERSION := $(shell $(GO) version | sed -e 's/^[^0-9.]*\([0-9.]*\).*/\1/')
GO_DEPENDENCIES := $(call rwildcard,pkg/,*.go) $(call rwildcard,cmd/,*.go)
BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2> /dev/null || echo 'unknown')
BUILD_DATE := $(shell date +%Y%m%d-%H:%M:%S)
CGO_ENABLED = 0
REPORTS_DIR=$(BUILD_TARGET)/reports
GOTEST := $(GO) test
# set dev version unless VERSION is explicitly set via environment
VERSION ?= $(shell echo "$$(git for-each-ref refs/tags/ --count=1 --sort=-version:refname --format='%(refname:short)' 2>/dev/null)-dev+$(REV)" | sed 's/^v//')
# Build flags for setting build-specific configuration at build time - defaults to empty
#BUILD_TIME_CONFIG_FLAGS ?= ""
# Full build flags used when building binaries. Not used for test compilation/execution.
BUILDFLAGS := -ldflags \
" -X $(ROOT_PACKAGE)/pkg/cmd/version.Version=$(VERSION)\
-X github.com/jenkins-x-plugins/jx-gitops/pkg/cmd/version.Version=$(VERSION)\
-X $(ROOT_PACKAGE)/pkg/cmd/version.Revision='$(REV)'\
-X $(ROOT_PACKAGE)/pkg/cmd/version.Branch='$(BRANCH)'\
-X $(ROOT_PACKAGE)/pkg/cmd/version.BuildDate='$(BUILD_DATE)'\
-X $(ROOT_PACKAGE)/pkg/cmd/version.GoVersion='$(GO_VERSION)'\
$(BUILD_TIME_CONFIG_FLAGS)"
# Some tests expect default values for version.*, so just use the config package values there.
TEST_BUILDFLAGS := -ldflags "$(BUILD_TIME_CONFIG_FLAGS)"
ifdef DEBUG
BUILDFLAGS := -gcflags "all=-N -l" $(BUILDFLAGS)
endif
ifdef PARALLEL_BUILDS
BUILDFLAGS += -p $(PARALLEL_BUILDS)
GOTEST += -p $(PARALLEL_BUILDS)
else
# -p 4 seems to work well for people
GOTEST += -p 4
endif
ifdef DISABLE_TEST_CACHING
GOTEST += -count=1
endif
TEST_PACKAGE ?= ./...
COVER_OUT:=$(REPORTS_DIR)/cover.out
COVERFLAGS=-coverprofile=$(COVER_OUT) --covermode=count --coverpkg=./...
.PHONY: list
list: ## List all make targets
@$(MAKE) -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort
.PHONY: help
.DEFAULT_GOAL := help
help:
@echo -e "Some useful commands for develop\n"
@grep -h -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
full: check ## Build and run the tests
check: build test ## Build and run the tests
get-test-deps: ## Install test dependencies
get install github.com/axw/gocov/gocov
get install gopkg.in/matm/v1/gocov-html
print-version: ## Print version
@echo $(VERSION)
build: $(GO_DEPENDENCIES) clean ## Build jx-labs binary for current OS
go mod download
CGO_ENABLED=$(CGO_ENABLED) $(GO) $(BUILD_TARGET) $(BUILDFLAGS) -o build/$(NAME) $(MAIN_SRC_FILE)
label: $(GO_DEPENDENCIES)
CGO_ENABLED=$(CGO_ENABLED) $(GO) $(BUILD_TARGET) $(BUILDFLAGS) -o build/jx-label fns/label/main.go
build-all: $(GO_DEPENDENCIES) build make-reports-dir ## Build all files - runtime, all tests etc.
CGO_ENABLED=$(CGO_ENABLED) $(GOTEST) -run=nope -tags=integration -failfast -short ./... $(BUILDFLAGS)
tidy-deps: ## Cleans up dependencies
$(GO) mod tidy
# mod tidy only takes compile dependencies into account, let's make sure we capture tooling dependencies as well
@$(MAKE) install-generate-deps
pprof-web: ## generate pprof file and start an web-ui
cd ./_example; go mod tidy; go run ./pprof
#go tool pprof rux_prof_data.prof
go tool pprof -http=:8080 ./_example/rux_prof_data.prof
.PHONY: make-reports-dir
make-reports-dir:
mkdir -p $(REPORTS_DIR)
test: ## Run tests with the "unit" build tag
KUBECONFIG=/cluster/connections/not/allowed CGO_ENABLED=$(CGO_ENABLED) $(GOTEST) --tags=unit -failfast -short ./... $(TEST_BUILDFLAGS)
test-coverage : make-reports-dir ## Run tests and coverage for all tests with the "unit" build tag
CGO_ENABLED=$(CGO_ENABLED) $(GOTEST) --tags=unit $(COVERFLAGS) -failfast -short ./... $(TEST_BUILDFLAGS)
test-report: make-reports-dir get-test-deps test-coverage ## Create the test report
@gocov convert $(COVER_OUT) | gocov report
test-report-html: make-reports-dir get-test-deps test-coverage ## Create the test report in HTML format
@gocov convert $(COVER_OUT) | gocov-html > $(REPORTS_DIR)/cover.html && open $(REPORTS_DIR)/cover.html
test-bench: ## run bench test report in _example dir
cd ./_example; go mod tidy; \
go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_loglibs_test.go
install: $(GO_DEPENDENCIES) ## Install the binary
GOBIN=${GOPATH}/bin $(GO) install $(BUILDFLAGS) $(MAIN_SRC_FILE)
linux: ## Build for Linux
CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 $(GO) $(BUILD_TARGET) $(BUILDFLAGS) -o build/linux/$(NAME) $(MAIN_SRC_FILE)
chmod +x build/linux/$(NAME)
arm: ## Build for ARM
CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=arm $(GO) $(BUILD_TARGET) $(BUILDFLAGS) -o build/arm/$(NAME) $(MAIN_SRC_FILE)
chmod +x build/arm/$(NAME)
win: ## Build for Windows
CGO_ENABLED=$(CGO_ENABLED) GOOS=windows GOARCH=amd64 $(GO) $(BUILD_TARGET) $(BUILDFLAGS) -o build/win/$(NAME)-windows-amd64.exe $(MAIN_SRC_FILE)
darwin: ## Build for OSX
CGO_ENABLED=$(CGO_ENABLED) GOOS=darwin GOARCH=amd64 $(GO) $(BUILD_TARGET) $(BUILDFLAGS) -o build/darwin/$(NAME) $(MAIN_SRC_FILE)
chmod +x build/darwin/$(NAME)
.PHONY: release
release: clean linux test
release-all: release linux win darwin
promoter:
cd promote && go build main.go
.PHONY: goreleaser
goreleaser:
step-go-releaser --organisation=$(ORG) --revision=$(REV) --branch=$(BRANCH) --build-date=$(BUILD_DATE) --go-version=$(GO_VERSION) --root-package=$(ROOT_PACKAGE) --version=$(VERSION) --timeout 200m
.PHONY: clean
clean: ## Clean the generated artifacts
rm -rf build release dist
get-fmt-deps: ## Install test dependencies
get install golang.org/x/tools/cmd/goimports
.PHONY: fmt
fmt: importfmt ## Format the code
$(eval FORMATTED = $(shell $(GO) fmt ./...))
@if [ "$(FORMATTED)" == "" ]; \
then \
echo "All Go files properly formatted"; \
else \
echo "Fixed formatting for: $(FORMATTED)"; \
fi
.PHONY: importfmt
importfmt: get-fmt-deps
@echo "Formatting the imports..."
goimports -w $(GO_DEPENDENCIES)
.PHONY: lint
lint: ## Lint the code
./hack/gofmt.sh
./hack/linter.sh
./hack/generate.sh
.PHONY: all
all: fmt build test lint generate-refdocs
install-refdocs:
$(GO) get github.com/jenkins-x/gen-crd-api-reference-docs
generate-refdocs: install-refdocs
gen-crd-api-reference-docs -config "hack/configdocs/config.json" \
-template-dir hack/configdocs/templates \
-api-dir "./pkg/apis/gitops/v1alpha1" \
-out-file docs/config.md
generate-scheduler-refdocs: install-refdocs
gen-crd-api-reference-docs -config "hack/configdocs/config.json" \
-template-dir hack/configdocs/templates \
-api-dir "./pkg/apis/scheduler/v1alpha1" \
-out-file docs/scheduler-config.md
bin/docs:
go build $(LDFLAGS) -v -o bin/docs cmd/docs/*.go
.PHONY: docs
docs: bin/docs generate-refdocs generate-scheduler-refdocs ## update docs
@echo "Generating docs"
@./bin/docs --target=./docs/cmd
@./bin/docs --target=./docs/man/man1 --kind=man
@rm -f ./bin/docs
================================================
FILE: README.md
================================================
# slog

[](https://pkg.go.dev/github.com/gookit/slog)
[](https://goreportcard.com/report/github.com/gookit/slog)
[](https://github.com/gookit/slog/actions)
[](https://github.com/gookit/slog)
[](https://coveralls.io/github/gookit/slog?branch=master)
📑 Lightweight, structured, extensible, configurable logging library written in Golang.
**Output in console:**

## Features
- Simple, directly available without configuration
- Support common log level processing.
- eg: `trace` `debug` `info` `notice` `warn` `error` `fatal` `panic`
- Support any extension of `Handler` `Formatter` as needed
- Supports adding multiple `Handler` log processing at the same time, outputting logs to different places
- Support to custom log message `Formatter`
- Built-in `json` `text` two log record formatting `Formatter`
- Support to custom build log messages `Handler`
- The built-in `handler.Config` `handler.Builder` can easily and quickly build the desired log handler
- Has built-in common log write handler program
- `console` output logs to the console, supports color output
- `writer` output logs to the specified `io.Writer`
- `file` output log to the specified file, optionally enable `buffer` to buffer writes
- `simple` output log to the specified file, write directly to the file without buffering
- `rotate_file` outputs logs to the specified file, and supports splitting files by time and size, and `buffer` buffered writing is enabled by default
- See [./handler](./handler) folder for more built-in implementations
- Benchmark performance test please see [Benchmarks](#benchmarks)
### Output logs to file
- Support enabling `buffer` for log writing
- Support splitting log files by `time` and `size`
- Support configuration to compress log files via `gzip`
- Support clean old log files by `BackupNum` `BackupTime`
### `rotatefile` subpackage
- The `rotatefile` subpackage is a stand-alone tool library with file splitting, cleaning, and compressing backups
- `rotatefile.Writer` can also be directly wrapped and used in other logging libraries. For example: `log`, `glog`, `zap`, etc.
- `rotatefile.FilesClear` is an independent file cleaning backup tool, which can be used in other places (such as other program log cleaning such as PHP)
- For more usage, please see [rotatefile](rotatefile/README.md)
### Use slog in GORM
Please see https://github.com/gookit/slog/issues/127#issuecomment-2827745713
## [中文说明](README.zh-CN.md)
中文说明请阅读 [README.zh-CN](README.zh-CN.md)
## GoDoc
- [Godoc for github](https://pkg.go.dev/github.com/gookit/slog?tab=doc)
## Install
```bash
go get github.com/gookit/slog
```
## Quick Start
`slog` is very simple to use and can be used without any configuration
```go
package main
import (
"github.com/gookit/slog"
)
func main() {
slog.Info("info log message")
slog.Warn("warning log message")
slog.Infof("info log %s", "message")
slog.Debugf("debug %s", "message")
}
```
**Output:**
```text
[2020/07/16 12:19:33] [application] [INFO] [main.go:7] info log message
[2020/07/16 12:19:33] [application] [WARNING] [main.go:8] warning log message
[2020/07/16 12:19:33] [application] [INFO] [main.go:9] info log message
[2020/07/16 12:19:33] [application] [DEBUG] [main.go:10] debug message
```
### Console Color
You can enable color on output logs to console. _This is default_
```go
package main
import (
"github.com/gookit/slog"
)
func main() {
slog.Configure(func(logger *slog.SugaredLogger) {
f := logger.Formatter.(*slog.TextFormatter)
f.EnableColor = true
})
slog.Trace("this is a simple log message")
slog.Debug("this is a simple log message")
slog.Info("this is a simple log message")
slog.Notice("this is a simple log message")
slog.Warn("this is a simple log message")
slog.Error("this is a simple log message")
slog.Fatal("this is a simple log message")
}
```
**Output:**

### Change log output style
Above is the `Formatter` setting that changed the default logger.
> You can also create your own logger and append `ConsoleHandler` to support printing logs to the console:
```go
h := handler.NewConsoleHandler(slog.AllLevels)
l := slog.NewWithHandlers(h)
l.Trace("this is a simple log message")
l.Debug("this is a simple log message")
```
Change the default logger log output style:
```go
h.Formatter().(*slog.TextFormatter).SetTemplate(slog.NamedTemplate)
```
**Output:**

> Note: `slog.TextFormatter` uses a template string to format the output log, so the new field output needs to adjust the template at the same time.
### Use JSON Format
`slog` also has a built-in `Formatter` for JSON format. If not specified, the default is to use `TextFormatter` to format log records.
```go
package main
import (
"github.com/gookit/slog"
)
func main() {
// use JSON formatter
slog.SetFormatter(slog.NewJSONFormatter())
slog.Info("info log message")
slog.Warn("warning log message")
slog.WithData(slog.M{
"key0": 134,
"key1": "abc",
}).Infof("info log %s", "message")
r := slog.WithFields(slog.M{
"category": "service",
"IP": "127.0.0.1",
})
r.Infof("info %s", "message")
r.Debugf("debug %s", "message")
}
```
**Output:**
```text
{"channel":"application","data":{},"datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info log message"}
{"channel":"application","data":{},"datetime":"2020/07/16 13:23:33","extra":{},"level":"WARNING","message":"warning log message"}
{"channel":"application","data":{"key0":134,"key1":"abc"},"datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info log message"}
{"IP":"127.0.0.1","category":"service","channel":"application","datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info message"}
{"IP":"127.0.0.1","category":"service","channel":"application","datetime":"2020/07/16 13:23:33","extra":{},"level":"DEBUG","message":"debug message"}
```
## Introduction
- `Logger` - log dispatcher. One logger can register multiple `Handler`, `Processor`
- `Record` - log records, each log is a `Record` instance.
- `Processor` - enables extended processing of log records. It is called before the log `Record` is processed by the `Handler`.
- You can use it to perform additional operations on `Record`, such as: adding fields, adding extended information, etc.
- `Handler` - log handler, each log will be processed by `Handler.Handle()`.
- Here you can send logs to console, file, remote server, etc.
- `Formatter` - logging data formatting process.
- Usually set in `Handler`, it can be used to format log records, convert records into text, JSON, etc., `Handler` then writes the formatted data to the specified place.
- `Formatter` is not required. You can do without it and handle logging directly in `Handler.Handle()`.
**Simple structure of log scheduler**:
```text
Processors
Logger --{
Handlers --|- Handler0 With Formatter0
|- Handler1 With Formatter1
|- Handler2 (can also without Formatter)
|- ... more
```
> Note: Be sure to remember to add `Handler`, `Processor` to the logger instance and log records will be processed by `Handler`.
### Processor
`Processor` interface:
```go
// Processor interface definition
type Processor interface {
// Process record
Process(record *Record)
}
// ProcessorFunc definition
type ProcessorFunc func(record *Record)
// Process record
func (fn ProcessorFunc) Process(record *Record) {
fn(record)
}
```
> You can use it to perform additional operations on the Record before the log `Record` reaches the `Handler` for processing, such as: adding fields, adding extended information, etc.
Add processor to logger:
```go
slog.AddProcessor(slog.AddHostname())
// or
l := slog.New()
l.AddProcessor(slog.AddHostname())
```
The built-in processor `slog.AddHostname` is used here as an example, which can add a new field `hostname` on each log record.
```go
slog.AddProcessor(slog.AddHostname())
slog.Info("message")
```
Output, including new fields `"hostname":"InhereMac"`:
```json
{"channel":"application","level":"INFO","datetime":"2020/07/17 12:01:35","hostname":"InhereMac","data":{},"extra":{},"message":"message"}
```
### Handler
`Handler` interface:
> You can customize any `Handler` you want, just implement the `slog.Handler` interface.
```go
// Handler interface definition
type Handler interface {
io.Closer
Flush() error
// IsHandling Checks whether the given record will be handled by this handler.
IsHandling(level Level) bool
// Handle a log record.
// all records may be passed to this method, and the handler should discard
// those that it does not want to handle.
Handle(*Record) error
}
```
### Formatter
`Formatter` interface:
```go
// Formatter interface
type Formatter interface {
Format(record *Record) ([]byte, error)
}
```
Function wrapper type:
```go
// FormatterFunc wrapper definition
type FormatterFunc func(r *Record) ([]byte, error)
// Format a log record
func (fn FormatterFunc) Format(r *Record) ([]byte, error) {
return fn(r)
}
```
**JSON formatter**
```go
type JSONFormatter struct {
// Fields exported log fields.
Fields []string
// Aliases for output fields. you can change export field name.
// item: `"field" : "output name"`
// eg: {"message": "msg"} export field will display "msg"
Aliases StringMap
// PrettyPrint will indent all json logs
PrettyPrint bool
// TimeFormat the time format layout. default is time.RFC3339
TimeFormat string
}
```
**Text formatter**
Default templates:
```go
const DefaultTemplate = "[{{datetime}}] [{{channel}}] [{{level}}] [{{caller}}] {{message}} {{data}} {{extra}}\n"
const NamedTemplate = "{{datetime}} channel={{channel}} level={{level}} [file={{caller}}] message={{message}} data={{data}}\n"
```
Change template:
```go
myTemplate := "[{{datetime}}] [{{level}}] {{message}}"
f := slog.NewTextFormatter()
f.SetTemplate(myTemplate)
```
## Custom logger
Custom `Processor` and `Formatter` are relatively simple, just implement a corresponding method.
### Create new logger
`slog.Info, slog.Warn` and other methods use the default logger and output logs to the console by default.
You can create a brand-new instance of `slog.Logger`:
**Method 1**:
```go
l := slog.New()
// add handlers ...
h1 := handler.NewConsoleHandler(slog.AllLevels)
l.AddHandlers(h1)
```
**Method 2**:
```go
l := slog.NewWithName("myLogger")
// add handlers ...
h1 := handler.NewConsoleHandler(slog.AllLevels)
l.AddHandlers(h1)
```
**Method 3**:
```go
package main
import (
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func main() {
l := slog.NewWithHandlers(handler.NewConsoleHandler(slog.AllLevels))
l.Info("message")
}
```
### Create custom Handler
You only need to implement the `slog.Handler` interface to create a custom `Handler`.
You can quickly assemble your own Handler through the built-in `handler.LevelsWithFormatter` `handler.LevelWithFormatter` and other fragments of slog.
Examples:
> Use `handler.LevelsWithFormatter`, only need to implement `Close, Flush, Handle` methods
```go
type MyHandler struct {
handler.LevelsWithFormatter
Output io.Writer
}
func (h *MyHandler) Handle(r *slog.Record) error {
// you can write log message to file or send to remote.
}
func (h *MyHandler) Flush() error {}
func (h *MyHandler) Close() error {}
```
Add `Handler` to the logger to use:
```go
// add to default logger
slog.AddHander(&MyHandler{})
// or, add to custom logger:
l := slog.New()
l.AddHander(&MyHandler{})
```
## Use the built-in handlers
[./handler](handler) package has built-in common log handlers, which can basically meet most scenarios.
```go
// Output logs to console, allow render color.
func NewConsoleHandler(levels []slog.Level) *ConsoleHandler
// Send logs to email
func NewEmailHandler(from EmailOption, toAddresses []string) *EmailHandler
// Send logs to syslog
func NewSysLogHandler(priority syslog.Priority, tag string) (*SysLogHandler, error)
// A simple handler implementation that outputs logs to a given io.Writer
func NewSimpleHandler(out io.Writer, level slog.Level) *SimpleHandler
```
**Output log to file**:
```go
// Output log to the specified file, without buffering by default
func NewFileHandler(logfile string, fns ...ConfigFn) (h *SyncCloseHandler, err error)
// Output logs to the specified file in JSON format, without buffering by default
func JSONFileHandler(logfile string, fns ...ConfigFn) (*SyncCloseHandler, error)
// Buffered output log to specified file
func NewBuffFileHandler(logfile string, buffSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
```
> TIP: `NewFileHandler` `JSONFileHandler` can also enable write buffering by passing in fns `handler.WithBuffSize(buffSize)`
**Output log to file and rotate automatically**:
```go
// Automatic rotating according to file size
func NewSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
// Automatic rotating according to time
func NewTimeRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
// It supports configuration to rotate according to size and time.
// The default setting file size is 20M, and the default automatic splitting time is 1 hour (EveryHour).
func NewRotateFileHandler(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
```
> TIP: By passing in `fns ...ConfigFn`, more options can be set, such as log file retention time, log write buffer size, etc. For detailed settings, see the `handler.Config` structure
### Logs to file
Output log to the specified file, `buffer` buffered writing is not enabled by default. Buffering can also be enabled by passing in a parameter.
```go
package mypkg
import (
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func main() {
defer slog.MustClose()
// DangerLevels contains: slog.PanicLevel, slog.ErrorLevel, slog.WarnLevel
h1 := handler.MustFileHandler("/tmp/error.log", handler.WithLogLevels(slog.DangerLevels))
// custom log format
// f := h1.Formatter().(*slog.TextFormatter)
f := slog.AsTextFormatter(h1.Formatter())
f.SetTemplate("your template format\n")
// NormalLevels contains: slog.InfoLevel, slog.NoticeLevel, slog.DebugLevel, slog.TraceLevel
h2 := handler.MustFileHandler("/tmp/info.log", handler.WithLogLevels(slog.NormalLevels))
// register handlers
slog.PushHandler(h1)
slog.PushHandler(h2)
// add logs
slog.Info("info message text")
slog.Error("error message text")
}
```
> **Note**: If write buffering `buffer` is enabled, be sure to call `logger.Close()` at the end of the program to flush the contents of the buffer to the file.
### Log to file with automatic rotating
`slog/handler` also has a built-in output log to a specified file, and supports splitting files by time and size at the same time.
By default, `buffer` buffered writing is enabled
```go
func Example_rotateFileHandler() {
h1 := handler.MustRotateFile("/tmp/error.log", handler.EveryHour, handler.WithLogLevels(slog.DangerLevels))
h2 := handler.MustRotateFile("/tmp/info.log", handler.EveryHour, handler.WithLogLevels(slog.NormalLevels))
slog.PushHandler(h1)
slog.PushHandler(h2)
// add logs
slog.Info("info message")
slog.Error("error message")
}
```
Example of file name sliced by time:
```text
time-rotate-file.log
time-rotate-file.log.20201229_155753
time-rotate-file.log.20201229_155754
```
Example of a filename cut by size, in the format `filename.log.HIS_000N`. For example:
```text
size-rotate-file.log
size-rotate-file.log.122915_0001
size-rotate-file.log.122915_0002
```
### Use rotatefile on another logger
`rotatefile.Writer` can also be used with other logging packages, such as: `log`, `glog`, etc.
For example, using `rotatefile` on golang `log`:
```go
package main
import (
"log"
"github.com/gookit/slog/rotatefile"
)
func main() {
logFile := "testdata/go_logger.log"
writer, err := rotatefile.NewConfig(logFile).Create()
if err != nil {
panic(err)
}
log.SetOutput(writer)
log.Println("log message")
}
```
### Quickly create a Handler based on config
This is config struct for create a Handler:
```go
// Config struct
type Config struct {
// Logfile for write logs
Logfile string `json:"logfile" yaml:"logfile"`
// LevelMode for filter log record. default LevelModeList
LevelMode uint8 `json:"level_mode" yaml:"level_mode"`
// Level value. use on LevelMode = LevelModeValue
Level slog.Level `json:"level" yaml:"level"`
// Levels for log record
Levels []slog.Level `json:"levels" yaml:"levels"`
// UseJSON for format logs
UseJSON bool `json:"use_json" yaml:"use_json"`
// BuffMode type name. allow: line, bite
BuffMode string `json:"buff_mode" yaml:"buff_mode"`
// BuffSize for enable buffer, unit is bytes. set 0 to disable buffer
BuffSize int `json:"buff_size" yaml:"buff_size"`
// RotateTime for rotate file, unit is seconds.
RotateTime rotatefile.RotateTime `json:"rotate_time" yaml:"rotate_time"`
// MaxSize on rotate file by size, unit is bytes.
MaxSize uint64 `json:"max_size" yaml:"max_size"`
// Compress determines if the rotated log files should be compressed using gzip.
// The default is not to perform compression.
Compress bool `json:"compress" yaml:"compress"`
// BackupNum max number for keep old files.
// 0 is not limit, default is 20.
BackupNum uint `json:"backup_num" yaml:"backup_num"`
// BackupTime max time for keep old files. unit is hours
// 0 is not limit, default is a week.
BackupTime uint `json:"backup_time" yaml:"backup_time"`
// RenameFunc build filename for rotate file
RenameFunc func(filepath string, rotateNum uint) string
}
```
**Examples**:
```go
testFile := "testdata/error.log"
h := handler.NewEmptyConfig(
handler.WithLogfile(testFile),
handler.WithBuffSize(1024*8),
handler.WithRotateTimeString("1hour"),
handler.WithLogLevels(slog.DangerLevels),
).
CreateHandler()
l := slog.NewWithHandlers(h)
```
**About BuffMode**
`Config.BuffMode` The name of the BuffMode type to use. Allow: line, bite
- `BuffModeBite`: Buffer by bytes, when the number of bytes in the buffer reaches the specified size, write the contents of the buffer to the file
- `BuffModeLine`: Buffer by line, when the buffer size is reached, always ensure that a complete line of log content is written to the file (to avoid log content being truncated)
### Use Builder to quickly create Handler
Use `handler.Builder` to easily and quickly create Handler instances.
```go
testFile := "testdata/info.log"
h := handler.NewBuilder().
WithLogfile(testFile).
WithLogLevels(slog.NormalLevels).
WithBuffSize(1024*8).
WithRotateTime(rotatefile.Every30Min).
WithCompress(true).
Build()
l := slog.NewWithHandlers(h)
```
## Extension packages
Package `bufwrite`:
- `bufwrite.BufIOWriter` additionally implements `Sync(), Close()` methods by wrapping go's `bufio.Writer`, which is convenient to use
- `bufwrite.LineWriter` refer to the implementation of `bufio.Writer` in go, which can support flushing the buffer by line, which is more useful for writing log files
Package `rotatefile`:
- `rotatefile.Writer` implements automatic cutting of log files according to size and specified time, and also supports automatic cleaning of log files
- `handler/rotate_file` is to use it to cut the log file
### Use rotatefile on other log package
Of course, the rotatefile.Writer can be use on other log package, such as: `log`, `glog` and more.
Examples, use rotatefile on golang `log`:
```go
package main
import (
"log"
"github.com/gookit/slog/rotatefile"
)
func main() {
logFile := "testdata/another_logger.log"
writer, err := rotatefile.NewConfig(logFile).Create()
if err != nil {
panic(err)
}
log.SetOutput(writer)
log.Println("log message")
}
```
## Testing and benchmark
### Unit tests
run unit tests:
```bash
go test ./...
```
### Benchmarks
Benchmark code at [_example/bench_loglibs_test.go](_example/bench_loglibs_test.go)
```bash
make test-bench
```
Benchmarks for `slog` and other log packages:
> **Note**: test and record ad 2023.04.13
```shell
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 8381674 1429 ns/op 216 B/op 3 allocs/op
BenchmarkZapSugarNegative
BenchmarkZapSugarNegative-4 8655980 1383 ns/op 104 B/op 4 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 14173719 849.8 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 27456256 451.2 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 2550771 4784 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogNegative
>>>> BenchmarkGookitSlogNegative-4 8798220 1375 ns/op 120 B/op 3 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 10302483 1167 ns/op 192 B/op 1 allocs/op
BenchmarkZapSugarPositive
BenchmarkZapSugarPositive-4 3833311 3154 ns/op 344 B/op 7 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 14120524 846.7 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 27152686 434.9 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 2601892 4691 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
>>>> BenchmarkGookitSlogPositive-4 8997104 1340 ns/op 120 B/op 3 allocs/op
PASS
ok command-line-arguments 167.095s
```
## Gookit packages
- [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files
- [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP
- [gookit/gcli](https://github.com/gookit/gcli) Build CLI application, tool library, running CLI commands
- [gookit/slog](https://github.com/gookit/slog) Lightweight, extensible, configurable logging library written in Go
- [gookit/color](https://github.com/gookit/color) A command-line color library with true color support, universal API methods and Windows support
- [gookit/event](https://github.com/gookit/event) Lightweight event manager and dispatcher implements by Go
- [gookit/cache](https://github.com/gookit/cache) Generic cache use and cache manager for golang. support File, Memory, Redis, Memcached.
- [gookit/config](https://github.com/gookit/config) Go config management. support JSON, YAML, TOML, INI, HCL, ENV and Flags
- [gookit/filter](https://github.com/gookit/filter) Provide filtering, sanitizing, and conversion of golang data
- [gookit/validate](https://github.com/gookit/validate) Use for data validation and filtering. support Map, Struct, Form data
- [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more
- More, please see https://github.com/gookit
## Acknowledgment
The projects is heavily inspired by follow packages:
- https://github.com/phuslu/log
- https://github.com/golang/glog
- https://github.com/sirupsen/logrus
- https://github.com/Seldaek/monolog
- https://github.com/syyongx/llog
- https://github.com/uber-go/zap
- https://github.com/rs/zerolog
- https://github.com/natefinch/lumberjack
## LICENSE
[MIT](LICENSE)
================================================
FILE: README.zh-CN.md
================================================
# slog

[](https://pkg.go.dev/github.com/gookit/slog)
[](https://goreportcard.com/report/github.com/gookit/slog)
[](https://github.com/gookit/slog/actions)
[](https://github.com/gookit/slog)
[](https://coveralls.io/github/gookit/slog?branch=master)
📑 Go 实现的一个易于使用的,结构化的,易扩展、可配置的日志库。
**控制台日志效果:**

## 功能特色
- 简单,无需配置,开箱即用
- 支持常用的日志级别处理
- 如: `trace` `debug` `info` `notice` `warn` `error` `fatal` `panic`
- 可以任意扩展自己需要的 `Handler` `Formatter`
- 支持同时添加多个 `Handler` 日志处理,输出日志到不同的地方
- 支持自定义构建 `Handler` 处理器
- 内置的 `handler.Config` `handler.Builder`,可以方便快捷的构建想要的日志处理器
- 支持自定义 `Formatter` 格式化处理
- 内置了 `json` `text` 两个日志记录格式化 `Formatter`
- 已经内置了常用的日志处理器
- `console` 输出日志到控制台,支持色彩输出
- `writer` 输出日志到指定的 `io.Writer`
- `file` 输出日志到指定文件,可选启用 `buffer` 缓冲写入
- `simple` 输出日志到指定文件,无缓冲直接写入文件
- `rotate_file` 输出日志到指定文件,并且同时支持按时间、按大小分割文件,默认启用 `buffer` 缓冲写入
- 更多内置实现请查看 [./handler](./handler) 文件夹
- 基准性能测试请看 [Benchmarks](#benchmarks)
### 输出日志到文件
- 支持启用 `buffer` 缓冲日志写入
- 支持按时间、按大小自动分割文件
- 支持配置通过 `gzip` 压缩日志文件
- 支持清理旧日志文件 配置: `BackupNum` `BackupTime`
### `rotatefile` 子包
- `rotatefile` 子包是一个拥有文件分割,清理,压缩备份的独立工具库
- `rotatefile.Writer` 也可以直接包装使用用在其他日志库。例如:`log`、`glog`、`zap` 等等
- `rotatefile.FilesClear` 是一个独立的文件清理备份工具, 可以用在其他地方(如 PHP等其他程序日志清理)
- 更多使用请查看 [rotatefile](rotatefile/README.md)
### GORM 中使用 slog
请查看 https://github.com/gookit/slog/issues/127#issuecomment-2827745713
## [English](README.md)
English instructions please see [./README](README.md)
## GoDoc
- [Godoc for github](https://pkg.go.dev/github.com/gookit/slog?tab=doc)
## 安装
```bash
go get github.com/gookit/slog
```
## 快速开始
`slog` 使用非常简单,无需任何配置即可使用。
```go
package main
import (
"github.com/gookit/slog"
)
func main() {
slog.Info("info log message")
slog.Warn("warning log message")
slog.Infof("info log %s", "message")
slog.Debugf("debug %s", "message")
}
```
**输出预览:**
```text
[2020/07/16 12:19:33] [application] [INFO] [main.go:7] info log message
[2020/07/16 12:19:33] [application] [WARNING] [main.go:8] warning log message
[2020/07/16 12:19:33] [application] [INFO] [main.go:9] info log message
[2020/07/16 12:19:33] [application] [DEBUG] [main.go:10] debug message
```
### 启用控制台颜色
您可以在输出控制台日志时启用颜色输出,将会根据不同级别打印不同色彩。
```go
package main
import (
"github.com/gookit/slog"
)
func main() {
slog.Configure(func(logger *slog.SugaredLogger) {
f := logger.Formatter.(*slog.TextFormatter)
f.EnableColor = true
})
slog.Trace("this is a simple log message")
slog.Debug("this is a simple log message")
slog.Info("this is a simple log message")
slog.Notice("this is a simple log message")
slog.Warn("this is a simple log message")
slog.Error("this is a simple log message")
slog.Fatal("this is a simple log message")
}
```
**输出预览:**

### 更改日志输出样式
上面是更改了默认logger的 `Formatter` 设置。
> 你也可以创建自己的logger,并追加 `ConsoleHandler` 来支持打印日志到控制台:
```go
h := handler.NewConsoleHandler(slog.AllLevels)
l := slog.NewWithHandlers(h)
l.Trace("this is a simple log message")
l.Debug("this is a simple log message")
```
更改默认的logger日志输出样式:
```go
h.Formatter().(*slog.TextFormatter).SetTemplate(slog.NamedTemplate)
```
**输出预览:**

> 注意:`slog.TextFormatter` 使用模板字符串来格式化输出日志,因此新增字段输出需要同时调整模板。
### 使用JSON格式
slog 也内置了 JSON 格式的 `Formatter`。若不特别指定,默认都是使用 `TextFormatter` 格式化日志记录。
```go
package main
import (
"github.com/gookit/slog"
)
func main() {
// use JSON formatter
slog.SetFormatter(slog.NewJSONFormatter())
slog.Info("info log message")
slog.Warn("warning log message")
slog.WithData(slog.M{
"key0": 134,
"key1": "abc",
}).Infof("info log %s", "message")
r := slog.WithFields(slog.M{
"category": "service",
"IP": "127.0.0.1",
})
r.Infof("info %s", "message")
r.Debugf("debug %s", "message")
}
```
**输出预览:**
```text
{"channel":"application","data":{},"datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info log message"}
{"channel":"application","data":{},"datetime":"2020/07/16 13:23:33","extra":{},"level":"WARNING","message":"warning log message"}
{"channel":"application","data":{"key0":134,"key1":"abc"},"datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info log message"}
{"IP":"127.0.0.1","category":"service","channel":"application","datetime":"2020/07/16 13:23:33","extra":{},"level":"INFO","message":"info message"}
{"IP":"127.0.0.1","category":"service","channel":"application","datetime":"2020/07/16 13:23:33","extra":{},"level":"DEBUG","message":"debug message"}
```
## 架构说明
- `Logger` - 日志调度器. 一个logger可以注册多个 `Handler`,`Processor`
- `Record` - 日志记录,每条日志就是一个 `Record` 实例。
- `Processor` - 可以对日志记录进行扩展处理。它在日志 `Record` 被 `Handler` 处理之前调用。
- 你可以使用它对 `Record` 进行额外的操作,比如:新增字段,添加扩展信息等
- `Handler` - 日志处理器,每条日志都会经过 `Handler.Handle()` 处理。
- 在这里你可以将日志发送到 控制台,文件,远程服务器等等。
- `Formatter` - 日志记录数据格式化处理。
- 通常设置于 `Handler` 中,可以用于格式化日志记录,将记录转成文本,JSON等,`Handler` 再将格式化后的数据写入到指定的地方。
- `Formatter` 不是必须的。你可以不使用它,直接在 `Handler.Handle()` 中对日志记录进行处理。
**日志调度器简易结构**:
```text
Processors
Logger --{
Handlers --|- Handler0 With Formatter0
|- Handler1 With Formatter1
|- Handler2 (can also without Formatter)
|- ... more
```
> 注意:一定要记得将 `Handler`, `Processor` 添加注册到 logger 实例上,日志记录才会经过 `Handler` 处理。
### Processor 定义
`Processor` 接口定义如下:
```go
// Processor interface definition
type Processor interface {
// Process record
Process(record *Record)
}
// ProcessorFunc definition
type ProcessorFunc func(record *Record)
// Process record
func (fn ProcessorFunc) Process(record *Record) {
fn(record)
}
```
> 你可以使用它在日志 `Record` 到达 `Handler` 处理之前,对Record进行额外的操作,比如:新增字段,添加扩展信息等
添加 processor 到 logger:
```go
slog.AddProcessor(mypkg.AddHostname())
// or
l := slog.New()
l.AddProcessor(mypkg.AddHostname())
```
这里使用内置的processor `slog.AddHostname` 作为示例,它可以在每条日志记录上添加新字段 `hostname`。
```go
slog.AddProcessor(slog.AddHostname())
slog.Info("message")
```
输出效果,包含新增字段 `"hostname":"InhereMac"`:
```json
{"channel":"application","level":"INFO","datetime":"2020/07/17 12:01:35","hostname":"InhereMac","data":{},"extra":{},"message":"message"}
```
### Handler 定义
`Handler` 接口定义如下:
> 你可以自定义任何想要的 `Handler`,只需要实现 `slog.Handler` 接口即可。
```go
// Handler interface definition
type Handler interface {
io.Closer
Flush() error
// IsHandling Checks whether the given record will be handled by this handler.
IsHandling(level Level) bool
// Handle a log record.
// all records may be passed to this method, and the handler should discard
// those that it does not want to handle.
Handle(*Record) error
}
```
### Formatter 定义
`Formatter` 接口定义如下:
```go
// Formatter interface
type Formatter interface {
Format(record *Record) ([]byte, error)
}
```
函数包装类型:
```go
// FormatterFunc wrapper definition
type FormatterFunc func(r *Record) ([]byte, error)
// Format a log record
func (fn FormatterFunc) Format(r *Record) ([]byte, error) {
return fn(r)
}
```
**JSON格式化Formatter**
```go
type JSONFormatter struct {
// Fields exported log fields.
Fields []string
// Aliases for output fields. you can change export field name.
// item: `"field" : "output name"`
// eg: {"message": "msg"} export field will display "msg"
Aliases StringMap
// PrettyPrint will indent all json logs
PrettyPrint bool
// TimeFormat the time format layout. default is time.RFC3339
TimeFormat string
}
```
**Text格式化formatter**
默认模板:
```go
const DefaultTemplate = "[{{datetime}}] [{{channel}}] [{{level}}] [{{caller}}] {{message}} {{data}} {{extra}}\n"
const NamedTemplate = "{{datetime}} channel={{channel}} level={{level}} [file={{caller}}] message={{message}} data={{data}}\n"
```
更改模板:
```go
myTemplate := "[{{datetime}}] [{{level}}] {{message}}"
f := slog.NewTextFormatter()
f.SetTemplate(myTemplate)
```
## 自定义日志
自定义 Processor 和 自定义 Formatter 都比较简单,实现一个对应方法即可。
### 创建自定义Logger实例
`slog.Info, slog.Warn` 等方法,使用的默认logger,并且默认输出日志到控制台。
你可以创建一个全新的 `slog.Logger` 实例:
**方式1**:
```go
l := slog.New()
// add handlers ...
h1 := handler.NewConsoleHandler(slog.AllLevels)
l.AddHandlers(h1)
```
**方式2**:
```go
l := slog.NewWithName("myLogger")
// add handlers ...
h1 := handler.NewConsoleHandler(slog.AllLevels)
l.AddHandlers(h1)
```
**方式3**:
```go
package main
import (
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func main() {
l := slog.NewWithHandlers(handler.NewConsoleHandler(slog.AllLevels))
l.Info("message")
}
```
### 创建自定义 Handler
你只需要实现 `slog.Handler` 接口即可创建自定义 `Handler`。你可以通过 slog内置的
`handler.LevelsWithFormatter` `handler.LevelWithFormatter`等片段快速的组装自己的 Handler。
示例:
> 使用了 `handler.LevelsWithFormatter`, 只还需要实现 `Close, Flush, Handle` 方法即可
```go
type MyHandler struct {
handler.LevelsWithFormatter
Output io.Writer
}
func (h *MyHandler) Handle(r *slog.Record) error {
// you can write log message to file or send to remote.
}
func (h *MyHandler) Flush() error {}
func (h *MyHandler) Close() error {}
```
将 `Handler` 添加到 logger即可使用:
```go
// 添加到默认 logger
slog.AddHander(&MyHandler{})
// 或者添加到自定义 logger:
l := slog.New()
l.AddHander(&MyHandler{})
```
## 使用内置处理器
[./handler](handler) 包已经内置了常用的日志 Handler,基本上可以满足绝大部分场景。
```go
// 输出日志到控制台
func NewConsoleHandler(levels []slog.Level) *ConsoleHandler
// 发送日志到email邮箱
func NewEmailHandler(from EmailOption, toAddresses []string) *EmailHandler
// 发送日志到系统的syslog
func NewSysLogHandler(priority syslog.Priority, tag string) (*SysLogHandler, error)
// 一个简单的handler实现,输出日志到给定的 io.Writer
func NewSimpleHandler(out io.Writer, level slog.Level) *SimpleHandler
```
**输出日志到文件**:
```go
// 输出日志到指定文件,默认不带缓冲
func NewFileHandler(logfile string, fns ...ConfigFn) (h *SyncCloseHandler, err error)
// 输出日志到指定文件且格式为JSON,默认不带缓冲
func JSONFileHandler(logfile string, fns ...ConfigFn) (*SyncCloseHandler, error)
// 带缓冲的输出日志到指定文件
func NewBuffFileHandler(logfile string, buffSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
```
> TIP: `NewFileHandler` `JSONFileHandler` 也可以通过传入 fns `handler.WithBuffSize(buffSize)` 启用写入缓冲
**输出日志到文件并自动切割**:
```go
// 根据文件大小进行自动切割
func NewSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
// 根据时间进行自动切割
func NewTimeRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
// 同时支持配置根据大小和时间进行切割, 默认设置文件大小是 20M,默认自动分割时间是 1小时(EveryHour)。
func NewRotateFileHandler(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
```
> TIP: 通过传入 `fns ...ConfigFn` 可以设置更多选项,比如 日志文件保留时间, 日志写入缓冲大小等。 详细设置请看 `handler.Config` 结构体
### 输出日志到文件
输出日志到指定文件,默认不启用 `buffer` 缓冲写入。 也可以通过传入参数启用缓冲。
```go
package mypkg
import (
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func main() {
defer slog.MustClose()
// DangerLevels 包含: slog.PanicLevel, slog.ErrorLevel, slog.WarnLevel
h1 := handler.MustFileHandler("/tmp/error.log", handler.WithLogLevels(slog.DangerLevels))
// 配置日志格式
// f := h1.Formatter().(*slog.TextFormatter)
f := slog.AsTextFormatter(h1.Formatter())
f.SetTemplate("your template format\n")
// NormalLevels 包含: slog.InfoLevel, slog.NoticeLevel, slog.DebugLevel, slog.TraceLevel
h2 := handler.MustFileHandler("/tmp/info.log", handler.WithLogLevels(slog.NormalLevels))
// 注册 handler 到 logger(调度器)
slog.PushHandler(h1)
slog.PushHandler(h2)
// add logs
slog.Info("info message text")
slog.Error("error message text")
}
```
> 提示: 如果启用了写入缓冲 `buffer`,一定要在程序结束时调用 `logger.Close()/MustClose()` 刷出缓冲区的内容到文件并关闭句柄。
### 带自动切割的日志处理器
`slog/handler` 也内置了输出日志到指定文件,并且同时支持按时间、按大小分割文件,默认启用 `buffer` 缓冲写入
```go
func Example_rotateFileHandler() {
h1 := handler.MustRotateFile("/tmp/error.log", handler.EveryHour, handler.WithLogLevels(slog.DangerLevels))
h2 := handler.MustRotateFile("/tmp/info.log", handler.EveryHour, handler.WithLogLevels(slog.NormalLevels))
slog.PushHandler(h1)
slog.PushHandler(h2)
// add logs
slog.Info("info message")
slog.Error("error message")
}
```
按时间切割文件示例:
```text
time-rotate-file.log
time-rotate-file.log.20201229_155753
time-rotate-file.log.20201229_155754
```
按大小进行切割的文件名示例, 格式 `filename.log.yMD_000N`. 例如:
```text
size-rotate-file.log
size-rotate-file.log.122915_00001
size-rotate-file.log.122915_00002
```
启用gzip压缩旧的日志文件:
```go
h1 := handler.MustRotateFile("/tmp/error.log", handler.EveryHour,
handler.WithLogLevels(slog.DangerLevels),
handler.WithCompress(true),
)
```
```text
size-rotate-file.log.122915_00001.gz
size-rotate-file.log.122915_00002.gz
```
### 根据配置快速创建Handler实例
```go
// Config struct
type Config struct {
// Logfile for write logs
Logfile string `json:"logfile" yaml:"logfile"`
// LevelMode 筛选日志记录的过滤级别,默认为 LevelModeList
LevelMode uint8 `json:"level_mode" yaml:"level_mode"`
// Level 筛选日志记录的级别值。当 LevelMode = LevelModeValue 时生效
Level slog.Level `json:"level" yaml:"level"`
// Levels 日志记录的级别列表。当 LevelMode = LevelModeList 时生效
Levels []slog.Level `json:"levels" yaml:"levels"`
// UseJSON 是否以 JSON 格式输出日志
UseJSON bool `json:"use_json" yaml:"use_json"`
// BuffMode 使用的buffer缓冲模式. allow: line, bite
BuffMode string `json:"buff_mode" yaml:"buff_mode"`
// BuffSize 开启缓冲时的缓冲区大小,单位为字节。设置为 0 时禁用缓冲
BuffSize int `json:"buff_size" yaml:"buff_size"`
// RotateTime 用于按时间切割文件,单位是秒。
RotateTime rotatefile.RotateTime `json:"rotate_time" yaml:"rotate_time"`
// MaxSize 用于按大小旋转切割文件,单位是字节。
MaxSize uint64 `json:"max_size" yaml:"max_size"`
// Compress 是否对切割后的日志进行 gzip 压缩。 默认为不压缩
Compress bool `json:"compress" yaml:"compress"`
// BackupNum 日志清理,保留旧文件的最大数量。
// 0 不限制,默认为 20。
BackupNum uint `json:"backup_num" yaml:"backup_num"`
// BackupTime 日志清理,保留旧文件的最长时间。单位是小时
// 0 不进行清理,默认为一周。
BackupTime uint `json:"backup_time" yaml:"backup_time"`
// RenameFunc build filename for rotate file
RenameFunc func(filepath string, rotateNum uint) string
}
```
**Examples**:
```go
testFile := "testdata/error.log"
h := handler.NewEmptyConfig(
handler.WithLogfile(testFile),
handler.WithBuffSize(1024*8),
handler.WithRotateTimeString("1hour"),
handler.WithLogLevels(slog.DangerLevels),
).
CreateHandler()
l := slog.NewWithHandlers(h)
```
**BuffMode说明**
`Config.BuffMode` 使用的 BuffMode 类型名称。允许的值:line、bite
- `BuffModeLine`:按行缓冲,到达缓冲大小时,始终保证一行完整日志内容写入文件(可以避免日志内容被截断)
- `BuffModeBite`:按字节缓冲,当缓冲区的字节数达到指定的大小时,将缓冲区的内容写入文件
### 使用Builder快速创建Handler实例
使用 `handler.Builder` 可以方便快速的创建Handler实例。
```go
testFile := "testdata/info.log"
h := handler.NewBuilder().
WithLogfile(testFile).
WithLogLevels(slog.NormalLevels).
WithBuffSize(1024*8).
WithRotateTime(rotatefile.Every30Min).
WithCompress(true).
Build()
l := slog.NewWithHandlers(h)
```
## 扩展工具包
`bufwrite` 包:
- `bufwrite.BufIOWriter` 通过包装go的 `bufio.Writer` 额外实现了 `Sync(), Close()` 方法,方便使用
- `bufwrite.LineWriter` 参考go的 `bufio.Writer` 实现, 可以支持按行刷出缓冲,对于写日志文件更有用
`rotatefile` 包:
- `rotatefile.Writer` 实现对日志文件按大小和指定时间进行自动切割,同时也支持自动清理日志文件
- `handler/rotate_file` 即是通过使用它对日志文件进行切割处理
### 在其他日志包上使用 rotatefile
`rotatefile.Writer` 也可以用在其他日志包上,例如:`log`、`glog` 等等。
例如,在 golang `log` 上使用 rotatefile:
```go
package main
import (
"log"
"github.com/gookit/slog/rotatefile"
)
func main() {
logFile := "testdata/another_logger.log"
writer, err := rotatefile.NewConfig(logFile).Create()
if err != nil {
panic(err)
}
log.SetOutput(writer)
log.Println("log message")
}
```
## 测试以及性能
### 单元测试
运行单元测试
```bash
go test -v ./...
```
### 性能压测
Benchmark code at [_example/bench_loglibs_test.go](_example/bench_loglibs_test.go)
```bash
make test-bench
```
Benchmarks for `slog` and other log packages:
> **Note**: test and record ad 2023.04.13
```shell
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 8381674 1429 ns/op 216 B/op 3 allocs/op
BenchmarkZapSugarNegative
BenchmarkZapSugarNegative-4 8655980 1383 ns/op 104 B/op 4 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 14173719 849.8 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 27456256 451.2 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 2550771 4784 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogNegative
>>>> BenchmarkGookitSlogNegative-4 8798220 1375 ns/op 120 B/op 3 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 10302483 1167 ns/op 192 B/op 1 allocs/op
BenchmarkZapSugarPositive
BenchmarkZapSugarPositive-4 3833311 3154 ns/op 344 B/op 7 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 14120524 846.7 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 27152686 434.9 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 2601892 4691 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
>>>> BenchmarkGookitSlogPositive-4 8997104 1340 ns/op 120 B/op 3 allocs/op
PASS
ok command-line-arguments 167.095s
```
## Gookit packages
- [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files
- [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP
- [gookit/gcli](https://github.com/gookit/gcli) Build CLI application, tool library, running CLI commands
- [gookit/slog](https://github.com/gookit/slog) Lightweight, extensible, configurable logging library written in Go
- [gookit/color](https://github.com/gookit/color) A command-line color library with true color support, universal API methods and Windows support
- [gookit/event](https://github.com/gookit/event) Lightweight event manager and dispatcher implements by Go
- [gookit/cache](https://github.com/gookit/cache) Generic cache use and cache manager for golang. support File, Memory, Redis, Memcached.
- [gookit/config](https://github.com/gookit/config) Go config management. support JSON, YAML, TOML, INI, HCL, ENV and Flags
- [gookit/filter](https://github.com/gookit/filter) Provide filtering, sanitizing, and conversion of golang data
- [gookit/validate](https://github.com/gookit/validate) Use for data validation and filtering. support Map, Struct, Form data
- [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more
- More, please see https://github.com/gookit
## Acknowledgment
实现参考了以下项目,非常感谢它们
- https://github.com/phuslu/log
- https://github.com/golang/glog
- https://github.com/sirupsen/logrus
- https://github.com/Seldaek/monolog
- https://github.com/syyongx/llog
- https://github.com/uber-go/zap
- https://github.com/rs/zerolog
- https://github.com/natefinch/lumberjack
## LICENSE
[MIT](LICENSE)
================================================
FILE: _example/bench_loglibs.md
================================================
# Log libs benchmarks
Run benchmark: `make test-bench`
> **Note**: on each test will update all package to latest.
## v0.5.5 - 2023.11.30
```shell
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 14441875 821.2 ns/op 216 B/op 3 allocs/op
BenchmarkZapSugarNegative
BenchmarkZapSugarNegative-4 13870006 916.1 ns/op 104 B/op 4 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 34721730 359.2 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 39690291 314.4 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 5605184 2161 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogNegative
BenchmarkGookitSlogNegative-4 14375598 819.2 ns/op 256 B/op 4 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 15237236 788.5 ns/op 192 B/op 1 allocs/op
BenchmarkZapSugarPositive
BenchmarkZapSugarPositive-4 6592038 1910 ns/op 344 B/op 7 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 33931623 366.1 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 38740174 309.4 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 5697038 2197 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 14531062 814.6 ns/op 256 B/op 4 allocs/op
PASS
ok command-line-arguments 159.849s
```
## v0.5.1 - 2023.04.13
> **Note**: test and record ad 2023.04.13
```shell
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 8381674 1429 ns/op 216 B/op 3 allocs/op
BenchmarkZapSugarNegative
BenchmarkZapSugarNegative-4 8655980 1383 ns/op 104 B/op 4 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 14173719 849.8 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 27456256 451.2 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 2550771 4784 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogNegative
BenchmarkGookitSlogNegative-4 8798220 1375 ns/op 120 B/op 3 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 10302483 1167 ns/op 192 B/op 1 allocs/op
BenchmarkZapSugarPositive
BenchmarkZapSugarPositive-4 3833311 3154 ns/op 344 B/op 7 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 14120524 846.7 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 27152686 434.9 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 2601892 4691 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 8997104 1340 ns/op 120 B/op 3 allocs/op
PASS
ok command-line-arguments 167.095s
```
## v0.3.5 - 2022.11.08
> **Note**: test and record ad 2022.11.08
```shell
% make test-bench
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 123297997 110.4 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 891508806 13.36 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 811990076 14.74 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 242633541 49.40 ns/op 16 B/op 1 allocs/op
BenchmarkGookitSlogNegative
BenchmarkGookitSlogNegative-4 29102253 422.8 ns/op 125 B/op 4 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 9772791 1194 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 13944360 856.8 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 27839614 431.2 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 2621076 4583 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 8908768 1359 ns/op 149 B/op 5 allocs/op
PASS
ok command-line-arguments 149.379s
```
## v0.3.0
> **Note**: test and record ad 2022.04.27
```shell
% make test-bench
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 128133166 93.97 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 909583207 13.41 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 784099310 15.24 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 289939296 41.60 ns/op 16 B/op 1 allocs/op
BenchmarkGookitSlogNegative
BenchmarkGookitSlogNegative-4 29131203 417.4 ns/op 125 B/op 4 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 9910075 1219 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 13966810 871.0 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 26743148 446.2 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 2658482 4481 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 8349562 1441 ns/op 165 B/op 6 allocs/op
PASS
ok command-line-arguments 146.669s
```
### beta 2022.04.17
> **Note**: test and record ad 2022.04.17
```shell
$ go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_loglibs_test.go
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 130808992 91.91 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 914445844 13.19 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 792539167 15.32 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 289393606 40.61 ns/op 16 B/op 1 allocs/op
BenchmarkGookitSlogNegative
BenchmarkGookitSlogNegative-4 29522170 405.3 ns/op 125 B/op 4 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 9113048 1283 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 14691699 797.0 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 27634338 424.5 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 2734669 4363 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 7740348 1563 ns/op 165 B/op 6 allocs/op
PASS
ok command-line-arguments 145.175s
```
## v0.2.1
> **Note**: test and record ad 2022.04.17
```shell
$ go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_loglibs_test.go
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 125500471 125.8 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 839046109 13.71 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 757766400 15.56 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkLogrusNegative-4 253178256 47.12 ns/op 16 B/op 1 allocs/op
BenchmarkGookitSlogNegative
BenchmarkGookitSlogNegative-4 30091606 401.9 ns/op 45 B/op 3 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 9761935 1216 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 13860344 837.1 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 27666529 447.8 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 2705653 4403 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 1836384 6882 ns/op 680 B/op 11 allocs/op
PASS
ok command-line-arguments 156.038s
```
## v0.2.0
> record ad 2022.02.26
```shell
$ go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_loglibs_test.go
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 139243226 86.39 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 1000000000 8.302 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkPhusLogNegative-4 1000000000 8.989 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusNegative
BenchmarkGookitSlogNegative-4 38300540 323.3 ns/op 221 B/op 5 allocs/op
BenchmarkZapPositive
BenchmarkZapPositive-4 14453001 828.1 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 28671724 420.9 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 45619569 261.9 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 5092164 2366 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 3184557 3754 ns/op 856 B/op 13 allocs/op
PASS
ok command-line-arguments 135.460s
```
## v0.1.5
> record ad 2022.02.26
```shell
$ go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_loglibs_test.go
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkZapNegative
BenchmarkZapNegative-4 137676860 86.43 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogNegative
BenchmarkZeroLogNegative-4 1000000000 8.284 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogNegative
BenchmarkZapPositive-4 14250313 831.7 ns/op 192 B/op 1 allocs/op
BenchmarkZeroLogPositive
BenchmarkZeroLogPositive-4 28183436 426.0 ns/op 0 B/op 0 allocs/op
BenchmarkPhusLogPositive
BenchmarkPhusLogPositive-4 44034984 258.7 ns/op 0 B/op 0 allocs/op
BenchmarkLogrusPositive
BenchmarkLogrusPositive-4 5005593 2421 ns/op 608 B/op 17 allocs/op
BenchmarkGookitSlogPositive
BenchmarkGookitSlogPositive-4 1714084 7029 ns/op 4480 B/op 45 allocs/op
PASS
ok command-line-arguments 138.199s
```
================================================
FILE: _example/bench_loglibs_test.go
================================================
package main
import (
"io"
goslog "log/slog"
"testing"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
phuslu "github.com/phuslu/log"
"github.com/rs/zerolog"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// In _example/ dir, run:
//
// go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_loglibs_test.go
//
// code refer:
//
// https://github.com/phuslu/log
var msg = "The quick brown fox jumps over the lazy dog"
func BenchmarkGoSlogNegative(b *testing.B) {
logger := goslog.New(goslog.NewTextHandler(io.Discard, &goslog.HandlerOptions{
Level: goslog.LevelInfo,
}))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info(msg, goslog.String("rate", "15"), goslog.Int("low", 16), goslog.Float64("high", 123.2))
}
}
func BenchmarkZapNegative(b *testing.B) {
logger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(io.Discard),
zapcore.InfoLevel,
))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info(msg, zap.String("rate", "15"), zap.Int("low", 16), zap.Float32("high", 123.2))
}
}
func BenchmarkZapSugarNegative(b *testing.B) {
logger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(io.Discard),
// zapcore.AddSync(os.Stdout),
zapcore.InfoLevel,
)).Sugar()
// logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
// return
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
}
func BenchmarkZeroLogNegative(b *testing.B) {
logger := zerolog.New(io.Discard).With().Timestamp().Logger().Level(zerolog.InfoLevel)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg)
}
}
func BenchmarkPhusLogNegative(b *testing.B) {
logger := phuslu.Logger{Level: phuslu.InfoLevel, Writer: phuslu.IOWriter{Writer: io.Discard}}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg)
}
}
// "github.com/sirupsen/logrus"
func BenchmarkLogrusNegative(b *testing.B) {
logger := logrus.New()
logger.Out = io.Discard
logger.Level = logrus.InfoLevel
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
}
func BenchmarkGookitSlogNegative(b *testing.B) {
logger := slog.NewWithHandlers(
handler.NewIOWriter(io.Discard, []slog.Level{slog.InfoLevel}),
// handler.NewIOWriter(os.Stdout, []slog.Level{slog.InfoLevel}),
)
logger.ReportCaller = false
// logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
// return
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
}
func BenchmarkZapPositive(b *testing.B) {
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(io.Discard),
zapcore.InfoLevel,
))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info(msg, zap.String("rate", "15"), zap.Int("low", 16), zap.Float32("high", 123.2))
}
}
func BenchmarkZapSugarPositive(b *testing.B) {
logger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(io.Discard),
zapcore.InfoLevel,
)).Sugar()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info(msg, zap.String("rate", "15"), zap.Int("low", 16), zap.Float32("high", 123.2))
}
}
func BenchmarkZeroLogPositive(b *testing.B) {
logger := zerolog.New(io.Discard).With().Timestamp().Logger().Level(zerolog.InfoLevel)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg)
}
}
func BenchmarkPhusLogPositive(b *testing.B) {
logger := phuslu.Logger{Level: phuslu.InfoLevel, Writer: phuslu.IOWriter{Writer: io.Discard}}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg)
}
}
func BenchmarkLogrusPositive(b *testing.B) {
logger := logrus.New()
logger.Out = io.Discard
logger.Level = logrus.InfoLevel
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
}
func BenchmarkGookitSlogPositive(b *testing.B) {
logger := slog.NewWithHandlers(
handler.NewIOWriter(io.Discard, []slog.Level{slog.InfoLevel}),
)
logger.ReportCaller = false
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
}
================================================
FILE: _example/demos/demo1.go
================================================
package main
import (
log "github.com/gookit/slog"
)
const simplestTemplate = "[{{datetime}}] [{{level}}] {{message}} {{data}} {{extra}}"
func init() {
log.GetFormatter().(*log.TextFormatter).SetTemplate(simplestTemplate)
log.SetLogLevel(log.ErrorLevel)
log.Errorf("Test")
}
func main() {
}
================================================
FILE: _example/demos/simple.go
================================================
package main
import "github.com/gookit/slog"
// profile run:
//
// go build -gcflags '-m -l' simple.go
func main() {
// stackIt()
// _ = stackIt2()
slogTest()
}
//go:noinline
func stackIt() int {
y := 2
return y * 2
}
//go:noinline
func stackIt2() *int {
y := 2
res := y * 2
return &res
}
func slogTest() {
var msg = "The quick brown fox jumps over the lazy dog"
slog.Info("rate", "15", "low", 16, "high", 123.2, msg)
// slog.WithFields(slog.M{
// "omg": true,
// "number": 122,
// }).Infof("slog %s", "message message")
}
================================================
FILE: _example/demos/slog_all_level.go
================================================
package main
import (
"errors"
"github.com/gookit/goutil/errorx"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
// run: go run ./_example/slog_all_level.go
func main() {
l := slog.NewWithConfig(func(l *slog.Logger) {
l.DoNothingOnPanicFatal()
})
l.AddHandler(handler.NewConsoleHandler(slog.AllLevels))
printAllLevel(l, "this is a", "log", "message")
}
func printAllLevel(l *slog.Logger, args ...any) {
l.Debug(args...)
l.Info(args...)
l.Warn(args...)
l.Error(args...)
l.Print(args...)
l.Fatal(args...)
l.Panic(args...)
l.Trace(args...)
l.Notice(args...)
l.ErrorT(errors.New("a error object"))
l.ErrorT(errorx.New("error with stack info"))
}
================================================
FILE: _example/diff-with-zap-zerolog.md
================================================
# diff with zap, zerolog
是的,zap 非常快速。
但是有一点问题:
- 配置起来稍显复杂
- 没有内置切割文件处理和文件清理
- 自定义扩展性不是很好
Yes, zap is very fast.
But there is a little problem:
- Slightly complicated to configure
- No built-in cutting file handling, file cleanup
- Custom extensibility is not very good
================================================
FILE: _example/go.mod
================================================
module slog_example
go 1.19
require (
github.com/golang/glog v1.2.5
github.com/gookit/goutil v0.7.4
github.com/gookit/slog v0.6.0
github.com/phuslu/log v1.0.119
github.com/rs/zerolog v1.34.0
github.com/sirupsen/logrus v1.9.3
github.com/syyongx/llog v0.0.0-20200222114215-e8f9f86ac0a3
go.uber.org/zap v1.27.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (
github.com/gookit/color v1.6.0 // indirect
github.com/gookit/gsr v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
)
replace github.com/gookit/slog => ../
================================================
FILE: _example/handler/grouped.go
================================================
package handler
import "github.com/gookit/slog"
/********************************************************************************
* Grouped Handler
********************************************************************************/
// GroupedHandler definition
type GroupedHandler struct {
handlers []slog.Handler
// Levels for log message
Levels []slog.Level
// IgnoreErr on handling messages
IgnoreErr bool
}
// NewGroupedHandler create new GroupedHandler
func NewGroupedHandler(handlers []slog.Handler) *GroupedHandler {
return &GroupedHandler{
handlers: handlers,
}
}
// IsHandling Check if the current level can be handling
func (h *GroupedHandler) IsHandling(level slog.Level) bool {
for _, l := range h.Levels {
if l == level {
return true
}
}
return false
}
// Handle log record
func (h *GroupedHandler) Handle(record *slog.Record) (err error) {
for _, handler := range h.handlers {
err = handler.Handle(record)
if !h.IgnoreErr && err != nil {
return err
}
}
return
}
// Close log handlers
func (h *GroupedHandler) Close() error {
for _, handler := range h.handlers {
err := handler.Close()
if !h.IgnoreErr && err != nil {
return err
}
}
return nil
}
// Flush log records
func (h *GroupedHandler) Flush() error {
for _, handler := range h.handlers {
err := handler.Flush()
if !h.IgnoreErr && err != nil {
return err
}
}
return nil
}
================================================
FILE: _example/issue100/issue100_test.go
================================================
package main
import (
"fmt"
"testing"
"time"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
type Obj struct {
a int
b int64
c string
d bool
}
var (
str1 = "str1"
str2 = "str222222222222"
int1 = 1
int2 = 2
obj = Obj{1, 2, "3", true}
)
func TestZapSugar(t *testing.T) {
w := zapcore.AddSync(&lumberjack.Logger{
Filename: "./zap-sugar.log",
MaxSize: 500, // megabytes
MaxBackups: 3,
MaxAge: 28, // days
})
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
w,
zap.InfoLevel,
)
logger := zap.New(core)
sugar := logger.Sugar()
sugar.Info("message is msg")
count := 100000
start := time.Now().UnixNano()
for n := count; n > 0; n-- {
sugar.Info("message is msg")
}
end := time.Now().UnixNano()
fmt.Printf("\n zap sugar no format\n total cost %d ns\n avg cost %d ns \n count %d \n", end-start, (end-start)/int64(count), count)
start = time.Now().UnixNano()
for n := count; n > 0; n-- {
sugar.Infof("message is %d %d %s %s %#v", int1, int2, str1, str2, obj)
}
end = time.Now().UnixNano()
fmt.Printf("\n zap sugar format\n total cost %d ns\n avg cost %d ns \n count %d \n", end-start, (end-start)/int64(count), count)
sugar.Sync()
}
func TestZapLog(t *testing.T) {
w := zapcore.AddSync(&lumberjack.Logger{
Filename: "./zap.log",
MaxSize: 500, // megabytes
MaxBackups: 3,
MaxAge: 28, // days
})
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
w,
zap.InfoLevel,
)
logger := zap.New(core)
count := 100000
start := time.Now().UnixNano()
for n := count; n > 0; n-- {
logger.Info("message is msg")
}
end := time.Now().UnixNano()
fmt.Printf("\n zap no format\n total cost %d ns\n avg cost %d ns \n count %d \n", end-start, (end-start)/int64(count), count)
start = time.Now().UnixNano()
for n := count; n > 0; n-- {
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.Int("int1", int1),
zap.Int("int2", int2),
zap.String("str", str1),
zap.String("str2", str2),
zap.Any("backoff", obj),
)
}
end = time.Now().UnixNano()
fmt.Printf("\n zap format\n total cost %d ns\n avg cost %d ns \n count %d \n", end-start, (end-start)/int64(count), count)
logger.Sync()
}
func TestSlog(t *testing.T) {
h1, err := handler.NewEmptyConfig(
handler.WithLogfile("./slog-info.log"), // 路径
handler.WithRotateTime(handler.EveryHour), // 日志分割间隔
handler.WithLogLevels(slog.AllLevels), // 日志level
handler.WithBuffSize(4*1024*1024), // buffer大小
handler.WithCompress(true), // 是否压缩旧日志 zip
handler.WithBackupNum(24*3), // 保留旧日志数量
handler.WithBuffMode(handler.BuffModeBite),
// handler.WithRenameFunc(), //RenameFunc build filename for rotate file
).CreateHandler()
if err != nil {
fmt.Printf("Create slog handler err: %#v", err)
return
}
f := slog.AsTextFormatter(h1.Formatter())
myTplt := "[{{datetime}}] [{{level}}] [{{caller}}] {{message}}\n"
f.SetTemplate(myTplt)
logs := slog.NewWithHandlers(h1)
count := 100000
start := time.Now().UnixNano()
for i := 0; i < count; i++ {
logs.Info("message is msg")
}
end := time.Now().UnixNano()
fmt.Printf("\n slog no format \n total cost %d ns\n avg cost %d ns \n count %d \n", end-start, (end-start)/int64(count), count)
start = time.Now().UnixNano()
for n := count; n > 0; n-- {
logs.Infof("message is %d %d %s %s %#v", int1, int2, str1, str2, obj)
}
end = time.Now().UnixNano()
fmt.Printf("\n slog format \n total cost %d ns\n avg cost %d ns \n count %d \n", end-start, (end-start)/int64(count), count)
logs.MustClose()
}
================================================
FILE: _example/issue111/main.go
================================================
package main
import (
"fmt"
"os"
"time"
"github.com/gookit/goutil/syncs"
"github.com/gookit/goutil/timex"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
"github.com/gookit/slog/rotatefile"
)
const pth = "./logs/main.log"
func main() {
log := slog.New()
h, err := handler.NewTimeRotateFileHandler(
pth,
rotatefile.RotateTime(30),
handler.WithBuffSize(0),
handler.WithBackupNum(5),
handler.WithCompress(true),
func(c *handler.Config) {
c.DebugMode = true
},
)
if err != nil {
panic(err)
}
log.AddHandler(h)
fmt.Println("Start...(can be stop by CTRL+C)", timex.NowDate())
go func() {
for {
select {
case <-time.After(time.Second):
log.Info("Log " + time.Now().String())
}
}
}()
syncs.WaitCloseSignals(func(sig os.Signal) {
fmt.Println("\nGot signal:", sig)
fmt.Println("Close logger ...")
log.MustClose()
})
fmt.Println("Exited at", timex.NowDate())
}
================================================
FILE: _example/issue137/main.go
================================================
package main
import (
"fmt"
"path"
"time"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
"github.com/gookit/slog/rotatefile"
)
type GLogConfig137 struct {
Level string `yaml:"Level"`
Pattern string `yaml:"Pattern"`
TimeField string `yaml:"TimeField"`
TimeFormat string `yaml:"TimeFormat"`
Template string `yaml:"Template"`
RotateTimeFormat string `yaml:"RotateTimeFormat"`
}
type LogRotateConfig137 struct {
Filepath string `yaml:"filepath"`
RotateMode rotatefile.RotateMode `yaml:"rotate_mode"`
RotateTime rotatefile.RotateTime `yaml:"rotate_time"`
MaxSize uint64 `yaml:"max_size"`
BackupNum uint `yaml:"backup_num"`
BackupTime uint `yaml:"backup_time"`
Compress bool `yaml:"compress"`
TimeFormat string `yaml:"time_format"`
BuffSize int `yaml:"buff_size"`
BuffMode string `yaml:"buff_mode"`
}
type LogConfig137 struct {
GLogConfig GLogConfig137 `yaml:"GLogConfig"`
LogRotate LogRotateConfig137 `yaml:"LogRotate"`
ErrorLogRotate LogRotateConfig137 `yaml:"ErrorLogRotate"`
}
func main() {
slog.DebugMode = true
logConfig := LogConfig137{
GLogConfig: GLogConfig137{
Level: "debug",
Pattern: "development",
TimeField: "time",
TimeFormat: "2006-01-02 15:04:05.000",
Template: "{{datetime}}\t{{level}}\t{{channel}}\t[{{caller}}]\t{{message}}\t{{data}}\t{{extra}}\n",
RotateTimeFormat: "20060102",
},
LogRotate: LogRotateConfig137{
Filepath: "testdata/info137c2.log",
RotateMode: 0,
RotateTime: 86400,
MaxSize: 512,
BackupNum: 3,
BackupTime: 72,
Compress: true,
TimeFormat: "20060102",
BuffSize: 512,
BuffMode: "line",
},
ErrorLogRotate: LogRotateConfig137{
Filepath: "testdata/err137c2.log",
RotateMode: 0,
RotateTime: 86400,
MaxSize: 512,
BackupNum: 3,
BackupTime: 72,
Compress: true,
TimeFormat: "20060102",
BuffSize: 512,
BuffMode: "line",
},
}
tpl := logConfig.GLogConfig.Template
// slog.DefaultChannelName = "gookit"
slog.DefaultTimeFormat = logConfig.GLogConfig.TimeFormat
slog.Configure(func(l *slog.SugaredLogger) {
l.Level = slog.TraceLevel
l.DoNothingOnPanicFatal()
l.ChannelName = "gookit"
})
slog.GetFormatter().(*slog.TextFormatter).SetTemplate(tpl)
slog.GetFormatter().(*slog.TextFormatter).TimeFormat = slog.DefaultTimeFormat
rotatefile.DefaultFilenameFn = func(filepath string, rotateNum uint) string {
suffix := time.Now().Format(logConfig.GLogConfig.RotateTimeFormat)
// eg: /tmp/error.log => /tmp/error_20250302_01.log
// 将文件名扩展名取出来, 然后在扩展名中间加入下划线+日期+下划线+序号+扩展名的形式
ext := path.Ext(filepath)
filename := filepath[:len(filepath)-len(ext)]
return filename + fmt.Sprintf("_%s_%02d", suffix, rotateNum) + ext
}
h1 := handler.MustRotateFile(logConfig.ErrorLogRotate.Filepath,
logConfig.ErrorLogRotate.RotateTime,
// handler.WithFilePerm(os.ModeAppend|os.ModePerm),
handler.WithLevelMode(slog.LevelModeList),
handler.WithLogLevels(slog.DangerLevels),
handler.WithMaxSize(logConfig.ErrorLogRotate.MaxSize),
handler.WithBackupNum(logConfig.ErrorLogRotate.BackupNum),
handler.WithBackupTime(logConfig.ErrorLogRotate.BackupTime),
handler.WithCompress(logConfig.ErrorLogRotate.Compress),
handler.WithBuffSize(logConfig.ErrorLogRotate.BuffSize),
handler.WithBuffMode(logConfig.ErrorLogRotate.BuffMode),
handler.WithRotateMode(logConfig.ErrorLogRotate.RotateMode),
)
h1.Formatter().(*slog.TextFormatter).SetTemplate(tpl)
h2 := handler.MustRotateFile(logConfig.LogRotate.Filepath,
logConfig.LogRotate.RotateTime,
// handler.WithFilePerm(os.ModeAppend|os.ModePerm),
handler.WithLevelMode(slog.LevelModeList),
handler.WithLogLevels(slog.AllLevels),
handler.WithMaxSize(logConfig.LogRotate.MaxSize),
handler.WithBackupNum(logConfig.LogRotate.BackupNum),
handler.WithBackupTime(logConfig.LogRotate.BackupTime),
handler.WithCompress(logConfig.LogRotate.Compress),
handler.WithBuffSize(logConfig.LogRotate.BuffSize),
handler.WithBuffMode(logConfig.LogRotate.BuffMode),
handler.WithRotateMode(logConfig.LogRotate.RotateMode),
)
h2.Formatter().(*slog.TextFormatter).SetTemplate(tpl)
slog.PushHandlers(h1, h2)
// add logs
for i := 0; i < 20; i++ {
slog.Infof("hi, this is a example information ... message text. log index=%d", i)
slog.WithValue("test137", "some value").Warn("测试滚动多个文件,同时设置了清理日志文件")
}
slog.MustClose()
time.Sleep(time.Second * 2)
}
================================================
FILE: _example/pprof/main.go
================================================
package main
import (
"fmt"
"io"
"log"
"os"
"runtime/pprof"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
// run serve:
//
// go run ./_examples/pprof
//
// see prof on cli:
//
// go tool pprof pprof/cpu_prof_data.out
//
// see prof on web:
//
// go tool pprof -http=:8080 pprof/cpu_prof_data.out
func main() {
logger := slog.NewWithHandlers(
handler.NewIOWriter(io.Discard, slog.NormalLevels),
)
times := 10000
fmt.Println("start profile, run times:", times)
cpuProfile := "cpu_prof_data.out"
f, err := os.Create(cpuProfile)
if err != nil {
log.Fatal(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
log.Fatal(err)
}
defer pprof.StopCPUProfile()
var msg = "The quick brown fox jumps over the lazy dog"
for i := 0; i < times; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
fmt.Println("see prof on web:\n go tool pprof -http=:8080", cpuProfile)
}
================================================
FILE: _example/refer/main.go
================================================
package main
import (
"flag"
"log"
"time"
"github.com/golang/glog"
"github.com/gookit/slog"
"github.com/sirupsen/logrus"
"github.com/syyongx/llog"
"go.uber.org/zap"
"github.com/rs/zerolog"
zlog "github.com/rs/zerolog/log"
)
func main() {
// for glog
flag.Parse()
// -- log
log.Println("raw log message")
// -- glog
glog.Infof("glog %s", "message message")
// -- llog
llog.NewLogger("llog test").Info("llog message message")
// -- slog
slog.Debug("slog message message")
slog.WithFields(slog.M{
"omg": true,
"number": 122,
}).Infof("slog %s", "message message")
// -- logrus
logrus.Debug("logrus message message")
logrus.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
// -- zerolog
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zlog.Debug().
Str("Scale", "833 cents").
Float64("Interval", 833.09).
Msg("zerolog message")
zlog.Print("zerolog hello")
// slog.Infof("log %s", "message")
url := "/path/to/some"
// -- zap
logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
// Structured context as loosely typed key-value pairs.
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("zap log. Failed to fetch URL: %s", url)
}
================================================
FILE: benchmark2_test.go
================================================
package slog
import (
"fmt"
"io"
"testing"
"github.com/gookit/goutil/dump"
)
func TestLogger_newRecord_AllocTimes(_ *testing.T) {
l := Std()
l.Output = io.Discard
defer l.Reset()
// output: 0 times
fmt.Println("Alloc Times:", int(testing.AllocsPerRun(100, func() {
// logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
r := l.newRecord()
// do something...
l.releaseRecord(r)
})))
}
func Test_AllocTimes_formatArgsWithSpaces_oneElem(_ *testing.T) {
// string Alloc Times: 0
fmt.Println("string Alloc Times:", int(testing.AllocsPerRun(10, func() {
// logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
formatArgsWithSpaces([]any{"msg"})
})))
// int Alloc Times: 1
fmt.Println("int Alloc Times:", int(testing.AllocsPerRun(10, func() {
formatArgsWithSpaces([]any{2343})
})))
// float Alloc Times: 2
fmt.Println("float Alloc Times:", int(testing.AllocsPerRun(10, func() {
formatArgsWithSpaces([]any{123.2})
})))
}
func Test_AllocTimes_formatArgsWithSpaces_manyElem(_ *testing.T) {
// Alloc Times: 1
// TIP:
// `float` will alloc 2 times memory
// `int <0`, `int > 100` will alloc 1 times memory
fmt.Println("Alloc Times:", int(testing.AllocsPerRun(50, func() {
formatArgsWithSpaces([]any{
"rate", -23, true, 106, "high", 123.2,
})
})))
}
func Test_AllocTimes_stringsPool(_ *testing.T) {
l := Std()
l.Output = io.Discard
l.LowerLevelName = true
defer l.Reset()
var ln, cp int
// output: 0 times
fmt.Println("Alloc Times:", int(testing.AllocsPerRun(100, func() {
// logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
// oldnew := stringsPool.Get().([]string)
// defer stringsPool.Put(oldnew)
oldnew := make([]string, 0, len(map[string]string{"a": "b"})*2+1)
oldnew = append(oldnew, "a")
oldnew = append(oldnew, "b")
oldnew = append(oldnew, "c")
// oldnew = append(oldnew, "d")
ln = len(oldnew)
cp = cap(oldnew)
})))
dump.P(ln, cp)
}
func TestLogger_Info_oneElem_AllocTimes(_ *testing.T) {
l := Std()
// l.Output = io.Discard
l.ReportCaller = false
l.LowerLevelName = true
// 启用 color 会导致多次(10次左右)内存分配
l.Formatter.(*TextFormatter).EnableColor = false
defer l.Reset()
// output: 2 times
fmt.Println("Alloc Times:", int(testing.AllocsPerRun(5, func() {
// l.Info("rate", "15", "low", 16, "high", 123.2, "msg")
l.Info("msg")
})))
}
func TestLogger_Info_moreElem_AllocTimes(_ *testing.T) {
l := NewStdLogger()
// l.Output = io.Discard
l.ReportCaller = false
l.LowerLevelName = true
// 启用 color 会导致多次(10次左右)内存分配
l.Formatter.(*TextFormatter).EnableColor = false
defer l.Reset()
// output: 5 times
fmt.Println("Alloc Times:", int(testing.AllocsPerRun(5, func() {
l.Info("rate", "15", "low", 16, "high", 123.2, "msg")
})))
// output: 5 times
fmt.Println("Alloc Times:", int(testing.AllocsPerRun(5, func() {
l.Info("rate", "15", "low", 16, "high")
// l.Info("msg")
})))
}
================================================
FILE: benchmark_test.go
================================================
package slog_test
import (
"io"
"testing"
"github.com/gookit/goutil/dump"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
// go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_test.go
//
// code refer:
//
// https://github.com/phuslu/log
var msg = "The quick brown fox jumps over the lazy dog"
func BenchmarkGookitSlogNegative(b *testing.B) {
logger := slog.NewWithHandlers(
handler.NewIOWriter(io.Discard, []slog.Level{slog.ErrorLevel}),
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
}
func TestLogger_Info_Negative(t *testing.T) {
logger := slog.NewWithHandlers(
handler.NewIOWriter(io.Discard, []slog.Level{slog.ErrorLevel}),
)
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
func BenchmarkGookitSlogPositive(b *testing.B) {
logger := slog.NewWithHandlers(
handler.NewIOWriter(io.Discard, slog.NormalLevels),
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
}
func BenchmarkTextFormatter_Format(b *testing.B) {
r := newLogRecord("TEST_LOG_MESSAGE")
f := slog.NewTextFormatter()
// 1284 ns/op 456 B/op 11 allocs/op
// On use DefaultTemplate
// 304.4 ns/op 200 B/op 2 allocs/op
// f.SetTemplate("{{datetime}} {{message}}")
// 271.3 ns/op 200 B/op 2 allocs/op
// f.SetTemplate("{{datetime}}")
// f.SetTemplate("{{message}}")
dump.P(f.Template())
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := f.Format(r)
if err != nil {
panic(err)
}
}
}
func TestLogger_Info_Positive(t *testing.T) {
logger := slog.NewWithHandlers(
handler.NewIOWriter(io.Discard, slog.NormalLevels),
)
logger.Info("rate", "15", "low", 16, "high", 123.2, msg)
}
================================================
FILE: bufwrite/bufio_writer.go
================================================
// Package bufwrite provides buffered io.Writer with sync and close methods.
package bufwrite
import (
"bufio"
"io"
)
// BufIOWriter wrap the bufio.Writer, implements the Sync() Close() methods
type BufIOWriter struct {
bufio.Writer
// backup the bufio.Writer.wr
writer io.Writer
}
// NewBufIOWriterSize instance with size
func NewBufIOWriterSize(w io.Writer, size int) *BufIOWriter {
return &BufIOWriter{
writer: w,
Writer: *bufio.NewWriterSize(w, size),
}
}
// NewBufIOWriter instance
func NewBufIOWriter(w io.Writer) *BufIOWriter {
return NewBufIOWriterSize(w, defaultBufSize)
}
// Close implements the io.Closer
func (w *BufIOWriter) Close() error {
if err := w.Flush(); err != nil {
return err
}
// is closer
if c, ok := w.writer.(io.Closer); ok {
return c.Close()
}
return nil
}
// Sync implements the Syncer
func (w *BufIOWriter) Sync() error {
return w.Flush()
}
================================================
FILE: bufwrite/bufwrite_test.go
================================================
package bufwrite_test
import (
"bytes"
"testing"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/slog/bufwrite"
)
func TestNewBufIOWriter_WriteString(t *testing.T) {
w := new(bytes.Buffer)
bw := bufwrite.NewBufIOWriterSize(w, 12)
_, err := bw.WriteString("hello, ")
assert.NoErr(t, err)
assert.Eq(t, 0, w.Len())
_, err = bw.WriteString("worlds. oh")
assert.NoErr(t, err)
assert.Eq(t, "hello, world", w.String()) // different the LineWriter
assert.NoErr(t, bw.Close())
assert.Eq(t, "hello, worlds. oh", w.String())
}
type closeWriter struct {
errOnWrite bool
errOnClose bool
writeNum int
}
func (w *closeWriter) Close() error {
if w.errOnClose {
return errorx.Raw("close error")
}
return nil
}
func (w *closeWriter) Write(p []byte) (n int, err error) {
if w.errOnWrite {
return w.writeNum, errorx.Raw("write error")
}
if w.writeNum > 0 {
return w.writeNum, nil
}
return len(p), nil
}
func TestBufIOWriter_Close_error(t *testing.T) {
bw := bufwrite.NewBufIOWriterSize(&closeWriter{errOnWrite: true}, 24)
_, err := bw.WriteString("hi")
assert.NoErr(t, err)
// flush write error
err = bw.Close()
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
bw = bufwrite.NewBufIOWriterSize(&closeWriter{errOnClose: true}, 24)
// close error
err = bw.Close()
assert.Err(t, err)
assert.Eq(t, "close error", err.Error())
}
func TestBufIOWriter_Sync(t *testing.T) {
w := new(bytes.Buffer)
bw := bufwrite.NewBufIOWriter(w)
_, err := bw.WriteString("hello")
assert.NoErr(t, err)
assert.Eq(t, 0, w.Len())
assert.Eq(t, "", w.String())
assert.NoErr(t, bw.Sync())
assert.Eq(t, "hello", w.String())
}
func TestNewLineWriter(t *testing.T) {
w := new(bytes.Buffer)
bw := bufwrite.NewLineWriter(w)
assert.True(t, bw.Size() > 0)
assert.NoErr(t, bw.Flush())
_, err := bw.WriteString("hello")
assert.NoErr(t, err)
assert.Eq(t, "", w.String())
assert.NoErr(t, bw.Sync())
assert.Eq(t, "hello", w.String())
bw.Reset(w)
}
func TestLineWriter_Write_error(t *testing.T) {
w := &closeWriter{errOnWrite: true}
bw := bufwrite.NewLineWriterSize(w, 6)
t.Run("flush err on write", func(t *testing.T) {
w1 := &closeWriter{}
bw.Reset(w1)
n, err := bw.WriteString("hi") // write ok
assert.NoErr(t, err)
assert.Equal(t, 2, n)
// fire flush
w1.errOnWrite = true
_, err = bw.WriteString("hello, tom")
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
})
_, err := bw.WriteString("hello, tom")
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
// get old error
w.errOnWrite = false
_, err = bw.WriteString("hello, wo")
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
bw.Reset(w)
_, err = bw.WriteString("hello")
assert.NoErr(t, err)
}
func TestLineWriter_Flush_error(t *testing.T) {
t.Run("write ok but n < b.n", func(t *testing.T) {
w := &closeWriter{}
bw := bufwrite.NewLineWriterSize(w, 6)
_, err := bw.WriteString("hi!")
assert.NoErr(t, err)
// err: write n < b.n
w.writeNum = 1
err = bw.Flush()
assert.Err(t, err)
assert.Eq(t, "short write", err.Error())
})
t.Run("write err and n < b.n", func(t *testing.T) {
w := &closeWriter{}
bw := bufwrite.NewLineWriterSize(w, 6)
_, err := bw.WriteString("hi!")
assert.NoErr(t, err)
// err: write n < b.n
w.writeNum = 1
w.errOnWrite = true
err = bw.Flush()
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
})
w := &closeWriter{}
bw := bufwrite.NewLineWriterSize(w, 6)
_, err := bw.WriteString("hello")
assert.NoErr(t, err)
// error on flush
w.errOnWrite = true
err = bw.Flush()
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
// err: write n < b.n
w.writeNum = 2
err = bw.Flush()
assert.Err(t, err)
w.writeNum = 0
// get old error
w.errOnWrite = false
err = bw.Flush()
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
bw.Reset(w)
_, err = bw.WriteString("hello")
assert.NoErr(t, err)
}
func TestLineWriter_Close_error(t *testing.T) {
w := &closeWriter{}
bw := bufwrite.NewLineWriterSize(w, 8)
_, err := bw.WriteString("hello")
assert.NoErr(t, err)
// error on flush
w.errOnWrite = true
err = bw.Close()
assert.Err(t, err)
assert.Eq(t, "write error", err.Error())
w = &closeWriter{errOnClose: true}
bw = bufwrite.NewLineWriterSize(w, 8)
err = bw.Close()
assert.Err(t, err)
assert.Eq(t, "close error", err.Error())
}
func TestNewLineWriterSize(t *testing.T) {
w := new(bytes.Buffer)
bw := bufwrite.NewLineWriterSize(w, 12)
_, err := bw.WriteString("hello, ")
assert.NoErr(t, err)
assert.Eq(t, 0, w.Len())
assert.True(t, bw.Size() > 0)
_, err = bw.WriteString("worlds. oh")
assert.NoErr(t, err)
assert.Eq(t, "hello, worlds. oh", w.String()) // different the BufIOWriter
_, err = bw.WriteString("...")
assert.NoErr(t, err)
assert.NoErr(t, bw.Close())
assert.Eq(t, "hello, worlds. oh...", w.String())
w.Reset()
bw = bufwrite.NewLineWriterSize(bw, 8)
assert.Eq(t, 12, bw.Size())
bw = bufwrite.NewLineWriterSize(w, -12)
assert.True(t, bw.Size() > 12)
}
================================================
FILE: bufwrite/line_writer.go
================================================
package bufwrite
import (
"io"
)
const (
defaultBufSize = 1024 * 8
)
// LineWriter implements buffering for an io.Writer object.
// If an error occurs writing to a LineWriter, no more data will be
// accepted and all subsequent writes, and Flush, will return the error.
// After all data has been written, the client should call the
// Flush method to guarantee all data has been forwarded to
// the underlying io.Writer.
//
// from bufio.Writer.
//
// Change:
//
// always keep write full line. more difference please see Write
type LineWriter struct {
err error
buf []byte
n int
wr io.Writer
}
// NewLineWriterSize returns a new LineWriter whose buffer has at least the specified
// size. If the argument io.Writer is already a LineWriter with large enough
// size, it returns the underlying LineWriter.
func NewLineWriterSize(w io.Writer, size int) *LineWriter {
// Is it already a LineWriter?
b, ok := w.(*LineWriter)
if ok && len(b.buf) >= size {
return b
}
if size <= 0 {
size = defaultBufSize
}
return &LineWriter{
buf: make([]byte, size),
wr: w,
}
}
// NewLineWriter returns a new LineWriter whose buffer has the default size.
func NewLineWriter(w io.Writer) *LineWriter {
return NewLineWriterSize(w, defaultBufSize)
}
// Size returns the size of the underlying buffer in bytes.
func (b *LineWriter) Size() int { return len(b.buf) }
// Reset discards any unflushed buffered data, clears any error, and
// resets b to write its output to w.
func (b *LineWriter) Reset(w io.Writer) {
b.n = 0
b.wr = w
b.err = nil
b.buf = b.buf[:0]
}
// Close implements the io.Closer
func (b *LineWriter) Close() error {
if err := b.Flush(); err != nil {
return err
}
// is closer
if c, ok := b.wr.(io.Closer); ok {
return c.Close()
}
return nil
}
// Sync implements the Syncer
func (b *LineWriter) Sync() error {
return b.Flush()
}
// Flush writes any buffered data to the underlying io.Writer.
//
// TIP: please add lock before calling the method.
func (b *LineWriter) Flush() error {
if b.err != nil {
return b.err
}
if b.n == 0 {
return nil
}
n, err := b.wr.Write(b.buf[0:b.n])
if n < b.n && err == nil {
err = io.ErrShortWrite
}
if err != nil {
if n > 0 && n < b.n {
copy(b.buf[0:b.n-n], b.buf[n:b.n])
}
b.n -= n
b.err = err
return err
}
b.n = 0
return nil
}
// Available returns how many bytes are unused in the buffer.
func (b *LineWriter) Available() int { return len(b.buf) - b.n }
// Buffered returns the number of bytes that have been written into the current buffer.
func (b *LineWriter) Buffered() int { return b.n }
// Write writes the contents of p into the buffer.
// It returns the number of bytes written.
// If nn < len(p), it also returns an error explaining
// why the writing is short.
func (b *LineWriter) Write(p []byte) (nn int, err error) {
// NOTE: 原来的 bufio.Writer#Write 会造成 p 写了一部分到 b.wr, 还有一部分在 b.buf,
// 如果现在外部工具从 b.wr 收集数据,会收集到一行无法解析的数据(例如每个p是一行json日志)
// for len(p) > b.Available() && b.err == nil {
// var n int
// if b.Buffered() == 0 {
// // Large write, empty buffer.
// // Write directly from p to avoid copy.
// n, b.err = b.wr.Write(p)
// } else {
// n = copy(b.buf[b.n:], p)
// b.n += n
// b.Flush()
// }
// nn += n
// p = p[n:]
// }
// UP: 改造一下逻辑,如果 len(p) > b.Available() 就将buf 和 p 都写入 b.wr
if len(p) > b.Available() && b.err == nil {
nn = b.Buffered()
if nn > 0 {
_ = b.Flush()
if b.err != nil {
return nn, b.err
}
}
var n int
n, b.err = b.wr.Write(p)
if b.err != nil {
return nn, b.err
}
nn += n
return nn, nil
}
if b.err != nil {
return nn, b.err
}
n := copy(b.buf[b.n:], p)
b.n += n
nn += n
return nn, nil
}
// WriteString to the writer
func (b *LineWriter) WriteString(s string) (int, error) {
return b.Write([]byte(s))
}
================================================
FILE: common.go
================================================
package slog
import (
"errors"
"strconv"
"strings"
"time"
"github.com/gookit/goutil/envutil"
"github.com/gookit/goutil/strutil"
"github.com/gookit/gsr"
)
// SLogger interface
type SLogger interface {
gsr.Logger
Log(level Level, v ...any)
Logf(level Level, format string, v ...any)
}
// LoggerFn func
type LoggerFn func(l *Logger)
//
// log level definitions
// region Log level
// Level type
type Level uint32
// String get level name
func (l Level) String() string { return LevelName(l) }
// Name get level name. eg: INFO, DEBUG ...
func (l Level) Name() string { return LevelName(l) }
// LowerName get lower level name. eg: info, debug ...
func (l Level) LowerName() string {
if n, ok := lowerLevelNames[l]; ok {
return n
}
return "unknown"
}
// ShouldHandling compare level, if current level <= l, it will be record.
func (l Level) ShouldHandling(curLevel Level) bool {
return curLevel <= l
}
// MarshalJSON implement the JSON Marshal interface [encoding/json.Marshaler]
func (l Level) MarshalJSON() ([]byte, error) {
return []byte(`"` + l.String() + `"`), nil
}
// UnmarshalJSON implement the JSON Unmarshal interface [encoding/json.Unmarshaler]
func (l *Level) UnmarshalJSON(data []byte) error {
s, err := strconv.Unquote(string(data))
if err != nil {
return err
}
*l, err = StringToLevel(s)
return err
}
// Levels level list
type Levels []Level
// Contains given level
func (ls Levels) Contains(level Level) bool {
for _, l := range ls {
if l == level {
return true
}
}
return false
}
// These are the different logging levels. You can set the logging level to log handler
const (
// PanicLevel level, the highest level of severity. will call panic() if the logging level <= PanicLevel.
PanicLevel Level = 100
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
// logging level <= FatalLevel.
FatalLevel Level = 200
// ErrorLevel level. Runtime errors. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel Level = 300
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel Level = 400
// NoticeLevel level Uncommon events
NoticeLevel Level = 500
// InfoLevel level. Examples: User logs in, SQL logs.
InfoLevel Level = 600
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel Level = 700
// TraceLevel level. Designates finer-grained informational events than the Debug.
TraceLevel Level = 800
)
//
// some common definitions
// region common types
// StringMap string map short name
type StringMap = map[string]string
// M short name of map[string]any
type M map[string]any
// String map to string
func (m M) String() string {
return mapToString(m)
}
// ClockFn func
type ClockFn func() time.Time
// Now implements the Clocker
func (fn ClockFn) Now() time.Time {
return fn()
}
// region CallerFlagMode
// CallerFlagMode Defines the Caller backtrace information mode.
type CallerFlagMode = uint8
// NOTICE: you must set `Logger.ReportCaller=true` for reporting caller.
// then config the Logger.CallerFlag by follow flags.
const (
// CallerFlagFnlFcn report short func name with filename and with line.
// eg: "logger_test.go:48,TestLogger_ReportCaller"
CallerFlagFnlFcn CallerFlagMode = iota
// CallerFlagFull full func name with filename and with line.
// eg: "github.com/gookit/slog_test.TestLogger_ReportCaller(),logger_test.go:48"
CallerFlagFull
// CallerFlagFunc full package with func name.
// eg: "github.com/gookit/slog_test.TestLogger_ReportCaller"
CallerFlagFunc
// CallerFlagFcLine full package with func name and with line.
// eg: "github.com/gookit/slog_test.TestLogger_ReportCaller:48"
CallerFlagFcLine
// CallerFlagPkg report full package name.
// eg: "github.com/gookit/slog_test"
CallerFlagPkg
// CallerFlagPkgFnl report full package name + filename + line.
// eg: "github.com/gookit/slog_test,logger_test.go:48"
CallerFlagPkgFnl
// CallerFlagFpLine report full filepath with line.
// eg: "/work/go/gookit/slog/logger_test.go:48"
CallerFlagFpLine
// CallerFlagFnLine report filename with line.
// eg: "logger_test.go:48"
CallerFlagFnLine
// CallerFlagFcName only report func name.
// eg: "TestLogger_ReportCaller"
CallerFlagFcName
)
var (
// FieldKeyData define the key name for Record.Data
FieldKeyData = "data"
// FieldKeyTime key name
FieldKeyTime = "time"
// FieldKeyDate key name
FieldKeyDate = "date"
// FieldKeyDatetime key name
FieldKeyDatetime = "datetime"
// FieldKeyTimestamp key name
FieldKeyTimestamp = "timestamp"
// FieldKeyCaller the field key name for report caller.
//
// For caller style please see CallerFlagFull, CallerFlagFunc and more.
//
// NOTICE: you must set `Logger.ReportCaller=true` for reporting caller
FieldKeyCaller = "caller"
// FieldKeyLevel name
FieldKeyLevel = "level"
// FieldKeyError Define the key when adding errors using WithError.
FieldKeyError = "error"
// FieldKeyExtra key name
FieldKeyExtra = "extra"
// FieldKeyChannel name
FieldKeyChannel = "channel"
// FieldKeyMessage name
FieldKeyMessage = "message"
)
// region Global variables
var (
// DefaultChannelName for log record
DefaultChannelName = "application"
// DefaultTimeFormat define
DefaultTimeFormat = "2006/01/02T15:04:05.000"
// DebugMode enable debug mode for logger. use for local development.
DebugMode = envutil.GetBool("OPEN_SLOG_DEBUG", false)
// DoNothingOnExit handle func. use for testing.
DoNothingOnExit = func(code int) {}
// DoNothingOnPanic handle func. use for testing.
DoNothingOnPanic = func(v any) {}
// DefaultPanicFn handle func
DefaultPanicFn = func(v any) { panic(v) }
// DefaultClockFn create func
DefaultClockFn = ClockFn(func() time.Time { return time.Now() })
)
var (
// PrintLevel for use Logger.Print / Printf / Println
PrintLevel = InfoLevel
// AllLevels exposing all logging levels
AllLevels = Levels{
PanicLevel,
FatalLevel,
ErrorLevel,
WarnLevel,
NoticeLevel,
InfoLevel,
DebugLevel,
TraceLevel,
}
// DangerLevels define the commonly danger log levels
DangerLevels = Levels{PanicLevel, FatalLevel, ErrorLevel, WarnLevel}
// NormalLevels define the commonly normal log levels
NormalLevels = Levels{InfoLevel, NoticeLevel, DebugLevel, TraceLevel}
// LevelNames all level mapping name
LevelNames = map[Level]string{
PanicLevel: "PANIC",
FatalLevel: "FATAL",
ErrorLevel: "ERROR",
WarnLevel: "WARNING",
NoticeLevel: "NOTICE",
InfoLevel: "INFO",
DebugLevel: "DEBUG",
TraceLevel: "TRACE",
}
// lower level name.
lowerLevelNames = buildLowerLevelName()
// empty time for reset record.
emptyTime = time.Time{}
)
// region Global functions
// LevelName match
func LevelName(l Level) string {
if n, ok := LevelNames[l]; ok {
return n
}
return "UNKNOWN"
}
// LevelByName convert name to level, fallback to InfoLevel if not match
func LevelByName(ln string) Level {
l, err := StringToLevel(ln)
if err != nil {
return InfoLevel
}
return l
}
// Name2Level convert name to level
func Name2Level(s string) (Level, error) { return StringToLevel(s) }
// StringToLevel parse and convert string value to Level
func StringToLevel(s string) (Level, error) {
switch strings.ToLower(s) {
case "panic":
return PanicLevel, nil
case "fatal":
return FatalLevel, nil
case "err", "error":
return ErrorLevel, nil
case "warn", "warning":
return WarnLevel, nil
case "note", "notice":
return NoticeLevel, nil
case "info", "": // make the zero value useful
return InfoLevel, nil
case "debug":
return DebugLevel, nil
case "trace":
return TraceLevel, nil
}
// is int value, try to parse as int
if strutil.IsInt(s) {
iVal := strutil.SafeInt(s)
return Level(iVal), nil
}
return 0, errors.New("slog: invalid log level name: " + s)
}
//
// exit handle logic
//
// global exit handler
var exitHandlers = make([]func(), 0)
func runExitHandlers() {
defer func() {
if err := recover(); err != nil {
printStderr("slog: run exit handler(global) recovered, error:", err)
}
}()
for _, handler := range exitHandlers {
handler()
}
}
// ExitHandlers get all global exitHandlers
func ExitHandlers() []func() {
return exitHandlers
}
// RegisterExitHandler register an exit-handler on global exitHandlers
func RegisterExitHandler(handler func()) {
exitHandlers = append(exitHandlers, handler)
}
// PrependExitHandler prepend register an exit-handler on global exitHandlers
func PrependExitHandler(handler func()) {
exitHandlers = append([]func(){handler}, exitHandlers...)
}
// ResetExitHandlers reset all exitHandlers
func ResetExitHandlers(applyToStd bool) {
exitHandlers = make([]func(), 0)
if applyToStd {
std.ResetExitHandlers()
}
}
================================================
FILE: common_test.go
================================================
package slog_test
import (
"bytes"
"fmt"
"testing"
"github.com/gookit/goutil/byteutil"
"github.com/gookit/goutil/dump"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/gsr"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
var (
testData1 = slog.M{"key0": "val0", "age": 23}
// testData2 = slog.M{"key0": "val0", "age": 23, "sub": slog.M{
// "subKey0": 345,
// }}
)
func TestDefine_basic(t *testing.T) {
assert.NotEmpty(t, slog.NoTimeFields)
assert.NotEmpty(t, slog.FieldKeyDate)
assert.NotEmpty(t, slog.FieldKeyTime)
assert.NotEmpty(t, slog.FieldKeyCaller)
assert.NotEmpty(t, slog.FieldKeyError)
}
func TestM_String(t *testing.T) {
m := slog.M{
"k0": 12,
"k1": "abc",
"k2": true,
"k3": 23.45,
"k4": []int{12, 23},
"k5": []string{"ab", "bc"},
"k6": map[string]any{
"k6-1": 23,
"k6-2": "def",
},
}
fmt.Println(m)
dump.P(m.String(), m)
assert.NotEmpty(t, m.String())
}
func TestLevelName_func(t *testing.T) {
for level, wantName := range slog.LevelNames {
realName := slog.LevelName(level)
assert.Eq(t, wantName, realName)
}
assert.Eq(t, "UNKNOWN", slog.LevelName(20))
// LevelByName
assert.Eq(t, slog.InfoLevel, slog.LevelByName("info"))
assert.Eq(t, slog.InfoLevel, slog.LevelByName("invalid"))
}
func TestName2Level(t *testing.T) {
for wantLevel, name := range slog.LevelNames {
level, err := slog.Name2Level(name)
assert.NoErr(t, err)
assert.Eq(t, wantLevel, level)
}
// special names
tests := map[slog.Level]string{
slog.WarnLevel: "warn",
slog.ErrorLevel: "err",
slog.InfoLevel: "",
}
for wantLevel, name := range tests {
level, err := slog.Name2Level(name)
assert.NoErr(t, err)
assert.Eq(t, wantLevel, level)
}
level, err := slog.Name2Level("unknown")
assert.Err(t, err)
assert.Eq(t, slog.Level(0), level)
level, err = slog.StringToLevel("300")
assert.NoErr(t, err)
assert.Eq(t, slog.ErrorLevel, level)
}
func TestLevel_methods(t *testing.T) {
t.Run("ShouldHandling", func(t *testing.T) {
assert.True(t, slog.InfoLevel.ShouldHandling(slog.ErrorLevel))
assert.False(t, slog.InfoLevel.ShouldHandling(slog.TraceLevel))
assert.True(t, slog.DebugLevel.ShouldHandling(slog.InfoLevel))
assert.False(t, slog.DebugLevel.ShouldHandling(slog.TraceLevel))
})
t.Run("Name", func(t *testing.T) {
assert.Eq(t, "INFO", slog.InfoLevel.Name())
assert.Eq(t, "INFO", slog.InfoLevel.String())
assert.Eq(t, "info", slog.InfoLevel.LowerName())
assert.Eq(t, "unknown", slog.Level(330).LowerName())
})
t.Run("encoding", func(t *testing.T) {
// MarshalJSON
bs, err := slog.InfoLevel.MarshalJSON()
assert.NoErr(t, err)
assert.Eq(t, `"INFO"`, string(bs))
// UnmarshalJSON
level := slog.Level(0)
assert.Eq(t, "UNKNOWN", level.Name())
err = level.UnmarshalJSON([]byte(`"warn"`))
assert.NoErr(t, err)
assert.Eq(t, "WARNING", level.Name())
assert.Err(t, level.UnmarshalJSON([]byte(`a`)))
})
}
func TestLevels_Contains(t *testing.T) {
assert.True(t, slog.DangerLevels.Contains(slog.ErrorLevel))
assert.False(t, slog.DangerLevels.Contains(slog.InfoLevel))
assert.True(t, slog.NormalLevels.Contains(slog.InfoLevel))
assert.False(t, slog.NormalLevels.Contains(slog.PanicLevel))
}
func newLogRecord(msg string) *slog.Record {
r := &slog.Record{
Channel: slog.DefaultChannelName,
Level: slog.InfoLevel,
Message: msg,
Time: slog.DefaultClockFn.Now(),
Data: map[string]any{
"data_key0": "value",
"username": "inhere",
},
Extra: map[string]any{
"source": "linux",
"extra_key0": "hello",
},
// Caller: goinfo.GetCallerInfo(),
}
r.Init(true)
return r
}
type closedBuffer struct {
bytes.Buffer
}
func newBuffer() *closedBuffer {
return &closedBuffer{}
}
func (w *closedBuffer) Close() error {
return nil
}
func (w *closedBuffer) StringReset() string {
s := w.Buffer.String()
w.Reset()
return s
}
//
// region test handler
//
type testHandler struct {
slog.FormatterWrapper
byteutil.Buffer
errOnHandle bool
errOnClose bool
errOnFlush bool
// hooks
beforeFormat func(r *slog.Record)
beforeWrite func(r *slog.Record)
callOnFlush func()
// NOTE: 如果设置为true,默认会让 error,fatal 等信息提前被reset丢弃掉.
// see Logger.writeRecord()
resetOnFlush bool
}
// built in test, will collect logs to buffer
func newTestHandler() *testHandler {
return &testHandler{}
}
func (h *testHandler) IsHandling(_ slog.Level) bool {
return true
}
func (h *testHandler) Close() error {
if h.errOnClose {
return errorx.Raw("close error")
}
h.Reset()
return nil
}
func (h *testHandler) Flush() error {
if h.errOnFlush {
return errorx.Raw("flush error")
}
if h.callOnFlush != nil {
h.callOnFlush()
}
if h.resetOnFlush {
h.Reset()
}
return nil
}
func (h *testHandler) Handle(r *slog.Record) error {
if h.errOnHandle {
return errorx.Raw("handle error")
}
if h.beforeFormat != nil {
h.beforeFormat(r)
}
bs, err := h.Format(r)
if err != nil {
return err
}
if h.beforeWrite != nil {
h.beforeWrite(r)
}
h.Write(bs)
return nil
}
//
// region test formatter
//
type testFormatter struct {
errOnFormat bool
}
func newTestFormatter(errOnFormat ...bool) *testFormatter {
return &testFormatter{
errOnFormat: len(errOnFormat) > 0 && errOnFormat[0],
}
}
func (f testFormatter) Format(r *slog.Record) ([]byte, error) {
if f.errOnFormat {
return nil, errorx.Raw("format error")
}
return []byte(r.Message), nil
}
//
// region test logger
//
func newLogger() *slog.Logger {
return slog.NewWithConfig(func(l *slog.Logger) {
l.ReportCaller = true
l.DoNothingOnPanicFatal()
})
}
// newTestLogger create a logger for test, will write logs to buffer
func newTestLogger() (*closedBuffer, *slog.Logger) {
l := slog.NewWithConfig(func(l *slog.Logger) {
l.DoNothingOnPanicFatal()
l.CallerFlag = slog.CallerFlagFull
})
w := newBuffer()
h := handler.NewIOWriter(w, slog.AllLevels)
// fmt.Print("Template:", h.TextFormatter().Template())
l.SetHandlers([]slog.Handler{h})
return w, l
}
func printAllLevelLogs(l gsr.Logger, args ...any) {
l.Debug(args...)
l.Info(args...)
l.Warn(args...)
l.Error(args...)
l.Print(args...)
l.Println(args...)
l.Fatal(args...)
l.Fatalln(args...)
l.Panic(args...)
l.Panicln(args...)
sl, ok := l.(*slog.Logger)
if ok {
sl.Trace(args...)
sl.Notice(args...)
sl.ErrorT(errorx.Raw("a error object"))
sl.ErrorT(errorx.New("error with stack info"))
}
}
func printfAllLevelLogs(l gsr.Logger, tpl string, args ...any) {
l.Printf(tpl, args...)
l.Debugf(tpl, args...)
l.Infof(tpl, args...)
l.Warnf(tpl, args...)
l.Errorf(tpl, args...)
l.Panicf(tpl, args...)
l.Fatalf(tpl, args...)
if sl, ok := l.(*slog.Logger); ok {
sl.Noticef(tpl, args...)
sl.Tracef(tpl, args...)
}
}
================================================
FILE: example_test.go
================================================
package slog_test
import (
"fmt"
"sync"
"time"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func Example_quickStart() {
slog.Info("info log message")
slog.Warn("warning log message")
slog.Infof("info log %s", "message")
slog.Debugf("debug %s", "message")
}
func Example_configSlog() {
slog.Configure(func(logger *slog.SugaredLogger) {
f := logger.Formatter.(*slog.TextFormatter)
f.EnableColor = true
})
slog.Trace("this is a simple log message")
slog.Debug("this is a simple log message")
slog.Info("this is a simple log message")
slog.Notice("this is a simple log message")
slog.Warn("this is a simple log message")
slog.Error("this is a simple log message")
slog.Fatal("this is a simple log message")
}
func Example_useJSONFormat() {
// use JSON formatter
slog.SetFormatter(slog.NewJSONFormatter())
slog.Info("info log message")
slog.Warn("warning log message")
slog.WithData(slog.M{
"key0": 134,
"key1": "abc",
}).Infof("info log %s", "message")
r := slog.WithFields(slog.M{
"category": "service",
"IP": "127.0.0.1",
})
r.Infof("info %s", "message")
r.Debugf("debug %s", "message")
}
func ExampleNew() {
mylog := slog.New()
levels := slog.AllLevels
mylog.AddHandler(handler.MustFileHandler("app.log", handler.WithLogLevels(levels)))
mylog.Info("info log message")
mylog.Warn("warning log message")
mylog.Infof("info log %s", "message")
}
func ExampleFlushDaemon() {
wg := sync.WaitGroup{}
wg.Add(1)
go slog.FlushDaemon(func() {
fmt.Println("flush daemon stopped")
slog.MustClose()
wg.Done()
})
go func() {
// mock app running
time.Sleep(time.Second * 2)
// stop daemon
fmt.Println("stop flush daemon")
slog.StopDaemon()
}()
// wait for stop
wg.Wait()
}
================================================
FILE: formatter.go
================================================
package slog
import "runtime"
//
// Formatter interface
//
// Formatter interface
type Formatter interface {
// Format you can format record and write result to record.Buffer
Format(record *Record) ([]byte, error)
}
// FormatterFunc wrapper definition
type FormatterFunc func(r *Record) ([]byte, error)
// Format a log record
func (fn FormatterFunc) Format(r *Record) ([]byte, error) {
return fn(r)
}
// Formattable interface
type Formattable interface {
// Formatter get the log formatter
Formatter() Formatter
// SetFormatter set the log formatter
SetFormatter(Formatter)
}
// FormattableTrait alias of FormatterWrapper
type FormattableTrait = FormatterWrapper
// FormatterWrapper use for format log record.
//
// Default will use the TextFormatter
type FormatterWrapper struct {
// if not set, default uses the TextFormatter
formatter Formatter
}
// Formatter get formatter. if not set, will return TextFormatter
func (f *FormatterWrapper) Formatter() Formatter {
if f.formatter == nil {
f.formatter = NewTextFormatter()
}
return f.formatter
}
// SetFormatter to handler
func (f *FormatterWrapper) SetFormatter(formatter Formatter) {
f.formatter = formatter
}
// Format log record to bytes
func (f *FormatterWrapper) Format(record *Record) ([]byte, error) {
return f.Formatter().Format(record)
}
// CallerFormatFn caller format func
type CallerFormatFn func(rf *runtime.Frame) (cs string)
// AsTextFormatter util func
func AsTextFormatter(f Formatter) *TextFormatter {
if tf, ok := f.(*TextFormatter); ok {
return tf
}
panic("slog: cannot cast input as *TextFormatter")
}
// AsJSONFormatter util func
func AsJSONFormatter(f Formatter) *JSONFormatter {
if jf, ok := f.(*JSONFormatter); ok {
return jf
}
panic("slog: cannot cast input as *JSONFormatter")
}
================================================
FILE: formatter_json.go
================================================
package slog
import (
"encoding/json"
"github.com/valyala/bytebufferpool"
)
var (
// DefaultFields default log export fields for json formatter.
DefaultFields = []string{
FieldKeyDatetime,
FieldKeyChannel,
FieldKeyLevel,
FieldKeyCaller,
FieldKeyMessage,
FieldKeyData,
FieldKeyExtra,
}
// NoTimeFields log export fields without time
NoTimeFields = []string{
FieldKeyChannel,
FieldKeyLevel,
FieldKeyMessage,
FieldKeyData,
FieldKeyExtra,
}
)
// JSONFormatter definition
type JSONFormatter struct {
// Fields set exported common log fields. default is DefaultFields
Fields []string
// Aliases for output fields. you can change the export field name.
//
// - item: `"field" : "output name"`
//
// eg: {"message": "msg"} export field will display "msg"
Aliases StringMap
// PrettyPrint will indent all JSON logs
PrettyPrint bool
// TimeFormat the time format layout. default is DefaultTimeFormat
TimeFormat string
// CallerFormatFunc the caller format layout. default is defined by CallerFlag
CallerFormatFunc CallerFormatFn
}
// NewJSONFormatter create new JSONFormatter
func NewJSONFormatter(fn ...func(f *JSONFormatter)) *JSONFormatter {
f := &JSONFormatter{
// Aliases: make(StringMap, 0),
Fields: DefaultFields,
TimeFormat: DefaultTimeFormat,
}
if len(fn) > 0 {
fn[0](f)
}
return f
}
// Configure current formatter
func (f *JSONFormatter) Configure(fn func(*JSONFormatter)) *JSONFormatter {
fn(f)
return f
}
// AddField for export
func (f *JSONFormatter) AddField(name string) *JSONFormatter {
f.Fields = append(f.Fields, name)
return f
}
var jsonPool bytebufferpool.Pool
// Format a log record to JSON bytes
func (f *JSONFormatter) Format(r *Record) ([]byte, error) {
logData := make(M, len(f.Fields))
// TODO perf: use buf write build JSON string.
for _, field := range f.Fields {
outName, ok := f.Aliases[field]
if !ok {
outName = field
}
switch {
case field == FieldKeyDatetime:
logData[outName] = r.Time.Format(f.TimeFormat)
case field == FieldKeyTimestamp:
logData[outName] = r.timestamp()
case field == FieldKeyCaller && r.Caller != nil:
logData[outName] = formatCaller(r.Caller, r.CallerFlag, f.CallerFormatFunc)
case field == FieldKeyLevel:
logData[outName] = r.LevelName()
case field == FieldKeyChannel:
logData[outName] = r.Channel
case field == FieldKeyMessage:
logData[outName] = r.Message
case field == FieldKeyData:
logData[outName] = r.Data
case field == FieldKeyExtra:
logData[outName] = r.Extra
// default:
// logData[outName] = r.Fields[field]
}
}
// exported custom record fields
for field, value := range r.Fields {
fieldKey := field
if _, has := logData[field]; has {
fieldKey = "fields." + field
}
logData[fieldKey] = value
}
// sort.Interface()
buf := jsonPool.Get()
// buf.Reset()
defer jsonPool.Put(buf)
// buf := r.NewBuffer()
// buf.Reset()
// buf.Grow(256)
encoder := json.NewEncoder(buf)
if f.PrettyPrint {
encoder.SetIndent("", " ")
}
// has been added newline in Encode().
err := encoder.Encode(logData)
return buf.Bytes(), err
}
================================================
FILE: formatter_test.go
================================================
package slog_test
import (
"fmt"
"runtime"
"strings"
"testing"
"github.com/gookit/goutil/byteutil"
"github.com/gookit/goutil/dump"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func TestFormattableTrait_Formatter(t *testing.T) {
ft := &slog.FormattableTrait{}
tf := slog.AsTextFormatter(ft.Formatter())
assert.NotNil(t, tf)
assert.Panics(t, func() {
slog.AsJSONFormatter(ft.Formatter())
})
ft.SetFormatter(slog.NewJSONFormatter())
jf := slog.AsJSONFormatter(ft.Formatter())
assert.NotNil(t, jf)
assert.Panics(t, func() {
slog.AsTextFormatter(ft.Formatter())
})
}
func TestFormattable_Format(t *testing.T) {
r := newLogRecord("TEST_LOG_MESSAGE format")
f := &slog.FormattableTrait{}
assert.Eq(t, "slog: TEST_LOG_MESSAGE format", r.GoString())
bts, err := f.Format(r)
assert.NoErr(t, err)
str := string(bts)
assert.Contains(t, str, "TEST_LOG_MESSAGE format")
fn := slog.FormatterFunc(func(r *slog.Record) ([]byte, error) {
return []byte(r.Message), nil
})
bts, err = fn.Format(r)
assert.NoErr(t, err)
str = string(bts)
assert.Contains(t, str, "TEST_LOG_MESSAGE format")
}
func TestNewTextFormatter(t *testing.T) {
f := slog.NewTextFormatter()
dump.Println(f.Fields())
assert.Contains(t, f.Fields(), "datetime")
assert.Len(t, f.Fields(), strings.Count(slog.DefaultTemplate, "{{"))
f.SetTemplate(slog.NamedTemplate)
dump.Println(f.Fields())
assert.Contains(t, f.Fields(), "datetime")
assert.Len(t, f.Fields(), strings.Count(slog.NamedTemplate, "{{"))
f.WithEnableColor(true)
assert.True(t, f.EnableColor)
f1 := slog.NewTextFormatter()
f1.Configure(func(f *slog.TextFormatter) {
f.FullDisplay = true
})
assert.True(t, f1.FullDisplay)
t.Run("CallerFormatFunc", func(t *testing.T) {
buf := byteutil.NewBuffer()
h := handler.IOWriterWithMaxLevel(buf, slog.DebugLevel)
h.SetFormatter(slog.TextFormatterWith(func(f *slog.TextFormatter) {
f.CallerFormatFunc = func(rf *runtime.Frame) string {
return "custom_caller"
}
}))
l := slog.NewWithHandlers(h)
l.Debug("test message")
assert.Contains(t, buf.String(), "custom_caller")
})
}
func TestTextFormatter_Format(t *testing.T) {
r := newLogRecord("TEST_LOG_MESSAGE")
f := slog.NewTextFormatter()
bs, err := f.Format(r)
logTxt := string(bs)
fmt.Println(f.Template(), logTxt)
assert.NoErr(t, err)
assert.NotEmpty(t, logTxt)
assert.NotContains(t, logTxt, "{{")
assert.NotContains(t, logTxt, "}}")
}
func TestTextFormatter_ColorRenderFunc(t *testing.T) {
f := slog.NewTextFormatter()
f.WithEnableColor(true)
f.ColorRenderFunc = func(field, s string, l slog.Level) string {
return fmt.Sprintf("NO-%s-NO", s)
}
r := newLogRecord("TEST_LOG_MESSAGE")
bts, err := f.Format(r)
assert.NoErr(t, err)
str := string(bts)
assert.StrContains(t, str, "[NO-info-NO]")
assert.StrContains(t, str, "NO-TEST_LOG_MESSAGE-NO")
}
func TestTextFormatter_LimitLevelNameLen(t *testing.T) {
f := slog.TextFormatterWith(slog.LimitLevelNameLen(4))
h := handler.ConsoleWithMaxLevel(slog.TraceLevel)
h.SetFormatter(f)
th := newTestHandler()
th.resetOnFlush = false
th.SetFormatter(f)
l := slog.NewWithHandlers(h, th)
l.DoNothingOnPanicFatal()
for _, level := range slog.AllLevels {
l.Logf(level, "a %s test message", level.String())
}
assert.NoErr(t, l.LastErr())
str := th.ResetAndGet()
assert.StrContains(t, str, "[PANI]")
assert.StrContains(t, str, "[FATA]")
assert.StrContains(t, str, "[ERRO]")
assert.StrContains(t, str, "[TRAC]")
}
func TestTextFormatter_LimitLevelNameLen2(t *testing.T) {
// set to max length.
f := slog.TextFormatterWith(slog.LimitLevelNameLen(7))
h := handler.ConsoleWithMaxLevel(slog.TraceLevel)
h.SetFormatter(f)
th := newTestHandler()
th.resetOnFlush = false
th.SetFormatter(f)
l := slog.NewWithHandlers(h, th)
l.DoNothingOnPanicFatal()
for _, level := range slog.AllLevels {
l.Logf(level, "a %s test message", level.String())
}
assert.NoErr(t, l.LastErr())
str := th.ResetAndGet()
assert.StrContains(t, str, "[PANIC ]")
assert.StrContains(t, str, "[FATAL ]")
assert.StrContains(t, str, "[ERROR ]")
assert.StrContains(t, str, "[WARNING]")
}
func TestNewJSONFormatter(t *testing.T) {
f := slog.NewJSONFormatter()
f.AddField(slog.FieldKeyTimestamp)
h := handler.ConsoleWithLevels(slog.AllLevels)
h.SetFormatter(f)
l := slog.NewWithHandlers(h)
fields := slog.M{
"field1": 123,
"field2": "abc",
"message": "field name is same of message", // will be as fields.message
}
l.WithFields(fields).Info("info", "message")
t.Run("CallerFormatFunc", func(t *testing.T) {
h.SetFormatter(slog.NewJSONFormatter(func(f *slog.JSONFormatter) {
f.CallerFormatFunc = func(rf *runtime.Frame) string {
return rf.Function
}
}))
l.WithFields(fields).Info("info", "message")
})
// PrettyPrint=true
t.Run("PrettyPrint", func(t *testing.T) {
l = slog.New()
h = handler.ConsoleWithMaxLevel(slog.DebugLevel)
f = slog.NewJSONFormatter(func(f *slog.JSONFormatter) {
f.Aliases = slog.StringMap{
"level": "levelName",
}
f.PrettyPrint = true
})
h.SetFormatter(f)
l.AddHandler(h)
l.WithFields(fields).
SetData(slog.M{"key1": "val1"}).
SetExtra(slog.M{"ext1": "val1"}).
Info("info message and PrettyPrint is TRUE")
})
}
================================================
FILE: formatter_text.go
================================================
package slog
import (
"github.com/gookit/color"
"github.com/gookit/goutil/arrutil"
"github.com/valyala/bytebufferpool"
)
// there are built in text log template
const (
DefaultTemplate = "[{{datetime}}] [{{channel}}] [{{level}}] [{{caller}}] {{message}} {{data}} {{extra}}\n"
NamedTemplate = "{{datetime}} channel={{channel}} level={{level}} [file={{caller}}] message={{message}} data={{data}}\n"
)
// ColorTheme for format log to console
var ColorTheme = map[Level]color.Color{
PanicLevel: color.FgRed,
FatalLevel: color.FgRed,
ErrorLevel: color.FgMagenta,
WarnLevel: color.FgYellow,
NoticeLevel: color.OpBold,
InfoLevel: color.FgGreen,
DebugLevel: color.FgCyan,
// TraceLevel: color.FgLightGreen,
}
// TextFormatter definition
type TextFormatter struct {
// template text template for render output log messages
template string
// fields list, parsed from template string.
//
// NOTE: contains no-field items in the list. eg: ["level", "}}"}
fields []string
// TimeFormat the time format layout. default is DefaultTimeFormat
TimeFormat string
// Enable color on print log to terminal
EnableColor bool
// ColorTheme setting on render color on terminal
ColorTheme map[Level]color.Color
// FullDisplay Whether to display when record.Data, record.Extra, etc. are empty
FullDisplay bool
// EncodeFunc data encode for Record.Data, Record.Extra, etc.
//
// Default is encode by EncodeToString()
EncodeFunc func(v any) string
// CallerFormatFunc the caller format layout. default is defined by CallerFlag
CallerFormatFunc CallerFormatFn
// LevelFormatFunc custom the level name format.
LevelFormatFunc func(s string) string
// ColorRenderFunc custom color render func.
//
// - `s`: level name or message
ColorRenderFunc func(filed, s string, l Level) string
// TODO BeforeFunc call it before format, update fields or other
// BeforeFunc func(r *Record)
}
// TextFormatterFn definition
type TextFormatterFn func(*TextFormatter)
// NewTextFormatter create new TextFormatter
func NewTextFormatter(template ...string) *TextFormatter {
var fmtTpl string
if len(template) > 0 {
fmtTpl = template[0]
} else {
fmtTpl = DefaultTemplate
}
f := &TextFormatter{
// default options
ColorTheme: ColorTheme,
TimeFormat: DefaultTimeFormat,
// EnableColor: color.SupportColor(),
// EncodeFunc: func(v any) string {
// return fmt.Sprint(v)
// },
EncodeFunc: EncodeToString,
}
f.SetTemplate(fmtTpl)
return f
}
// TextFormatterWith create new TextFormatter with options
func TextFormatterWith(fns ...TextFormatterFn) *TextFormatter {
return NewTextFormatter().WithOptions(fns...)
}
// LimitLevelNameLen limit the length of the level name
func LimitLevelNameLen(length int) TextFormatterFn {
return func(f *TextFormatter) {
f.LevelFormatFunc = func(s string) string {
return FormatLevelName(s, length)
}
}
}
// Configure the formatter
func (f *TextFormatter) Configure(fn TextFormatterFn) *TextFormatter {
return f.WithOptions(fn)
}
// WithOptions func on the formatter
func (f *TextFormatter) WithOptions(fns ...TextFormatterFn) *TextFormatter {
for _, fn := range fns {
fn(f)
}
return f
}
// SetTemplate set the log format template and update field-map
func (f *TextFormatter) SetTemplate(fmtTpl string) {
f.template = fmtTpl
f.fields = parseTemplateToFields(fmtTpl)
}
// Template get
func (f *TextFormatter) Template() string {
return f.template
}
// WithEnableColor enable color on print log to terminal
func (f *TextFormatter) WithEnableColor(enable bool) *TextFormatter {
f.EnableColor = enable
return f
}
// Fields get an export field list
func (f *TextFormatter) Fields() []string {
ss := make([]string, 0, len(f.fields)/2)
for _, s := range f.fields {
if s[0] >= 'a' && s[0] <= 'z' {
ss = append(ss, s)
}
}
return ss
}
var textPool bytebufferpool.Pool
// Format a log record
//
//goland:noinspection GoUnhandledErrorResult
func (f *TextFormatter) Format(r *Record) ([]byte, error) {
f.beforeFormat()
buf := textPool.Get()
defer textPool.Put(buf)
// record formatted custom fields
var formattedFields []string
for _, field := range f.fields {
// is not field name. eg: "}}] "
if field[0] < 'a' || field[0] > 'z' {
// remove left "}}"
if len(field) > 1 && field[0:2] == "}}" {
buf.WriteString(field[2:])
} else {
buf.WriteString(field)
}
continue
}
switch {
case field == FieldKeyDatetime:
buf.B = r.Time.AppendFormat(buf.B, f.TimeFormat)
case field == FieldKeyTimestamp:
buf.WriteString(r.timestamp())
case field == FieldKeyCaller && r.Caller != nil:
buf.WriteString(formatCaller(r.Caller, r.CallerFlag, f.CallerFormatFunc))
case field == FieldKeyLevel:
buf.WriteString(f.renderColorText(field, r.LevelName(), r.Level))
case field == FieldKeyChannel:
buf.WriteString(r.Channel)
case field == FieldKeyMessage:
buf.WriteString(f.renderColorText(field, r.Message, r.Level))
case field == FieldKeyData:
if f.FullDisplay || len(r.Data) > 0 {
buf.WriteString(f.EncodeFunc(r.Data))
}
case field == FieldKeyExtra:
if f.FullDisplay || len(r.Extra) > 0 {
buf.WriteString(f.EncodeFunc(r.Extra))
}
default:
if _, ok := r.Fields[field]; ok {
formattedFields = append(formattedFields, field)
buf.WriteString(f.EncodeFunc(r.Fields[field]))
} else {
buf.WriteString(field)
}
}
}
// UP: check not configured fields in template.
if fLen := len(r.Fields); fLen > 0 && fLen != len(formattedFields) {
unformattedFields := make(map[string]any)
for k, v := range r.Fields {
if !arrutil.StringsContains(formattedFields, k) {
unformattedFields[k] = v
}
}
buf.WriteString("UN-CONFIGURED FIELDS: ")
buf.WriteString(f.EncodeFunc(unformattedFields))
buf.WriteByte('\n')
}
// return buf.Bytes(), nil
return buf.B, nil
}
func (f *TextFormatter) beforeFormat() {
// if f.BeforeFunc == nil {}
if f.EncodeFunc == nil {
f.EncodeFunc = EncodeToString
}
if f.ColorTheme == nil {
f.ColorTheme = ColorTheme
}
}
func (f *TextFormatter) renderColorText(field, s string, l Level) string {
// custom level name format
if f.LevelFormatFunc != nil && field == FieldKeyLevel {
s = f.LevelFormatFunc(s)
}
if !f.EnableColor {
return s
}
// custom color render func
if f.ColorRenderFunc != nil {
return f.ColorRenderFunc(field, s, l)
}
// output colored logs for console output
if theme, ok := f.ColorTheme[l]; ok {
return theme.Render(s)
}
return s
}
================================================
FILE: go.mod
================================================
module github.com/gookit/slog
go 1.19
require (
github.com/gookit/color v1.6.0
github.com/gookit/goutil v0.7.4
github.com/gookit/gsr v0.1.1
github.com/valyala/bytebufferpool v1.0.0
)
require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/gookit/goutil v0.7.4 h1:OWgUngToNz+bPlX5aP+EMG31DraEU63uvKMwwT3vseM=
github.com/gookit/goutil v0.7.4/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/gookit/gsr v0.1.1 h1:TaHD3M7qa6lcAf9D2J4mGNg+QjgDtD1bw7uctF8RXOM=
github.com/gookit/gsr v0.1.1/go.mod h1:7wv4Y4WCnil8+DlDYHBjidzrEzfHhXEoFjEA0pPPWpI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
================================================
FILE: handler/README.md
================================================
# Handlers
Package handler provide useful common log handlers. eg: file, console, multi_file, rotate_file, stream, syslog, email
```text
handler -> buffered -> rotated -> writer(os.File)
```
## Built-in handlers
- `handler.ConsoleHandler` Console handler
- `handler.FileHandler` File handler
- `handler.StreamHandler` Stream handler
- `handler.SyslogHandler` Syslog handler
- `handler.EmailHandler` Email handler
- `handler.FlushCloseHandler` Flush and close handler
## Go Docs
Docs generated by: `go doc ./handler`
### Handler Functions
```go
func LineBuffOsFile(f *os.File, bufSize int, levels []slog.Level) slog.Handler
func LineBuffWriter(w io.Writer, bufSize int, levels []slog.Level) slog.Handler
func LineBufferedFile(logfile string, bufSize int, levels []slog.Level) (slog.Handler, error)
type ConsoleHandler = IOWriterHandler
func ConsoleWithLevels(levels []slog.Level) *ConsoleHandler
func ConsoleWithMaxLevel(level slog.Level) *ConsoleHandler
func NewConsole(levels []slog.Level) *ConsoleHandler
func NewConsoleHandler(levels []slog.Level) *ConsoleHandler
func NewConsoleWithLF(lf slog.LevelFormattable) *ConsoleHandler
type EmailHandler struct{ ... }
func NewEmailHandler(from EmailOption, toAddresses []string) *EmailHandler
type EmailOption struct{ ... }
type FlushCloseHandler struct{ ... }
func FlushCloserWithLevels(out FlushCloseWriter, levels []slog.Level) *FlushCloseHandler
func FlushCloserWithMaxLevel(out FlushCloseWriter, maxLevel slog.Level) *FlushCloseHandler
func NewBuffered(w io.WriteCloser, bufSize int, levels ...slog.Level) *FlushCloseHandler
func NewBufferedHandler(w io.WriteCloser, bufSize int, levels ...slog.Level) *FlushCloseHandler
func NewFlushCloseHandler(out FlushCloseWriter, levels []slog.Level) *FlushCloseHandler
func NewFlushCloser(out FlushCloseWriter, levels []slog.Level) *FlushCloseHandler
func NewFlushCloserWithLF(out FlushCloseWriter, lf slog.LevelFormattable) *FlushCloseHandler
type IOWriterHandler struct{ ... }
func IOWriterWithLevels(out io.Writer, levels []slog.Level) *IOWriterHandler
func IOWriterWithMaxLevel(out io.Writer, maxLevel slog.Level) *IOWriterHandler
func NewIOWriter(out io.Writer, levels []slog.Level) *IOWriterHandler
func NewIOWriterHandler(out io.Writer, levels []slog.Level) *IOWriterHandler
func NewIOWriterWithLF(out io.Writer, lf slog.LevelFormattable) *IOWriterHandler
func NewSimpleHandler(out io.Writer, maxLevel slog.Level) *IOWriterHandler
func SimpleWithLevels(out io.Writer, levels []slog.Level) *IOWriterHandler
type SimpleHandler = IOWriterHandler
func NewHandler(out io.Writer, maxLevel slog.Level) *SimpleHandler
func NewSimple(out io.Writer, maxLevel slog.Level) *SimpleHandler
type SyncCloseHandler struct{ ... }
func JSONFileHandler(logfile string, fns ...ConfigFn) (*SyncCloseHandler, error)
func MustFileHandler(logfile string, fns ...ConfigFn) *SyncCloseHandler
func MustRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) *SyncCloseHandler
func MustSimpleFile(filepath string, maxLv ...slog.Level) *SyncCloseHandler
func MustSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) *SyncCloseHandler
func MustTimeRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) *SyncCloseHandler
func NewBuffFileHandler(logfile string, buffSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
func NewFileHandler(logfile string, fns ...ConfigFn) (h *SyncCloseHandler, err error)
func NewRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
func NewRotateFileHandler(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
func NewSimpleFile(filepath string, maxLv ...slog.Level) (*SyncCloseHandler, error)
func NewSimpleFileHandler(filePath string, maxLv ...slog.Level) (*SyncCloseHandler, error)
func NewSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
func NewSizeRotateFileHandler(logfile string, maxSize int, fns ...ConfigFn) (*SyncCloseHandler, error)
func NewSyncCloseHandler(out SyncCloseWriter, levels []slog.Level) *SyncCloseHandler
func NewSyncCloser(out SyncCloseWriter, levels []slog.Level) *SyncCloseHandler
func NewSyncCloserWithLF(out SyncCloseWriter, lf slog.LevelFormattable) *SyncCloseHandler
func NewTimeRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
func NewTimeRotateFileHandler(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error)
func SyncCloserWithLevels(out SyncCloseWriter, levels []slog.Level) *SyncCloseHandler
func SyncCloserWithMaxLevel(out SyncCloseWriter, maxLevel slog.Level) *SyncCloseHandler
type SysLogHandler struct{ ... }
func NewSysLogHandler(priority syslog.Priority, tag string) (*SysLogHandler, error)
type WriteCloserHandler struct{ ... }
func NewWriteCloser(out io.WriteCloser, levels []slog.Level) *WriteCloserHandler
func NewWriteCloserHandler(out io.WriteCloser, levels []slog.Level) *WriteCloserHandler
func NewWriteCloserWithLF(out io.WriteCloser, lf slog.LevelFormattable) *WriteCloserHandler
func WriteCloserWithLevels(out io.WriteCloser, levels []slog.Level) *WriteCloserHandler
func WriteCloserWithMaxLevel(out io.WriteCloser, maxLevel slog.Level) *WriteCloserHandler
```
### Config Functions
```go
type Builder struct{ ... }
func NewBuilder() *Builder
type Config struct{ ... }
func NewConfig(fns ...ConfigFn) *Config
func NewEmptyConfig(fns ...ConfigFn) *Config
type ConfigFn func(c *Config)
func WithBackupNum(n uint) ConfigFn
func WithBackupTime(bt uint) ConfigFn
func WithBuffMode(buffMode string) ConfigFn
func WithBuffSize(buffSize int) ConfigFn
func WithCompress(compress bool) ConfigFn
func WithFilePerm(filePerm fs.FileMode) ConfigFn
func WithLevelMode(mode slog.LevelMode) ConfigFn
func WithLevelName(name string) ConfigFn
func WithLevelNames(names []string) ConfigFn
func WithLevelNamesString(names string) ConfigFn
func WithLogLevel(level slog.Level) ConfigFn
func WithLogLevels(levels slog.Levels) ConfigFn
func WithLogfile(logfile string) ConfigFn
func WithMaxLevelName(name string) ConfigFn
func WithMaxSize(maxSize uint64) ConfigFn
func WithRotateMode(m rotatefile.RotateMode) ConfigFn
func WithRotateTime(rt rotatefile.RotateTime) ConfigFn
func WithRotateTimeString(rt string) ConfigFn
func WithTimeClock(clock rotatefile.Clocker) ConfigFn
func WithUseJSON(useJSON bool) ConfigFn
```
================================================
FILE: handler/buffer.go
================================================
package handler
import (
"io"
"os"
"github.com/gookit/slog"
"github.com/gookit/slog/bufwrite"
)
// NewBuffered create new BufferedHandler
func NewBuffered(w io.WriteCloser, bufSize int, levels ...slog.Level) *FlushCloseHandler {
return NewBufferedHandler(w, bufSize, levels...)
}
// NewBufferedHandler create new BufferedHandler
func NewBufferedHandler(w io.WriteCloser, bufSize int, levels ...slog.Level) *FlushCloseHandler {
if len(levels) == 0 {
levels = slog.AllLevels
}
out := bufwrite.NewBufIOWriterSize(w, bufSize)
return FlushCloserWithLevels(out, levels)
}
// LineBufferedFile handler
func LineBufferedFile(logfile string, bufSize int, levels []slog.Level) (slog.Handler, error) {
cfg := NewConfig(
WithLogfile(logfile),
WithBuffSize(bufSize),
WithLogLevels(levels),
WithBuffMode(BuffModeLine),
)
out, err := cfg.CreateWriter()
if err != nil {
return nil, err
}
return SyncCloserWithLevels(out, levels), nil
}
// LineBuffOsFile handler
func LineBuffOsFile(f *os.File, bufSize int, levels []slog.Level) slog.Handler {
if f == nil {
panic("slog: the os file cannot be nil")
}
out := bufwrite.NewLineWriterSize(f, bufSize)
return SyncCloserWithLevels(out, levels)
}
// LineBuffWriter handler
func LineBuffWriter(w io.Writer, bufSize int, levels []slog.Level) slog.Handler {
if w == nil {
panic("slog: the io writer cannot be nil")
}
out := bufwrite.NewLineWriterSize(w, bufSize)
return IOWriterWithLevels(out, levels)
}
//
// --------- wrap a handler with buffer ---------
//
// FormatWriterHandler interface
type FormatWriterHandler interface {
slog.Handler
// Formatter record formatter
Formatter() slog.Formatter
// Writer the output writer
Writer() io.Writer
}
================================================
FILE: handler/buffer_test.go
================================================
package handler_test
import (
"os"
"testing"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func TestNewBufferedHandler(t *testing.T) {
logfile := "./testdata/buffer-os-file.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
file, err := handler.QuickOpenFile(logfile)
assert.NoErr(t, err)
assert.True(t, fsutil.IsFile(logfile))
bh := handler.NewBuffered(file, 128)
// new logger
l := slog.NewWithHandlers(bh)
l.Info("buffered info message")
bts, err := os.ReadFile(logfile)
assert.NoErr(t, err)
assert.Empty(t, bts)
l.Warn("buffered warn message")
bts, err = os.ReadFile(logfile)
assert.NoErr(t, err)
str := string(bts)
assert.Contains(t, str, "[INFO]")
err = l.FlushAll()
assert.NoErr(t, err)
}
func TestLineBufferedFile(t *testing.T) {
logfile := "./testdata/line-buff-file.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
h, err := handler.LineBufferedFile(logfile, 12, slog.AllLevels)
assert.NoErr(t, err)
assert.True(t, fsutil.IsFile(logfile))
r := newLogRecord("Test LineBufferedFile")
err = h.Handle(r)
assert.NoErr(t, err)
bts, err := os.ReadFile(logfile)
assert.NoErr(t, err)
str := string(bts)
assert.Contains(t, str, "[INFO]")
assert.Contains(t, str, "Test LineBufferedFile")
}
func TestLineBuffOsFile(t *testing.T) {
logfile := "./testdata/line-buff-os-file.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
file, err := fsutil.QuickOpenFile(logfile)
assert.NoErr(t, err)
h := handler.LineBuffOsFile(file, 12, slog.AllLevels)
assert.NoErr(t, err)
assert.True(t, fsutil.IsFile(logfile))
r := newLogRecord("Test LineBuffOsFile")
err = h.Handle(r)
assert.NoErr(t, err)
bts, err := os.ReadFile(logfile)
assert.NoErr(t, err)
str := string(bts)
assert.Contains(t, str, "[INFO]")
assert.Contains(t, str, "Test LineBuffOsFile")
assert.Panics(t, func() {
handler.LineBuffOsFile(nil, 12, slog.AllLevels)
})
}
func TestLineBuffWriter(t *testing.T) {
logfile := "./testdata/line-buff-writer.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
file, err := fsutil.QuickOpenFile(logfile)
assert.NoErr(t, err)
h := handler.LineBuffWriter(file, 12, slog.AllLevels)
assert.NoErr(t, err)
assert.True(t, fsutil.IsFile(logfile))
assert.Panics(t, func() {
handler.LineBuffWriter(nil, 12, slog.AllLevels)
})
r := newLogRecord("Test LineBuffWriter")
err = h.Handle(r)
assert.NoErr(t, err)
bts, err := os.ReadFile(logfile)
assert.NoErr(t, err)
str := string(bts)
assert.Contains(t, str, "[INFO]")
assert.Contains(t, str, "Test LineBuffWriter")
assert.Panics(t, func() {
handler.LineBuffOsFile(nil, 12, slog.AllLevels)
})
}
================================================
FILE: handler/builder.go
================================================
package handler
import (
"io"
"github.com/gookit/slog"
"github.com/gookit/slog/rotatefile"
)
//
// ---------------------------------------------------------------------------
// handler builder
// ---------------------------------------------------------------------------
//
// Builder struct for create handler
type Builder struct {
*Config
Output io.Writer
}
// NewBuilder create
func NewBuilder() *Builder {
return &Builder{
Config: NewEmptyConfig(),
}
}
// WithOutput to the builder
func (b *Builder) WithOutput(w io.Writer) *Builder {
b.Output = w
return b
}
// With some config fn
//
// Deprecated: please use WithConfigFn()
func (b *Builder) With(fns ...ConfigFn) *Builder {
return b.WithConfigFn(fns...)
}
// WithConfigFn some config fn
func (b *Builder) WithConfigFn(fns ...ConfigFn) *Builder {
b.Config.With(fns...)
return b
}
// WithLogfile setting
func (b *Builder) WithLogfile(logfile string) *Builder {
b.Logfile = logfile
return b
}
// WithLevelMode setting
func (b *Builder) WithLevelMode(mode slog.LevelMode) *Builder {
b.LevelMode = mode
return b
}
// WithLogLevel setting max log level
func (b *Builder) WithLogLevel(level slog.Level) *Builder {
b.Level = level
b.LevelMode = slog.LevelModeMax
return b
}
// WithLogLevels setting
func (b *Builder) WithLogLevels(levels []slog.Level) *Builder {
b.Levels = levels
b.LevelMode = slog.LevelModeList
return b
}
// WithBuffMode setting
func (b *Builder) WithBuffMode(bufMode string) *Builder {
b.BuffMode = bufMode
return b
}
// WithBuffSize setting
func (b *Builder) WithBuffSize(bufSize int) *Builder {
b.BuffSize = bufSize
return b
}
// WithMaxSize setting
func (b *Builder) WithMaxSize(maxSize uint64) *Builder {
b.MaxSize = maxSize
return b
}
// WithRotateTime setting
func (b *Builder) WithRotateTime(rt rotatefile.RotateTime) *Builder {
b.RotateTime = rt
return b
}
// WithCompress setting
func (b *Builder) WithCompress(compress bool) *Builder {
b.Compress = compress
return b
}
// WithUseJSON setting
func (b *Builder) WithUseJSON(useJSON bool) *Builder {
b.UseJSON = useJSON
return b
}
// Build slog handler.
func (b *Builder) Build() slog.FormattableHandler {
if b.Output != nil {
return b.buildFromWriter(b.Output)
}
if b.Logfile != "" {
w, err := b.CreateWriter()
if err != nil {
panic(err)
}
return b.buildFromWriter(w)
}
panic("slog: missing information for build slog handler")
}
// Build slog handler.
func (b *Builder) buildFromWriter(w io.Writer) (h slog.FormattableHandler) {
defer b.reset()
bufSize := b.BuffSize
lf := b.newLevelFormattable()
if scw, ok := w.(SyncCloseWriter); ok {
if bufSize > 0 {
scw = b.wrapBuffer(scw)
}
h = NewSyncCloserWithLF(scw, lf)
} else if fcw, ok := w.(FlushCloseWriter); ok {
if bufSize > 0 {
fcw = b.wrapBuffer(fcw)
}
h = NewFlushCloserWithLF(fcw, lf)
} else if wc, ok := w.(io.WriteCloser); ok {
if bufSize > 0 {
wc = b.wrapBuffer(wc)
}
h = NewWriteCloserWithLF(wc, lf)
} else {
if bufSize > 0 {
w = b.wrapBuffer(w)
}
h = NewIOWriterWithLF(w, lf)
}
// use json format.
if b.UseJSON {
h.SetFormatter(slog.NewJSONFormatter())
}
return
}
// rest builder.
func (b *Builder) reset() {
b.Output = nil
b.Config = NewEmptyConfig()
}
================================================
FILE: handler/config.go
================================================
package handler
import (
"encoding/json"
"io"
"io/fs"
"strings"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/slog"
"github.com/gookit/slog/bufwrite"
"github.com/gookit/slog/rotatefile"
)
// the buff mode constants
const (
BuffModeLine = "line"
BuffModeBite = "bite"
)
const (
// LevelModeList use level list for limit record write
LevelModeList = slog.LevelModeList
// LevelModeValue use max level limit log record write
LevelModeValue = slog.LevelModeMax
)
// ConfigFn for config some settings
type ConfigFn func(c *Config)
// Config struct
type Config struct {
// Logfile for writing logs
Logfile string `json:"logfile" yaml:"logfile"`
// FilePerm for create log file. default rotatefile.DefaultFilePerm
FilePerm fs.FileMode `json:"file_perm" yaml:"file_perm"`
// LevelMode for limit log records. default LevelModeList
LevelMode slog.LevelMode `json:"level_mode" yaml:"level_mode"`
// Level max value. valid on LevelMode = LevelModeValue
//
// eg: set Level=slog.LevelError, it will only write messages on level <= error.
Level slog.Level `json:"level" yaml:"level"`
// Levels list for writing. valid on LevelMode = LevelModeList
Levels []slog.Level `json:"levels" yaml:"levels"`
// UseJSON for format logs
UseJSON bool `json:"use_json" yaml:"use_json"`
// BuffMode type name. allow: line, bite
//
// Recommend use BuffModeLine(it's default)
BuffMode string `json:"buff_mode" yaml:"buff_mode"`
// BuffSize for enable buffer, unit is bytes. set 0 to disable buffer
BuffSize int `json:"buff_size" yaml:"buff_size"`
// RotateTime for a rotating file, unit is seconds.
RotateTime rotatefile.RotateTime `json:"rotate_time" yaml:"rotate_time"`
// RotateMode for a rotating file by time. default rotatefile.ModeRename
RotateMode rotatefile.RotateMode `json:"rotate_mode" yaml:"rotate_mode"`
// TimeClock for a rotating file by time.
TimeClock rotatefile.Clocker `json:"-" yaml:"-"`
// MaxSize for a rotating file by size, unit is bytes.
MaxSize uint64 `json:"max_size" yaml:"max_size"`
// Compress determines if the rotated log files should be compressed using gzip.
// The default is not to perform compression.
Compress bool `json:"compress" yaml:"compress"`
// BackupNum max number for keep old files.
//
// 0 is not limit, default is 20.
BackupNum uint `json:"backup_num" yaml:"backup_num"`
// BackupTime max time for keep old files, unit is hours.
//
// 0 is not limit, default is a week.
BackupTime uint `json:"backup_time" yaml:"backup_time"`
// RenameFunc build filename for rotate file
RenameFunc func(filepath string, rotateNum uint) string
// CleanOnClose determines if the rotated log files should be cleaned up when close.
CleanOnClose bool `json:"clean_on_close" yaml:"clean_on_close"`
// DebugMode for debug on development.
DebugMode bool
}
// NewEmptyConfig new config instance
func NewEmptyConfig(fns ...ConfigFn) *Config {
c := &Config{Levels: slog.AllLevels}
return c.WithConfigFn(fns...)
}
// NewConfig new config instance with some default settings.
func NewConfig(fns ...ConfigFn) *Config {
c := &Config{
Levels: slog.AllLevels,
BuffMode: BuffModeLine,
BuffSize: DefaultBufferSize,
// rotate file settings
MaxSize: rotatefile.DefaultMaxSize,
RotateTime: rotatefile.EveryHour,
// old files clean settings
BackupNum: rotatefile.DefaultBackNum,
BackupTime: rotatefile.DefaultBackTime,
DebugMode: slog.DebugMode,
}
return c.WithConfigFn(fns...)
}
// FromJSON load config from json string
func (c *Config) FromJSON(bts []byte) error { return json.Unmarshal(bts, c) }
// With more config settings func
func (c *Config) With(fns ...ConfigFn) *Config { return c.WithConfigFn(fns...) }
// WithConfigFn more config settings func
func (c *Config) WithConfigFn(fns ...ConfigFn) *Config {
for _, fn := range fns {
fn(c)
}
return c
}
func (c *Config) newLevelFormattable() slog.LevelFormattable {
if c.LevelMode == LevelModeValue {
return slog.NewLvFormatter(c.Level)
}
return slog.NewLvsFormatter(c.Levels)
}
// CreateHandler quick create a handler by config
func (c *Config) CreateHandler() (*SyncCloseHandler, error) {
output, err := c.CreateWriter()
if err != nil {
return nil, err
}
h := &SyncCloseHandler{
Output: output,
// with log level and formatter
LevelFormattable: c.newLevelFormattable(),
}
if c.UseJSON {
h.SetFormatter(slog.NewJSONFormatter())
}
return h, nil
}
// RotateWriter build rotate writer by config
func (c *Config) RotateWriter() (output SyncCloseWriter, err error) {
if c.MaxSize == 0 && c.RotateTime == 0 {
return nil, errorx.E("slog: cannot create rotate writer, MaxSize and RotateTime both is 0")
}
return c.CreateWriter()
}
// CreateWriter build writer by config
func (c *Config) CreateWriter() (output SyncCloseWriter, err error) {
if c.Logfile == "" {
return nil, errorx.Raw("slog: logfile cannot be empty for create writer")
}
if c.FilePerm == 0 {
c.FilePerm = rotatefile.DefaultFilePerm
}
// create a rotated writer by config.
if c.MaxSize > 0 || c.RotateTime > 0 {
rc := rotatefile.EmptyConfigWith()
// has locked on logger.write()
rc.CloseLock = true
rc.Filepath = c.Logfile
rc.FilePerm = c.FilePerm
rc.DebugMode = c.DebugMode
// copy settings
rc.MaxSize = c.MaxSize
rc.RotateTime = c.RotateTime
rc.RotateMode = c.RotateMode
rc.BackupNum = c.BackupNum
rc.BackupTime = c.BackupTime
rc.Compress = c.Compress
rc.CleanOnClose = c.CleanOnClose
if c.RenameFunc != nil {
rc.RenameFunc = c.RenameFunc
}
if c.TimeClock != nil {
rc.TimeClock = c.TimeClock
}
output, err = rc.Create()
} else {
// create a file writer
output, err = fsutil.OpenAppendFile(c.Logfile, c.FilePerm)
}
if err != nil {
return nil, err
}
// wrap buffer
if c.BuffSize > 0 {
output = c.wrapBuffer(output)
}
return
}
type flushSyncCloseWriter interface {
FlushCloseWriter
Sync() error
}
// wrap buffer for the writer
func (c *Config) wrapBuffer(w io.Writer) (bw flushSyncCloseWriter) {
if c.BuffMode == BuffModeLine {
bw = bufwrite.NewLineWriterSize(w, c.BuffSize)
} else {
bw = bufwrite.NewBufIOWriterSize(w, c.BuffSize)
}
return bw
}
//
// ---------------------------------------------------------------------------
// global config func
// ---------------------------------------------------------------------------
//
// WithLogfile setting
func WithLogfile(logfile string) ConfigFn {
return func(c *Config) { c.Logfile = logfile }
}
// WithFilePerm setting
func WithFilePerm(filePerm fs.FileMode) ConfigFn {
return func(c *Config) { c.FilePerm = filePerm }
}
// WithLevelMode setting
func WithLevelMode(lm slog.LevelMode) ConfigFn {
return func(c *Config) { c.LevelMode = lm }
}
// WithLevelModeString setting
func WithLevelModeString(s string) ConfigFn {
return func(c *Config) { c.LevelMode = slog.SafeToLevelMode(s) }
}
// WithLogLevel setting max log level
func WithLogLevel(level slog.Level) ConfigFn {
return func(c *Config) {
c.Level = level
c.LevelMode = LevelModeValue
}
}
// WithLevelName setting max level by name
func WithLevelName(name string) ConfigFn { return WithLogLevel(slog.LevelByName(name)) }
// WithMaxLevelName setting max level by name
func WithMaxLevelName(name string) ConfigFn { return WithLogLevel(slog.LevelByName(name)) }
// WithLogLevels setting
func WithLogLevels(levels slog.Levels) ConfigFn {
return func(c *Config) {
c.Levels = levels
c.LevelMode = LevelModeList
}
}
// WithLevelNamesString setting multi levels by level names string, multi names split by comma.
func WithLevelNamesString(names string) ConfigFn {
return WithLevelNames(strings.Split(names, ","))
}
// WithLevelNames set multi levels by level names.
func WithLevelNames(names []string) ConfigFn {
levels := make([]slog.Level, 0, len(names))
for _, name := range names {
levels = append(levels, slog.LevelByName(name))
}
return WithLogLevels(levels)
}
// WithRotateTime setting the rotated time
func WithRotateTime(rt rotatefile.RotateTime) ConfigFn {
return func(c *Config) { c.RotateTime = rt }
}
// WithRotateTimeString setting the rotated time by string.
//
// eg: "1hour", "24h", "1day", "7d", "1m", "30s"
func WithRotateTimeString(s string) ConfigFn {
return func(c *Config) {
rt, err := rotatefile.StringToRotateTime(s)
if err != nil {
panic(err)
}
c.RotateTime = rt
}
}
// WithRotateMode setting rotating mode rotatefile.RotateMode
func WithRotateMode(m rotatefile.RotateMode) ConfigFn {
return func(c *Config) { c.RotateMode = m }
}
// WithRotateModeString setting rotatefile.RotateMode by string.
func WithRotateModeString(s string) ConfigFn {
return func(c *Config) {
m, err := rotatefile.StringToRotateMode(s)
if err != nil {
panic(err)
}
c.RotateMode = m
}
}
// WithTimeClock setting
func WithTimeClock(clock rotatefile.Clocker) ConfigFn {
return func(c *Config) { c.TimeClock = clock }
}
// WithBackupNum setting
func WithBackupNum(n uint) ConfigFn {
return func(c *Config) { c.BackupNum = n }
}
// WithBackupTime setting backup time
func WithBackupTime(bt uint) ConfigFn {
return func(c *Config) { c.BackupTime = bt }
}
// WithBuffMode setting buffer mode
func WithBuffMode(buffMode string) ConfigFn {
return func(c *Config) { c.BuffMode = buffMode }
}
// WithBuffSize setting buffer size, unit is bytes.
func WithBuffSize(buffSize int) ConfigFn {
return func(c *Config) { c.BuffSize = buffSize }
}
// WithMaxSize setting max size for a rotated file
func WithMaxSize(maxSize uint64) ConfigFn {
return func(c *Config) { c.MaxSize = maxSize }
}
// WithCompress setting compress
func WithCompress(compress bool) ConfigFn {
return func(c *Config) { c.Compress = compress }
}
// WithUseJSON setting uses JSON format
func WithUseJSON(useJSON bool) ConfigFn {
return func(c *Config) { c.UseJSON = useJSON }
}
// WithDebugMode setting for debug mode
func WithDebugMode(c *Config) { c.DebugMode = true }
================================================
FILE: handler/config_test.go
================================================
package handler_test
import (
"bytes"
"testing"
"github.com/gookit/goutil/dump"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/goutil/x/fmtutil"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
"github.com/gookit/slog/rotatefile"
)
func TestNewConfig(t *testing.T) {
c := handler.NewConfig(
handler.WithCompress(true),
handler.WithLevelMode(handler.LevelModeValue),
handler.WithBackupNum(20),
handler.WithBackupTime(1800),
handler.WithRotateMode(rotatefile.ModeCreate),
func(c *handler.Config) {
c.BackupTime = 23
c.RenameFunc = func(fpath string, num uint) string {
return fpath + ".bak"
}
},
).
With(handler.WithBuffSize(129)).
WithConfigFn(handler.WithLogLevel(slog.ErrorLevel))
assert.True(t, c.Compress)
assert.Eq(t, 129, c.BuffSize)
assert.Eq(t, handler.LevelModeValue, c.LevelMode)
assert.Eq(t, slog.ErrorLevel, c.Level)
assert.Eq(t, rotatefile.ModeCreate, c.RotateMode)
c.With(handler.WithLevelModeString("max"))
assert.Eq(t, slog.LevelModeMax, c.LevelMode)
c.WithConfigFn(handler.WithLevelNames([]string{"info", "debug"}))
assert.Eq(t, []slog.Level{slog.InfoLevel, slog.DebugLevel}, c.Levels)
}
func TestConfig_fromJSON(t *testing.T) {
c := &handler.Config{}
assert.Eq(t, slog.LevelModeList, c.LevelMode)
assert.Eq(t, rotatefile.ModeRename, c.RotateMode)
assert.NoErr(t, c.FromJSON([]byte(`{
"logfile": "testdata/config_test.log",
"level": "debug",
"level_mode": "max",
"levels": ["info", "debug"],
"buff_mode": "line",
"buff_size": 128,
"backup_num": 3,
"backup_time": 3600,
"rotate_mode": "create",
"rotate_time": "1day"
}`)))
c.With(handler.WithDebugMode)
dump.P(c)
assert.Eq(t, slog.LevelModeMax, c.LevelMode)
assert.Eq(t, rotatefile.ModeCreate, c.RotateMode)
assert.Eq(t, "Every 1 Day", c.RotateTime.String())
}
func TestWithLevelNamesString(t *testing.T) {
c := handler.NewConfig(handler.WithLevelNamesString("info,error"))
assert.Eq(t, []slog.Level{slog.InfoLevel, slog.ErrorLevel}, c.Levels)
}
func TestWithMaxLevelName(t *testing.T) {
c := handler.NewConfig(handler.WithMaxLevelName("error"))
assert.Eq(t, slog.ErrorLevel, c.Level)
assert.Eq(t, handler.LevelModeValue, c.LevelMode)
c1 := handler.NewConfig(handler.WithLevelName("warn"))
assert.Eq(t, slog.WarnLevel, c1.Level)
assert.Eq(t, handler.LevelModeValue, c1.LevelMode)
}
func TestWithRotateMode(t *testing.T) {
c := handler.Config{}
c.With(handler.WithRotateModeString("rename"))
assert.Eq(t, rotatefile.ModeRename, c.RotateMode)
assert.PanicsErrMsg(t, func() {
c.With(handler.WithRotateModeString("invalid"))
}, "rotatefile: invalid rotate mode: invalid")
}
func TestWithRotateTimeString(t *testing.T) {
tests := []struct {
input string
expected rotatefile.RotateTime
panics bool
}{
{"1hours", rotatefile.RotateTime(3600), false},
{"24h", rotatefile.RotateTime(86400), false},
{"1day", rotatefile.RotateTime(86400), false},
{"7d", rotatefile.RotateTime(604800), false},
{"1m", rotatefile.RotateTime(60), false},
{"30s", rotatefile.RotateTime(30), false},
{"invalid", 0, true},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
c := &handler.Config{}
if tt.panics {
assert.Panics(t, func() {
handler.WithRotateTimeString(tt.input)(c)
})
} else {
assert.NotPanics(t, func() {
handler.WithRotateTimeString(tt.input)(c)
})
assert.Eq(t, tt.expected, c.RotateTime)
}
})
}
}
func TestNewBuilder(t *testing.T) {
testFile := "testdata/builder.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(testFile))
b := handler.NewBuilder().
WithLogfile(testFile).
WithLogLevels(slog.AllLevels).
WithBuffSize(128).
WithBuffMode(handler.BuffModeBite).
WithMaxSize(fmtutil.OneMByte * 3).
WithRotateTime(rotatefile.Every30Min).
WithCompress(true).
With(func(c *handler.Config) {
c.BackupNum = 3
})
assert.Eq(t, uint(3), b.BackupNum)
assert.Eq(t, handler.BuffModeBite, b.BuffMode)
assert.Eq(t, rotatefile.Every30Min, b.RotateTime)
h := b.Build()
assert.NotNil(t, h)
assert.NoErr(t, h.Close())
b1 := handler.NewBuilder().
WithOutput(new(bytes.Buffer)).
WithUseJSON(true).
WithLogLevel(slog.ErrorLevel).
WithLevelMode(handler.LevelModeValue)
assert.Eq(t, handler.LevelModeValue, b1.LevelMode)
assert.Eq(t, slog.ErrorLevel, b1.Level)
h2 := b1.Build()
assert.NotNil(t, h2)
assert.Panics(t, func() {
handler.NewBuilder().Build()
})
}
type simpleWriter struct {
errOnWrite bool
}
func (w *simpleWriter) Write(p []byte) (n int, err error) {
if w.errOnWrite {
return 0, errorx.Raw("write error")
}
return len(p), nil
}
type closeWriter struct {
errOnWrite bool
errOnClose bool
}
func (w *closeWriter) Close() error {
if w.errOnClose {
return errorx.Raw("close error")
}
return nil
}
func (w *closeWriter) Write(p []byte) (n int, err error) {
if w.errOnWrite {
return 0, errorx.Raw("write error")
}
return len(p), nil
}
type flushCloseWriter struct {
closeWriter
errOnFlush bool
}
// Flush implement stdio.Flusher
func (w *flushCloseWriter) Flush() error {
if w.errOnFlush {
return errorx.Raw("flush error")
}
return nil
}
type syncCloseWriter struct {
closeWriter
errOnSync bool
}
// Sync implement stdio.Syncer
func (w *syncCloseWriter) Sync() error {
if w.errOnSync {
return errorx.Raw("sync error")
}
return nil
}
func TestNewBuilder_buildFromWriter(t *testing.T) {
t.Run("FlushCloseWriter", func(t *testing.T) {
out := &flushCloseWriter{}
out.errOnFlush = true
h := handler.NewBuilder().
WithOutput(out).
WithConfigFn(func(c *handler.Config) {
c.RenameFunc = func(fpath string, num uint) string {
return fpath + ".bak"
}
}).
Build()
assert.Err(t, h.Flush())
// wrap buffer
h = handler.NewBuilder().
WithOutput(out).
WithBuffSize(128).
Build()
assert.NoErr(t, h.Close())
assert.NoErr(t, h.Flush())
})
t.Run("CloseWriter", func(t *testing.T) {
h := handler.NewBuilder().
WithOutput(&closeWriter{errOnClose: true}).
WithBuffSize(128).
Build()
assert.NotNil(t, h)
assert.Err(t, h.Close())
})
t.Run("SimpleWriter", func(t *testing.T) {
h := handler.NewBuilder().
WithOutput(&simpleWriter{errOnWrite: true}).
WithBuffSize(128).
Build()
assert.NotNil(t, h)
assert.NoErr(t, h.Close())
})
}
================================================
FILE: handler/console.go
================================================
package handler
import (
"os"
"github.com/gookit/color"
"github.com/gookit/slog"
)
/********************************************************************************
* console log handler
********************************************************************************/
// ConsoleHandler definition
type ConsoleHandler = IOWriterHandler
// NewConsoleWithLF create new ConsoleHandler and with custom slog.LevelFormattable
func NewConsoleWithLF(lf slog.LevelFormattable) *ConsoleHandler {
h := NewIOWriterWithLF(os.Stdout, lf)
// default use text formatter
f := slog.NewTextFormatter()
// default enable color on console
f.WithEnableColor(color.SupportColor())
h.SetFormatter(f)
return h
}
//
// ------------- Use max log level -------------
//
// ConsoleWithMaxLevel create new ConsoleHandler and with max log level
func ConsoleWithMaxLevel(level slog.Level) *ConsoleHandler {
return NewConsoleWithLF(slog.NewLvFormatter(level))
}
//
// ------------- Use multi log levels -------------
//
// NewConsole create new ConsoleHandler, alias of NewConsoleHandler
func NewConsole(levels []slog.Level) *ConsoleHandler {
return NewConsoleHandler(levels)
}
// ConsoleWithLevels create new ConsoleHandler and with limited log levels
func ConsoleWithLevels(levels []slog.Level) *ConsoleHandler {
return NewConsoleHandler(levels)
}
// NewConsoleHandler create new ConsoleHandler with limited log levels
func NewConsoleHandler(levels []slog.Level) *ConsoleHandler {
return NewConsoleWithLF(slog.NewLvsFormatter(levels))
}
================================================
FILE: handler/console_test.go
================================================
package handler_test
import (
"testing"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func TestConsoleWithMaxLevel(t *testing.T) {
l := slog.NewWithHandlers(handler.ConsoleWithMaxLevel(slog.InfoLevel))
l.DoNothingOnPanicFatal()
for _, level := range slog.AllLevels {
l.Log(level, "a test message")
}
assert.NoErr(t, l.LastErr())
}
================================================
FILE: handler/email.go
================================================
package handler
import (
"net/smtp"
"strconv"
"github.com/gookit/slog"
)
// EmailOption struct
type EmailOption struct {
SMTPHost string `json:"smtp_host"` // eg "smtp.gmail.com"
SMTPPort int `json:"smtp_port"` // eg 587
FromAddr string `json:"from_addr"` // eg "yourEmail@gmail.com"
Password string `json:"password"`
}
// EmailHandler struct
type EmailHandler struct {
NopFlushClose
slog.LevelWithFormatter
// From the sender email information
From EmailOption
// ToAddresses email list
ToAddresses []string
}
// NewEmailHandler instance
func NewEmailHandler(from EmailOption, toAddresses []string) *EmailHandler {
h := &EmailHandler{
From: from,
// to receivers
ToAddresses: toAddresses,
}
// init default log level
h.Level = slog.InfoLevel
return h
}
// Handle a log record
func (h *EmailHandler) Handle(r *slog.Record) error {
msgBytes, err := h.Format(r)
if err != nil {
return err
}
var auth = smtp.PlainAuth("", h.From.FromAddr, h.From.Password, h.From.SMTPHost)
addr := h.From.SMTPHost + ":" + strconv.Itoa(h.From.SMTPPort)
return smtp.SendMail(addr, auth, h.From.FromAddr, h.ToAddresses, msgBytes)
}
================================================
FILE: handler/example_test.go
================================================
package handler_test
import (
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
func Example_fileHandler() {
withLevels := handler.WithLogLevels(slog.Levels{slog.PanicLevel, slog.ErrorLevel, slog.WarnLevel})
h1 := handler.MustFileHandler("/tmp/error.log", withLevels)
withLevels = handler.WithLogLevels(slog.Levels{slog.InfoLevel, slog.NoticeLevel, slog.DebugLevel, slog.TraceLevel})
h2 := handler.MustFileHandler("/tmp/info.log", withLevels)
slog.PushHandler(h1)
slog.PushHandler(h2)
// add logs
slog.Info("info message")
slog.Error("error message")
}
func Example_rotateFileHandler() {
h1 := handler.MustRotateFile("/tmp/error.log", handler.EveryHour, handler.WithLogLevels(slog.DangerLevels))
h2 := handler.MustRotateFile("/tmp/info.log", handler.EveryHour, handler.WithLogLevels(slog.NormalLevels))
slog.PushHandler(h1)
slog.PushHandler(h2)
// add logs
slog.Info("info message")
slog.Error("error message")
}
================================================
FILE: handler/file.go
================================================
package handler
import (
"github.com/gookit/goutil/x/basefn"
"github.com/gookit/slog"
)
// JSONFileHandler create new FileHandler with JSON formatter
func JSONFileHandler(logfile string, fns ...ConfigFn) (*SyncCloseHandler, error) {
return NewFileHandler(logfile, append(fns, WithUseJSON(true))...)
}
// NewBuffFileHandler create file handler with buff size
func NewBuffFileHandler(logfile string, buffSize int, fns ...ConfigFn) (*SyncCloseHandler, error) {
return NewFileHandler(logfile, append(fns, WithBuffSize(buffSize))...)
}
// MustFileHandler create file handler
func MustFileHandler(logfile string, fns ...ConfigFn) *SyncCloseHandler {
return basefn.Must(NewFileHandler(logfile, fns...))
}
// NewFileHandler create new FileHandler
func NewFileHandler(logfile string, fns ...ConfigFn) (h *SyncCloseHandler, err error) {
return NewEmptyConfig(fns...).With(WithLogfile(logfile)).CreateHandler()
}
//
// ------------- simple file handler -------------
//
// MustSimpleFile new instance
func MustSimpleFile(filepath string, maxLv ...slog.Level) *SyncCloseHandler {
return basefn.Must(NewSimpleFileHandler(filepath, maxLv...))
}
// NewSimpleFile new instance
func NewSimpleFile(filepath string, maxLv ...slog.Level) (*SyncCloseHandler, error) {
return NewSimpleFileHandler(filepath, maxLv...)
}
// NewSimpleFileHandler instance, default log level is InfoLevel
//
// Usage:
//
// h, err := NewSimpleFileHandler("/tmp/error.log")
//
// Custom formatter:
//
// h.SetFormatter(slog.NewJSONFormatter())
// slog.PushHandler(h)
// slog.Info("log message")
func NewSimpleFileHandler(filePath string, maxLv ...slog.Level) (*SyncCloseHandler, error) {
file, err := QuickOpenFile(filePath)
if err != nil {
return nil, err
}
h := SyncCloserWithMaxLevel(file, basefn.FirstOr(maxLv, slog.InfoLevel))
return h, nil
}
================================================
FILE: handler/file_test.go
================================================
package handler_test
import (
"os"
"testing"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
// const testSubFile = "./testdata/subdir/app.log"
func TestNewFileHandler(t *testing.T) {
testFile := "testdata/file.log"
h, err := handler.NewFileHandler(testFile, handler.WithFilePerm(0644))
assert.NoErr(t, err)
l := slog.NewWithHandlers(h)
l.DoNothingOnPanicFatal()
l.Info("info message")
l.Warn("warn message")
logAllLevel(l, "file handler message")
assert.True(t, fsutil.IsFile(testFile))
str, err := fsutil.ReadStringOrErr(testFile)
assert.NoErr(t, err)
assert.Contains(t, str, "[INFO]")
assert.Contains(t, str, "info message")
assert.Contains(t, str, "[WARNING]")
assert.Contains(t, str, "warn message")
// assert.NoErr(t, os.Remove(testFile))
}
func TestMustFileHandler(t *testing.T) {
testFile := "testdata/file-must.log"
h := handler.MustFileHandler(testFile)
assert.NotEmpty(t, h.Writer())
r := newLogRecord("test file must handler")
err := h.Handle(r)
assert.NoErr(t, err)
assert.NoErr(t, h.Close())
bts := fsutil.MustReadFile(testFile)
str := string(bts)
assert.Contains(t, str, `INFO`)
assert.Contains(t, str, `test file must handler`)
}
func TestNewFileHandler_basic(t *testing.T) {
testFile := "testdata/file-basic.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(testFile))
h, err := handler.NewFileHandler(testFile)
assert.NoErr(t, err)
assert.NotEmpty(t, h.Writer())
r := newLogRecord("test file handler")
err = h.Handle(r)
assert.NoErr(t, err)
assert.NoErr(t, h.Close())
bts := fsutil.MustReadFile(testFile)
str := string(bts)
assert.Contains(t, str, `INFO`)
assert.Contains(t, str, `test file handler`)
}
func TestNewBuffFileHandler(t *testing.T) {
testFile := "testdata/file-buff.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(testFile))
h, err := handler.NewBuffFileHandler(testFile, 56)
assert.NoErr(t, err)
assert.NotEmpty(t, h.Writer())
r := newLogRecord("test file buff handler")
err = h.Handle(r)
assert.NoErr(t, err)
assert.NoErr(t, h.Close())
bts := fsutil.MustReadFile(testFile)
str := string(bts)
assert.Contains(t, str, `INFO`)
assert.Contains(t, str, `test file buff handler`)
}
func TestJSONFileHandler(t *testing.T) {
testFile := "testdata/file-json.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(testFile))
h, err := handler.JSONFileHandler(testFile)
assert.NoErr(t, err)
r := newLogRecord("test json file handler")
err = h.Handle(r)
assert.NoErr(t, err)
err = h.Close()
assert.NoErr(t, err)
bts := fsutil.MustReadFile(testFile)
str := string(bts)
assert.Contains(t, str, `"level":"INFO"`)
assert.Contains(t, str, `"message":"test json file handler"`)
}
func TestSimpleFile(t *testing.T) {
logfile := "./testdata/must-simple-file.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
h := handler.MustSimpleFile(logfile)
assert.True(t, h.IsHandling(slog.InfoLevel))
// NewSimpleFile
logfile = "./testdata/test-simple-file.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
h2, err := handler.NewSimpleFile(logfile)
assert.NoErr(t, err)
assert.True(t, h2.IsHandling(slog.InfoLevel))
}
func TestNewSimpleFileHandler(t *testing.T) {
logfile := "./testdata/simple-file.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
assert.False(t, fsutil.IsFile(logfile))
h, err := handler.NewSimpleFileHandler(logfile)
assert.NoErr(t, err)
l := slog.NewWithHandlers(h)
l.Info("info message")
l.Warn("warn message")
assert.True(t, fsutil.IsFile(logfile))
// assert.NoErr(t, os.Remove(logfile))
bts, err := os.ReadFile(logfile)
assert.NoErr(t, err)
str := string(bts)
assert.Contains(t, str, "[INFO]")
assert.Contains(t, str, slog.WarnLevel.Name())
}
================================================
FILE: handler/handler.go
================================================
// Package handler provide useful common log handlers.
//
// eg: file, console, multi_file, rotate_file, stream, syslog, email
package handler
import (
"io"
"os"
"sync"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/slog"
)
// DefaultBufferSize sizes the buffer associated with each log file. It's large
// so that log records can accumulate without the logging thread blocking
// on disk I/O. The flushDaemon will block instead.
var DefaultBufferSize = 8 * 1024
var (
// DefaultFilePerm perm and flags for create log file
DefaultFilePerm os.FileMode = 0664
// DefaultFileFlags for create/open file
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
)
// FlushWriter is the interface satisfied by logging destinations.
type FlushWriter interface {
Flush() error
// Writer the output writer
io.Writer
}
// FlushCloseWriter is the interface satisfied by logging destinations.
type FlushCloseWriter interface {
Flush() error
// WriteCloser the output writer
io.WriteCloser
}
// SyncCloseWriter is the interface satisfied by logging destinations.
// such as os.File
type SyncCloseWriter interface {
Sync() error
// WriteCloser the output writer
io.WriteCloser
}
/********************************************************************************
* Common parts for handler
********************************************************************************/
// LevelWithFormatter struct definition
//
// - support set log formatter
// - only support set one log level
//
// Deprecated: please use slog.LevelWithFormatter instead.
type LevelWithFormatter = slog.LevelWithFormatter
// LevelsWithFormatter struct definition
//
// - support set log formatter
// - support setting multi log levels
//
// Deprecated: please use slog.LevelsWithFormatter instead.
type LevelsWithFormatter = slog.LevelsWithFormatter
// NopFlushClose no operation.
//
// provide empty Flush(), Close() methods, useful for tests.
type NopFlushClose struct{}
// Flush logs to disk
func (h *NopFlushClose) Flush() error {
return nil
}
// Close handler
func (h *NopFlushClose) Close() error {
return nil
}
// LockWrapper struct
type LockWrapper struct {
sync.Mutex
disable bool
}
// Lock it
func (lw *LockWrapper) Lock() {
if !lw.disable {
lw.Mutex.Lock()
}
}
// Unlock it
func (lw *LockWrapper) Unlock() {
if !lw.disable {
lw.Mutex.Unlock()
}
}
// EnableLock enable lock
func (lw *LockWrapper) EnableLock(enable bool) {
lw.disable = !enable
}
// LockEnabled status
func (lw *LockWrapper) LockEnabled() bool {
return !lw.disable
}
// QuickOpenFile like os.OpenFile
func QuickOpenFile(filepath string) (*os.File, error) {
return fsutil.OpenFile(filepath, DefaultFileFlags, DefaultFilePerm)
}
================================================
FILE: handler/handler_test.go
================================================
package handler_test
import (
"fmt"
"testing"
"github.com/gookit/goutil"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/slog"
"github.com/gookit/slog/handler"
)
var (
sampleData = slog.M{
"name": "inhere",
"age": 100,
"skill": "go,php,java",
}
)
func TestMain(m *testing.M) {
fmt.Println("TestMain: remove all test files in ./testdata")
goutil.PanicErr(fsutil.RemoveSub("./testdata", fsutil.ExcludeNames(".keep")))
m.Run()
}
func TestConfig_CreateWriter(t *testing.T) {
cfg := handler.NewEmptyConfig()
w, err := cfg.CreateWriter()
assert.Nil(t, w)
assert.Err(t, err)
h, err := cfg.CreateHandler()
assert.Nil(t, h)
assert.Err(t, err)
logfile := "./testdata/file-by-config.log"
assert.NoErr(t, fsutil.DeleteIfFileExist(logfile))
cfg.With(
handler.WithBuffMode(handler.BuffModeBite),
handler.WithLogLevels(slog.NormalLevels),
handler.WithLogfile(logfile),
)
w, err = cfg.CreateWriter()
assert.NoErr(t, err)
_, err = w.Write([]byte("hello, config"))
assert.NoErr(t, err)
bts := fsutil.MustReadFile(logfile)
str := string(bts)
assert.Eq(t, str, "hello, config")
assert.NoErr(t, w.Sync())
assert.NoErr(t, w.Close())
}
func TestConfig_RotateWriter(t *testing.T) {
cfg := handler.NewEmptyConfig()
w, err := cfg.RotateWriter()
assert.Nil(t, w)
assert.Err(t, err)
}
func TestConsoleHandlerWithColor(t *testing.T) {
l := slog.NewWithHandlers(handler.ConsoleWithLevels(slog.AllLevels))
l.DoNothingOnPanicFatal()
l.Configure(func(l *slog.Logger) {
l.ReportCaller = true
})
logAllLevel(l, "this is a simple log message")
// logfAllLevel()
}
func TestConsoleHandlerNoColor(t *testing.T) {
h := handler.NewConsole(slog.AllLevels)
// no color
h.TextFormatter().EnableColor = false
l := slog.NewWithHandlers(h)
l.DoNothingOnPanicFatal()
l.ReportCaller = true
logAllLevel(l, "this is a simple log message")
}
func TestNewEmailHandler(t *testing.T) {
from := handler.EmailOption{
SMTPHost: "smtp.gmail.com",
SMTPPort: 587,
FromAddr: "someone@gmail.com",
}
h := handler.NewEmailHandler(from, []string{
"another@gmail.com",
})
assert.Eq(t, slog.InfoLevel, h.Level)
// handle error
h.SetFormatter(newTestFormatter(true))
assert.Err(t, h.Handle(newLogRecord("test email handler")))
}
func TestLevelWithFormatter(t *testing.T) {
lf := handler.LevelWithFormatter{Level: slog.InfoLevel}
assert.True(t, lf.IsHandling(slog.ErrorLevel))
assert.True(t, lf.IsHandling(slog.InfoLevel))
assert.False(t, lf.IsHandling(slog.DebugLevel))
}
func TestLevelsWithFormatter(t *testing.T) {
lsf := handler.LevelsWithFormatter{Levels: slog.NormalLevels}
assert.False(t, lsf.IsHandling(slog.ErrorLevel))
assert.True(t, lsf.IsHandling(slog.InfoLevel))
assert.True(t, lsf.IsHandling(slog.DebugLevel))
}
func TestNopFlushClose_Flush(t *testing.T) {
nfc := handler.NopFlushClose{}
assert.NoErr(t, nfc.Flush())
assert.NoErr(t, nfc.Close())
}
func TestLockWrapper_Lock(t *testing.T) {
lw := &handler.LockWrapper{}
assert.True(t, lw.LockEnabled())
lw.EnableLock(true)
assert.True(t, lw.LockEnabled())
a := 1
lw.Lock()
a++
lw.Unlock()
assert.Eq(t, 2, a)
}
func logAllLevel(log slog.SLogger, msg string) {
for _, level := range slog.AllLevels {
log.Log(level, msg)
}
}
func newLogRecord(msg string) *slog.Record {
r := &slog.Record{
Channel: "handler_test",
Level: slog.InfoLevel,
Message: msg,
Time: slog.DefaultClockFn.Now(),
Data: sampleData,
Extra: map[string]any{
"source": "linux",
"extra_key0": "hello",
"sub": slog.M{"sub_key1": "val0"},
},
}
r.Init(false)
return r
}
type testHandler struct {
errOnHandle bool
errOnFlush bool
errOnClose bool
}
func newTestHandler() *testHandler {
return &testHandler{}
}
// func (h testHandler) Reset() {
// h.errOnHandle = false
// h.errOnFlush = false
// h.errOnClose = false
// }
func (h testHandler) IsHandling(_ slog.Level) bool {
return true
}
func (h testHandler) Close() error {
if h.errOnClose {
return errorx.Raw("close error")
}
return nil
}
func (h testHandler) Flush() error {
if h.errOnFlush {
return errorx.Raw("flush error")
}
return nil
}
func (h testHandler) Handle(_ *slog.Record) error {
if h.errOnHandle {
return errorx.Raw("handle error")
}
return nil
}
type testFormatter struct {
errOnFormat bool
}
func newTestFormatter(errOnFormat ...bool) *testFormatter {
return &testFormatter{
errOnFormat: len(errOnFormat) > 0 && errOnFormat[0],
}
}
func (f testFormatter) Format(r *slog.Record) ([]byte, error) {
if f.errOnFormat {
return nil, errorx.Raw("format error")
}
return []byte(r.Message), nil
}
================================================
FILE: handler/rotatefile.go
================================================
package handler
import (
"github.com/gookit/goutil/x/basefn"
"github.com/gookit/slog/rotatefile"
)
// NewRotateFileHandler instance. It supports splitting log files by time and size
func NewRotateFileHandler(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error) {
cfg := NewConfig(fns...).With(WithLogfile(logfile), WithRotateTime(rt))
writer, err := cfg.RotateWriter()
if err != nil {
return nil, err
}
h := NewSyncCloseHandler(writer, cfg.Levels)
return h, nil
}
// MustRotateFile handler instance, will panic on create error
func MustRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) *SyncCloseHandler {
return basefn.Must(NewRotateFileHandler(logfile, rt, fns...))
}
// NewRotateFile instance. alias of NewRotateFileHandler()
func NewRotateFile(logfile string, rt rotatefile.RotateTime, fns ...ConfigFn) (*SyncCloseHandler, error) {
return NewRotateFileHandler(logfile, rt, fns...)
}
//
// ---------------------------------------------------------------------------
// rotate file by size
// ---------------------------------------------------------------------------
//
// MustSizeRotateFile instance
func MustSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) *SyncCloseHandler {
return basefn.Must(NewSizeRotateFileHandler(logfile, maxSize, fns...))
}
// NewSizeRotateFile instance
func NewSizeRotateFile(logfile string, maxSi
gitextract_ub2bczst/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ ├── changelog.yml │ ├── dependabot.yml │ ├── revive.toml │ └── workflows/ │ ├── go.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README.zh-CN.md ├── _example/ │ ├── bench_loglibs.md │ ├── bench_loglibs_test.go │ ├── demos/ │ │ ├── demo1.go │ │ ├── simple.go │ │ └── slog_all_level.go │ ├── diff-with-zap-zerolog.md │ ├── go.mod │ ├── handler/ │ │ └── grouped.go │ ├── issue100/ │ │ └── issue100_test.go │ ├── issue111/ │ │ └── main.go │ ├── issue137/ │ │ └── main.go │ ├── pprof/ │ │ └── main.go │ └── refer/ │ └── main.go ├── benchmark2_test.go ├── benchmark_test.go ├── bufwrite/ │ ├── bufio_writer.go │ ├── bufwrite_test.go │ └── line_writer.go ├── common.go ├── common_test.go ├── example_test.go ├── formatter.go ├── formatter_json.go ├── formatter_test.go ├── formatter_text.go ├── go.mod ├── go.sum ├── handler/ │ ├── README.md │ ├── buffer.go │ ├── buffer_test.go │ ├── builder.go │ ├── config.go │ ├── config_test.go │ ├── console.go │ ├── console_test.go │ ├── email.go │ ├── example_test.go │ ├── file.go │ ├── file_test.go │ ├── handler.go │ ├── handler_test.go │ ├── rotatefile.go │ ├── rotatefile_test.go │ ├── syslog.go │ ├── syslog_test.go │ ├── write_close_flusher.go │ ├── write_close_syncer.go │ ├── write_closer.go │ ├── writer.go │ └── writer_test.go ├── handler.go ├── handler_test.go ├── internal/ │ └── util.go ├── issues_test.go ├── logger.go ├── logger_sub.go ├── logger_test.go ├── logger_write.go ├── processor.go ├── processor_test.go ├── record.go ├── record_test.go ├── rotatefile/ │ ├── README.md │ ├── cleanup.go │ ├── cleanup_test.go │ ├── config.go │ ├── config_test.go │ ├── issues_test.go │ ├── rotatefile.go │ ├── rotatefile_test.go │ ├── util.go │ ├── util_test.go │ ├── writer.go │ └── writer_test.go ├── slog.go ├── slog_test.go ├── sugared.go ├── util.go └── util_test.go
SYMBOL INDEX (932 symbols across 72 files)
FILE: _example/bench_loglibs_test.go
function BenchmarkGoSlogNegative (line 26) | func BenchmarkGoSlogNegative(b *testing.B) {
function BenchmarkZapNegative (line 38) | func BenchmarkZapNegative(b *testing.B) {
function BenchmarkZapSugarNegative (line 52) | func BenchmarkZapSugarNegative(b *testing.B) {
function BenchmarkZeroLogNegative (line 70) | func BenchmarkZeroLogNegative(b *testing.B) {
function BenchmarkPhusLogNegative (line 80) | func BenchmarkPhusLogNegative(b *testing.B) {
function BenchmarkLogrusNegative (line 91) | func BenchmarkLogrusNegative(b *testing.B) {
function BenchmarkGookitSlogNegative (line 103) | func BenchmarkGookitSlogNegative(b *testing.B) {
function BenchmarkZapPositive (line 121) | func BenchmarkZapPositive(b *testing.B) {
function BenchmarkZapSugarPositive (line 135) | func BenchmarkZapSugarPositive(b *testing.B) {
function BenchmarkZeroLogPositive (line 149) | func BenchmarkZeroLogPositive(b *testing.B) {
function BenchmarkPhusLogPositive (line 159) | func BenchmarkPhusLogPositive(b *testing.B) {
function BenchmarkLogrusPositive (line 169) | func BenchmarkLogrusPositive(b *testing.B) {
function BenchmarkGookitSlogPositive (line 181) | func BenchmarkGookitSlogPositive(b *testing.B) {
FILE: _example/demos/demo1.go
constant simplestTemplate (line 7) | simplestTemplate = "[{{datetime}}] [{{level}}] {{message}} {{data}} {{ex...
function init (line 9) | func init() {
function main (line 15) | func main() {
FILE: _example/demos/simple.go
function main (line 8) | func main() {
function stackIt (line 15) | func stackIt() int {
function stackIt2 (line 21) | func stackIt2() *int {
function slogTest (line 27) | func slogTest() {
FILE: _example/demos/slog_all_level.go
function main (line 12) | func main() {
function printAllLevel (line 21) | func printAllLevel(l *slog.Logger, args ...any) {
FILE: _example/handler/grouped.go
type GroupedHandler (line 10) | type GroupedHandler struct
method IsHandling (line 26) | func (h *GroupedHandler) IsHandling(level slog.Level) bool {
method Handle (line 36) | func (h *GroupedHandler) Handle(record *slog.Record) (err error) {
method Close (line 47) | func (h *GroupedHandler) Close() error {
method Flush (line 58) | func (h *GroupedHandler) Flush() error {
function NewGroupedHandler (line 19) | func NewGroupedHandler(handlers []slog.Handler) *GroupedHandler {
FILE: _example/issue100/issue100_test.go
type Obj (line 15) | type Obj struct
function TestZapSugar (line 30) | func TestZapSugar(t *testing.T) {
function TestZapLog (line 64) | func TestZapLog(t *testing.T) {
function TestSlog (line 103) | func TestSlog(t *testing.T) {
FILE: _example/issue111/main.go
constant pth (line 15) | pth = "./logs/main.log"
function main (line 17) | func main() {
FILE: _example/issue137/main.go
type GLogConfig137 (line 13) | type GLogConfig137 struct
type LogRotateConfig137 (line 22) | type LogRotateConfig137 struct
type LogConfig137 (line 35) | type LogConfig137 struct
function main (line 41) | func main() {
FILE: _example/pprof/main.go
function main (line 25) | func main() {
FILE: _example/refer/main.go
function main (line 20) | func main() {
FILE: benchmark2_test.go
function TestLogger_newRecord_AllocTimes (line 11) | func TestLogger_newRecord_AllocTimes(_ *testing.T) {
function Test_AllocTimes_formatArgsWithSpaces_oneElem (line 25) | func Test_AllocTimes_formatArgsWithSpaces_oneElem(_ *testing.T) {
function Test_AllocTimes_formatArgsWithSpaces_manyElem (line 43) | func Test_AllocTimes_formatArgsWithSpaces_manyElem(_ *testing.T) {
function Test_AllocTimes_stringsPool (line 55) | func Test_AllocTimes_stringsPool(_ *testing.T) {
function TestLogger_Info_oneElem_AllocTimes (line 83) | func TestLogger_Info_oneElem_AllocTimes(_ *testing.T) {
function TestLogger_Info_moreElem_AllocTimes (line 100) | func TestLogger_Info_moreElem_AllocTimes(_ *testing.T) {
FILE: benchmark_test.go
function BenchmarkGookitSlogNegative (line 19) | func BenchmarkGookitSlogNegative(b *testing.B) {
function TestLogger_Info_Negative (line 32) | func TestLogger_Info_Negative(t *testing.T) {
function BenchmarkGookitSlogPositive (line 40) | func BenchmarkGookitSlogPositive(b *testing.B) {
function BenchmarkTextFormatter_Format (line 53) | func BenchmarkTextFormatter_Format(b *testing.B) {
function TestLogger_Info_Positive (line 78) | func TestLogger_Info_Positive(t *testing.T) {
FILE: bufwrite/bufio_writer.go
type BufIOWriter (line 10) | type BufIOWriter struct
method Close (line 30) | func (w *BufIOWriter) Close() error {
method Sync (line 43) | func (w *BufIOWriter) Sync() error {
function NewBufIOWriterSize (line 17) | func NewBufIOWriterSize(w io.Writer, size int) *BufIOWriter {
function NewBufIOWriter (line 25) | func NewBufIOWriter(w io.Writer) *BufIOWriter {
FILE: bufwrite/bufwrite_test.go
function TestNewBufIOWriter_WriteString (line 12) | func TestNewBufIOWriter_WriteString(t *testing.T) {
type closeWriter (line 28) | type closeWriter struct
method Close (line 34) | func (w *closeWriter) Close() error {
method Write (line 41) | func (w *closeWriter) Write(p []byte) (n int, err error) {
function TestBufIOWriter_Close_error (line 52) | func TestBufIOWriter_Close_error(t *testing.T) {
function TestBufIOWriter_Sync (line 70) | func TestBufIOWriter_Sync(t *testing.T) {
function TestNewLineWriter (line 83) | func TestNewLineWriter(t *testing.T) {
function TestLineWriter_Write_error (line 100) | func TestLineWriter_Write_error(t *testing.T) {
function TestLineWriter_Flush_error (line 134) | func TestLineWriter_Flush_error(t *testing.T) {
function TestLineWriter_Close_error (line 190) | func TestLineWriter_Close_error(t *testing.T) {
function TestNewLineWriterSize (line 211) | func TestNewLineWriterSize(t *testing.T) {
FILE: bufwrite/line_writer.go
constant defaultBufSize (line 8) | defaultBufSize = 1024 * 8
type LineWriter (line 23) | type LineWriter struct
method Size (line 55) | func (b *LineWriter) Size() int { return len(b.buf) }
method Reset (line 59) | func (b *LineWriter) Reset(w io.Writer) {
method Close (line 67) | func (b *LineWriter) Close() error {
method Sync (line 80) | func (b *LineWriter) Sync() error {
method Flush (line 87) | func (b *LineWriter) Flush() error {
method Available (line 113) | func (b *LineWriter) Available() int { return len(b.buf) - b.n }
method Buffered (line 116) | func (b *LineWriter) Buffered() int { return b.n }
method Write (line 122) | func (b *LineWriter) Write(p []byte) (nn int, err error) {
method WriteString (line 171) | func (b *LineWriter) WriteString(s string) (int, error) {
function NewLineWriterSize (line 33) | func NewLineWriterSize(w io.Writer, size int) *LineWriter {
function NewLineWriter (line 50) | func NewLineWriter(w io.Writer) *LineWriter {
FILE: common.go
type SLogger (line 15) | type SLogger interface
type LoggerFn (line 22) | type LoggerFn
type Level (line 29) | type Level
method String (line 32) | func (l Level) String() string { return LevelName(l) }
method Name (line 35) | func (l Level) Name() string { return LevelName(l) }
method LowerName (line 38) | func (l Level) LowerName() string {
method ShouldHandling (line 46) | func (l Level) ShouldHandling(curLevel Level) bool {
method MarshalJSON (line 51) | func (l Level) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 56) | func (l *Level) UnmarshalJSON(data []byte) error {
type Levels (line 67) | type Levels
method Contains (line 70) | func (ls Levels) Contains(level Level) bool {
constant PanicLevel (line 82) | PanicLevel Level = 100
constant FatalLevel (line 85) | FatalLevel Level = 200
constant ErrorLevel (line 88) | ErrorLevel Level = 300
constant WarnLevel (line 90) | WarnLevel Level = 400
constant NoticeLevel (line 92) | NoticeLevel Level = 500
constant InfoLevel (line 94) | InfoLevel Level = 600
constant DebugLevel (line 96) | DebugLevel Level = 700
constant TraceLevel (line 98) | TraceLevel Level = 800
type M (line 109) | type M
method String (line 112) | func (m M) String() string {
type ClockFn (line 117) | type ClockFn
method Now (line 120) | func (fn ClockFn) Now() time.Time {
constant CallerFlagFnlFcn (line 134) | CallerFlagFnlFcn CallerFlagMode = iota
constant CallerFlagFull (line 137) | CallerFlagFull
constant CallerFlagFunc (line 140) | CallerFlagFunc
constant CallerFlagFcLine (line 143) | CallerFlagFcLine
constant CallerFlagPkg (line 146) | CallerFlagPkg
constant CallerFlagPkgFnl (line 149) | CallerFlagPkgFnl
constant CallerFlagFpLine (line 152) | CallerFlagFpLine
constant CallerFlagFnLine (line 155) | CallerFlagFnLine
constant CallerFlagFcName (line 158) | CallerFlagFcName
function LevelName (line 258) | func LevelName(l Level) string {
function LevelByName (line 266) | func LevelByName(ln string) Level {
function Name2Level (line 275) | func Name2Level(s string) (Level, error) { return StringToLevel(s) }
function StringToLevel (line 278) | func StringToLevel(s string) (Level, error) {
function runExitHandlers (line 313) | func runExitHandlers() {
function ExitHandlers (line 326) | func ExitHandlers() []func() {
function RegisterExitHandler (line 331) | func RegisterExitHandler(handler func()) {
function PrependExitHandler (line 336) | func PrependExitHandler(handler func()) {
function ResetExitHandlers (line 341) | func ResetExitHandlers(applyToStd bool) {
FILE: common_test.go
function TestDefine_basic (line 24) | func TestDefine_basic(t *testing.T) {
function TestM_String (line 32) | func TestM_String(t *testing.T) {
function TestLevelName_func (line 51) | func TestLevelName_func(t *testing.T) {
function TestName2Level (line 64) | func TestName2Level(t *testing.T) {
function TestLevel_methods (line 92) | func TestLevel_methods(t *testing.T) {
function TestLevels_Contains (line 125) | func TestLevels_Contains(t *testing.T) {
function newLogRecord (line 132) | func newLogRecord(msg string) *slog.Record {
type closedBuffer (line 153) | type closedBuffer struct
method Close (line 161) | func (w *closedBuffer) Close() error {
method StringReset (line 165) | func (w *closedBuffer) StringReset() string {
function newBuffer (line 157) | func newBuffer() *closedBuffer {
type testHandler (line 175) | type testHandler struct
method IsHandling (line 195) | func (h *testHandler) IsHandling(_ slog.Level) bool {
method Close (line 199) | func (h *testHandler) Close() error {
method Flush (line 208) | func (h *testHandler) Flush() error {
method Handle (line 222) | func (h *testHandler) Handle(r *slog.Record) error {
function newTestHandler (line 191) | func newTestHandler() *testHandler {
type testFormatter (line 247) | type testFormatter struct
method Format (line 257) | func (f testFormatter) Format(r *slog.Record) ([]byte, error) {
function newTestFormatter (line 251) | func newTestFormatter(errOnFormat ...bool) *testFormatter {
function newLogger (line 268) | func newLogger() *slog.Logger {
function newTestLogger (line 276) | func newTestLogger() (*closedBuffer, *slog.Logger) {
function printAllLevelLogs (line 288) | func printAllLevelLogs(l gsr.Logger, args ...any) {
function printfAllLevelLogs (line 309) | func printfAllLevelLogs(l gsr.Logger, tpl string, args ...any) {
FILE: example_test.go
function Example_quickStart (line 12) | func Example_quickStart() {
function Example_configSlog (line 19) | func Example_configSlog() {
function Example_useJSONFormat (line 34) | func Example_useJSONFormat() {
function ExampleNew (line 53) | func ExampleNew() {
function ExampleFlushDaemon (line 64) | func ExampleFlushDaemon() {
FILE: formatter.go
type Formatter (line 10) | type Formatter interface
type FormatterFunc (line 16) | type FormatterFunc
method Format (line 19) | func (fn FormatterFunc) Format(r *Record) ([]byte, error) {
type Formattable (line 24) | type Formattable interface
type FormatterWrapper (line 37) | type FormatterWrapper struct
method Formatter (line 43) | func (f *FormatterWrapper) Formatter() Formatter {
method SetFormatter (line 51) | func (f *FormatterWrapper) SetFormatter(formatter Formatter) {
method Format (line 56) | func (f *FormatterWrapper) Format(record *Record) ([]byte, error) {
type CallerFormatFn (line 61) | type CallerFormatFn
function AsTextFormatter (line 64) | func AsTextFormatter(f Formatter) *TextFormatter {
function AsJSONFormatter (line 72) | func AsJSONFormatter(f Formatter) *JSONFormatter {
FILE: formatter_json.go
type JSONFormatter (line 32) | type JSONFormatter struct
method Configure (line 65) | func (f *JSONFormatter) Configure(fn func(*JSONFormatter)) *JSONFormat...
method AddField (line 71) | func (f *JSONFormatter) AddField(name string) *JSONFormatter {
method Format (line 79) | func (f *JSONFormatter) Format(r *Record) ([]byte, error) {
function NewJSONFormatter (line 51) | func NewJSONFormatter(fn ...func(f *JSONFormatter)) *JSONFormatter {
FILE: formatter_test.go
function TestFormattableTrait_Formatter (line 16) | func TestFormattableTrait_Formatter(t *testing.T) {
function TestFormattable_Format (line 32) | func TestFormattable_Format(t *testing.T) {
function TestNewTextFormatter (line 54) | func TestNewTextFormatter(t *testing.T) {
function TestTextFormatter_Format (line 91) | func TestTextFormatter_Format(t *testing.T) {
function TestTextFormatter_ColorRenderFunc (line 105) | func TestTextFormatter_ColorRenderFunc(t *testing.T) {
function TestTextFormatter_LimitLevelNameLen (line 120) | func TestTextFormatter_LimitLevelNameLen(t *testing.T) {
function TestTextFormatter_LimitLevelNameLen2 (line 145) | func TestTextFormatter_LimitLevelNameLen2(t *testing.T) {
function TestNewJSONFormatter (line 171) | func TestNewJSONFormatter(t *testing.T) {
FILE: formatter_text.go
constant DefaultTemplate (line 11) | DefaultTemplate = "[{{datetime}}] [{{channel}}] [{{level}}] [{{caller}}]...
constant NamedTemplate (line 12) | NamedTemplate = "{{datetime}} channel={{channel}} level={{level}} [fil...
type TextFormatter (line 28) | type TextFormatter struct
method Configure (line 103) | func (f *TextFormatter) Configure(fn TextFormatterFn) *TextFormatter {
method WithOptions (line 108) | func (f *TextFormatter) WithOptions(fns ...TextFormatterFn) *TextForma...
method SetTemplate (line 116) | func (f *TextFormatter) SetTemplate(fmtTpl string) {
method Template (line 122) | func (f *TextFormatter) Template() string {
method WithEnableColor (line 127) | func (f *TextFormatter) WithEnableColor(enable bool) *TextFormatter {
method Fields (line 133) | func (f *TextFormatter) Fields() []string {
method Format (line 148) | func (f *TextFormatter) Format(r *Record) ([]byte, error) {
method beforeFormat (line 216) | func (f *TextFormatter) beforeFormat() {
method renderColorText (line 226) | func (f *TextFormatter) renderColorText(field, s string, l Level) stri...
type TextFormatterFn (line 62) | type TextFormatterFn
function NewTextFormatter (line 65) | func NewTextFormatter(template ...string) *TextFormatter {
function TextFormatterWith (line 89) | func TextFormatterWith(fns ...TextFormatterFn) *TextFormatter {
function LimitLevelNameLen (line 94) | func LimitLevelNameLen(length int) TextFormatterFn {
FILE: handler.go
type Handler (line 16) | type Handler interface
type LevelFormattable (line 33) | type LevelFormattable interface
type FormattableHandler (line 39) | type FormattableHandler interface
type LevelWithFormatter (line 52) | type LevelWithFormatter struct
method SetMaxLevel (line 64) | func (h *LevelWithFormatter) SetMaxLevel(maxLv Level) {
method IsHandling (line 69) | func (h *LevelWithFormatter) IsHandling(level Level) bool {
function NewLvFormatter (line 59) | func NewLvFormatter(maxLv Level) *LevelWithFormatter {
type LevelsWithFormatter (line 77) | type LevelsWithFormatter struct
method SetLimitLevels (line 89) | func (h *LevelsWithFormatter) SetLimitLevels(levels []Level) {
method IsHandling (line 94) | func (h *LevelsWithFormatter) IsHandling(level Level) bool {
function NewLvsFormatter (line 84) | func NewLvsFormatter(levels []Level) *LevelsWithFormatter {
type LevelMode (line 104) | type LevelMode
method MarshalJSON (line 107) | func (m LevelMode) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 112) | func (m *LevelMode) UnmarshalJSON(data []byte) error {
method String (line 123) | func (m LevelMode) String() string {
constant LevelModeList (line 136) | LevelModeList LevelMode = iota
constant LevelModeMax (line 138) | LevelModeMax
function SafeToLevelMode (line 142) | func SafeToLevelMode(s string) LevelMode {
function StringToLevelMode (line 151) | func StringToLevelMode(s string) (LevelMode, error) {
type LevelHandling (line 170) | type LevelHandling struct
method SetMaxLevel (line 180) | func (h *LevelHandling) SetMaxLevel(maxLv Level) {
method SetLimitLevels (line 186) | func (h *LevelHandling) SetLimitLevels(levels []Level) {
method IsHandling (line 192) | func (h *LevelHandling) IsHandling(level Level) bool {
type LevelFormatting (line 206) | type LevelFormatting struct
function NewMaxLevelFormatting (line 212) | func NewMaxLevelFormatting(maxLevel Level) *LevelFormatting {
function NewLevelsFormatting (line 219) | func NewLevelsFormatting(levels []Level) *LevelFormatting {
FILE: handler/buffer.go
function NewBuffered (line 12) | func NewBuffered(w io.WriteCloser, bufSize int, levels ...slog.Level) *F...
function NewBufferedHandler (line 17) | func NewBufferedHandler(w io.WriteCloser, bufSize int, levels ...slog.Le...
function LineBufferedFile (line 27) | func LineBufferedFile(logfile string, bufSize int, levels []slog.Level) ...
function LineBuffOsFile (line 43) | func LineBuffOsFile(f *os.File, bufSize int, levels []slog.Level) slog.H...
function LineBuffWriter (line 53) | func LineBuffWriter(w io.Writer, bufSize int, levels []slog.Level) slog....
type FormatWriterHandler (line 67) | type FormatWriterHandler interface
FILE: handler/buffer_test.go
function TestNewBufferedHandler (line 13) | func TestNewBufferedHandler(t *testing.T) {
function TestLineBufferedFile (line 42) | func TestLineBufferedFile(t *testing.T) {
function TestLineBuffOsFile (line 62) | func TestLineBuffOsFile(t *testing.T) {
function TestLineBuffWriter (line 89) | func TestLineBuffWriter(t *testing.T) {
FILE: handler/builder.go
type Builder (line 17) | type Builder struct
method WithOutput (line 30) | func (b *Builder) WithOutput(w io.Writer) *Builder {
method With (line 38) | func (b *Builder) With(fns ...ConfigFn) *Builder {
method WithConfigFn (line 43) | func (b *Builder) WithConfigFn(fns ...ConfigFn) *Builder {
method WithLogfile (line 49) | func (b *Builder) WithLogfile(logfile string) *Builder {
method WithLevelMode (line 55) | func (b *Builder) WithLevelMode(mode slog.LevelMode) *Builder {
method WithLogLevel (line 61) | func (b *Builder) WithLogLevel(level slog.Level) *Builder {
method WithLogLevels (line 68) | func (b *Builder) WithLogLevels(levels []slog.Level) *Builder {
method WithBuffMode (line 75) | func (b *Builder) WithBuffMode(bufMode string) *Builder {
method WithBuffSize (line 81) | func (b *Builder) WithBuffSize(bufSize int) *Builder {
method WithMaxSize (line 87) | func (b *Builder) WithMaxSize(maxSize uint64) *Builder {
method WithRotateTime (line 93) | func (b *Builder) WithRotateTime(rt rotatefile.RotateTime) *Builder {
method WithCompress (line 99) | func (b *Builder) WithCompress(compress bool) *Builder {
method WithUseJSON (line 105) | func (b *Builder) WithUseJSON(useJSON bool) *Builder {
method Build (line 111) | func (b *Builder) Build() slog.FormattableHandler {
method buildFromWriter (line 128) | func (b *Builder) buildFromWriter(w io.Writer) (h slog.FormattableHand...
method reset (line 167) | func (b *Builder) reset() {
function NewBuilder (line 23) | func NewBuilder() *Builder {
FILE: handler/config.go
constant BuffModeLine (line 18) | BuffModeLine = "line"
constant BuffModeBite (line 19) | BuffModeBite = "bite"
constant LevelModeList (line 24) | LevelModeList = slog.LevelModeList
constant LevelModeValue (line 26) | LevelModeValue = slog.LevelModeMax
type ConfigFn (line 30) | type ConfigFn
type Config (line 33) | type Config struct
method FromJSON (line 123) | func (c *Config) FromJSON(bts []byte) error { return json.Unmarshal(bt...
method With (line 126) | func (c *Config) With(fns ...ConfigFn) *Config { return c.WithConfigFn...
method WithConfigFn (line 129) | func (c *Config) WithConfigFn(fns ...ConfigFn) *Config {
method newLevelFormattable (line 136) | func (c *Config) newLevelFormattable() slog.LevelFormattable {
method CreateHandler (line 144) | func (c *Config) CreateHandler() (*SyncCloseHandler, error) {
method RotateWriter (line 163) | func (c *Config) RotateWriter() (output SyncCloseWriter, err error) {
method CreateWriter (line 172) | func (c *Config) CreateWriter() (output SyncCloseWriter, err error) {
method wrapBuffer (line 229) | func (c *Config) wrapBuffer(w io.Writer) (bw flushSyncCloseWriter) {
function NewEmptyConfig (line 99) | func NewEmptyConfig(fns ...ConfigFn) *Config {
function NewConfig (line 105) | func NewConfig(fns ...ConfigFn) *Config {
type flushSyncCloseWriter (line 223) | type flushSyncCloseWriter interface
function WithLogfile (line 245) | func WithLogfile(logfile string) ConfigFn {
function WithFilePerm (line 250) | func WithFilePerm(filePerm fs.FileMode) ConfigFn {
function WithLevelMode (line 255) | func WithLevelMode(lm slog.LevelMode) ConfigFn {
function WithLevelModeString (line 260) | func WithLevelModeString(s string) ConfigFn {
function WithLogLevel (line 265) | func WithLogLevel(level slog.Level) ConfigFn {
function WithLevelName (line 273) | func WithLevelName(name string) ConfigFn { return WithLogLevel(slog.Leve...
function WithMaxLevelName (line 276) | func WithMaxLevelName(name string) ConfigFn { return WithLogLevel(slog.L...
function WithLogLevels (line 279) | func WithLogLevels(levels slog.Levels) ConfigFn {
function WithLevelNamesString (line 287) | func WithLevelNamesString(names string) ConfigFn {
function WithLevelNames (line 292) | func WithLevelNames(names []string) ConfigFn {
function WithRotateTime (line 301) | func WithRotateTime(rt rotatefile.RotateTime) ConfigFn {
function WithRotateTimeString (line 308) | func WithRotateTimeString(s string) ConfigFn {
function WithRotateMode (line 319) | func WithRotateMode(m rotatefile.RotateMode) ConfigFn {
function WithRotateModeString (line 324) | func WithRotateModeString(s string) ConfigFn {
function WithTimeClock (line 335) | func WithTimeClock(clock rotatefile.Clocker) ConfigFn {
function WithBackupNum (line 340) | func WithBackupNum(n uint) ConfigFn {
function WithBackupTime (line 345) | func WithBackupTime(bt uint) ConfigFn {
function WithBuffMode (line 350) | func WithBuffMode(buffMode string) ConfigFn {
function WithBuffSize (line 355) | func WithBuffSize(buffSize int) ConfigFn {
function WithMaxSize (line 360) | func WithMaxSize(maxSize uint64) ConfigFn {
function WithCompress (line 365) | func WithCompress(compress bool) ConfigFn {
function WithUseJSON (line 370) | func WithUseJSON(useJSON bool) ConfigFn {
function WithDebugMode (line 375) | func WithDebugMode(c *Config) { c.DebugMode = true }
FILE: handler/config_test.go
function TestNewConfig (line 17) | func TestNewConfig(t *testing.T) {
function TestConfig_fromJSON (line 47) | func TestConfig_fromJSON(t *testing.T) {
function TestWithLevelNamesString (line 72) | func TestWithLevelNamesString(t *testing.T) {
function TestWithMaxLevelName (line 77) | func TestWithMaxLevelName(t *testing.T) {
function TestWithRotateMode (line 87) | func TestWithRotateMode(t *testing.T) {
function TestWithRotateTimeString (line 99) | func TestWithRotateTimeString(t *testing.T) {
function TestNewBuilder (line 131) | func TestNewBuilder(t *testing.T) {
type simpleWriter (line 171) | type simpleWriter struct
method Write (line 175) | func (w *simpleWriter) Write(p []byte) (n int, err error) {
type closeWriter (line 182) | type closeWriter struct
method Close (line 187) | func (w *closeWriter) Close() error {
method Write (line 194) | func (w *closeWriter) Write(p []byte) (n int, err error) {
type flushCloseWriter (line 201) | type flushCloseWriter struct
method Flush (line 207) | func (w *flushCloseWriter) Flush() error {
type syncCloseWriter (line 214) | type syncCloseWriter struct
method Sync (line 220) | func (w *syncCloseWriter) Sync() error {
function TestNewBuilder_buildFromWriter (line 227) | func TestNewBuilder_buildFromWriter(t *testing.T) {
FILE: handler/console.go
function NewConsoleWithLF (line 18) | func NewConsoleWithLF(lf slog.LevelFormattable) *ConsoleHandler {
function ConsoleWithMaxLevel (line 35) | func ConsoleWithMaxLevel(level slog.Level) *ConsoleHandler {
function NewConsole (line 44) | func NewConsole(levels []slog.Level) *ConsoleHandler {
function ConsoleWithLevels (line 49) | func ConsoleWithLevels(levels []slog.Level) *ConsoleHandler {
function NewConsoleHandler (line 54) | func NewConsoleHandler(levels []slog.Level) *ConsoleHandler {
FILE: handler/console_test.go
function TestConsoleWithMaxLevel (line 11) | func TestConsoleWithMaxLevel(t *testing.T) {
FILE: handler/email.go
type EmailOption (line 11) | type EmailOption struct
type EmailHandler (line 19) | type EmailHandler struct
method Handle (line 42) | func (h *EmailHandler) Handle(r *slog.Record) error {
function NewEmailHandler (line 29) | func NewEmailHandler(from EmailOption, toAddresses []string) *EmailHandl...
FILE: handler/example_test.go
function Example_fileHandler (line 8) | func Example_fileHandler() {
function Example_rotateFileHandler (line 23) | func Example_rotateFileHandler() {
FILE: handler/file.go
function JSONFileHandler (line 9) | func JSONFileHandler(logfile string, fns ...ConfigFn) (*SyncCloseHandler...
function NewBuffFileHandler (line 14) | func NewBuffFileHandler(logfile string, buffSize int, fns ...ConfigFn) (...
function MustFileHandler (line 19) | func MustFileHandler(logfile string, fns ...ConfigFn) *SyncCloseHandler {
function NewFileHandler (line 24) | func NewFileHandler(logfile string, fns ...ConfigFn) (h *SyncCloseHandle...
function MustSimpleFile (line 33) | func MustSimpleFile(filepath string, maxLv ...slog.Level) *SyncCloseHand...
function NewSimpleFile (line 38) | func NewSimpleFile(filepath string, maxLv ...slog.Level) (*SyncCloseHand...
function NewSimpleFileHandler (line 53) | func NewSimpleFileHandler(filePath string, maxLv ...slog.Level) (*SyncCl...
FILE: handler/file_test.go
function TestNewFileHandler (line 15) | func TestNewFileHandler(t *testing.T) {
function TestMustFileHandler (line 39) | func TestMustFileHandler(t *testing.T) {
function TestNewFileHandler_basic (line 58) | func TestNewFileHandler_basic(t *testing.T) {
function TestNewBuffFileHandler (line 79) | func TestNewBuffFileHandler(t *testing.T) {
function TestJSONFileHandler (line 100) | func TestJSONFileHandler(t *testing.T) {
function TestSimpleFile (line 121) | func TestSimpleFile(t *testing.T) {
function TestNewSimpleFileHandler (line 135) | func TestNewSimpleFileHandler(t *testing.T) {
FILE: handler/handler.go
type FlushWriter (line 28) | type FlushWriter interface
type FlushCloseWriter (line 35) | type FlushCloseWriter interface
type SyncCloseWriter (line 43) | type SyncCloseWriter interface
type NopFlushClose (line 72) | type NopFlushClose struct
method Flush (line 75) | func (h *NopFlushClose) Flush() error {
method Close (line 80) | func (h *NopFlushClose) Close() error {
type LockWrapper (line 85) | type LockWrapper struct
method Lock (line 91) | func (lw *LockWrapper) Lock() {
method Unlock (line 98) | func (lw *LockWrapper) Unlock() {
method EnableLock (line 105) | func (lw *LockWrapper) EnableLock(enable bool) {
method LockEnabled (line 110) | func (lw *LockWrapper) LockEnabled() bool {
function QuickOpenFile (line 115) | func QuickOpenFile(filepath string) (*os.File, error) {
FILE: handler/handler_test.go
function TestMain (line 23) | func TestMain(m *testing.M) {
function TestConfig_CreateWriter (line 29) | func TestConfig_CreateWriter(t *testing.T) {
function TestConfig_RotateWriter (line 63) | func TestConfig_RotateWriter(t *testing.T) {
function TestConsoleHandlerWithColor (line 71) | func TestConsoleHandlerWithColor(t *testing.T) {
function TestConsoleHandlerNoColor (line 82) | func TestConsoleHandlerNoColor(t *testing.T) {
function TestNewEmailHandler (line 94) | func TestNewEmailHandler(t *testing.T) {
function TestLevelWithFormatter (line 112) | func TestLevelWithFormatter(t *testing.T) {
function TestLevelsWithFormatter (line 120) | func TestLevelsWithFormatter(t *testing.T) {
function TestNopFlushClose_Flush (line 128) | func TestNopFlushClose_Flush(t *testing.T) {
function TestLockWrapper_Lock (line 135) | func TestLockWrapper_Lock(t *testing.T) {
function logAllLevel (line 149) | func logAllLevel(log slog.SLogger, msg string) {
function newLogRecord (line 155) | func newLogRecord(msg string) *slog.Record {
type testHandler (line 173) | type testHandler struct
method IsHandling (line 189) | func (h testHandler) IsHandling(_ slog.Level) bool {
method Close (line 193) | func (h testHandler) Close() error {
method Flush (line 200) | func (h testHandler) Flush() error {
method Handle (line 207) | func (h testHandler) Handle(_ *slog.Record) error {
function newTestHandler (line 179) | func newTestHandler() *testHandler {
type testFormatter (line 214) | type testFormatter struct
method Format (line 224) | func (f testFormatter) Format(r *slog.Record) ([]byte, error) {
function newTestFormatter (line 218) | func newTestFormatter(errOnFormat ...bool) *testFormatter {
FILE: handler/rotatefile.go
function NewRotateFileHandler (line 9) | func NewRotateFileHandler(logfile string, rt rotatefile.RotateTime, fns ...
function MustRotateFile (line 22) | func MustRotateFile(logfile string, rt rotatefile.RotateTime, fns ...Con...
function NewRotateFile (line 27) | func NewRotateFile(logfile string, rt rotatefile.RotateTime, fns ...Conf...
function MustSizeRotateFile (line 38) | func MustSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) *S...
function NewSizeRotateFile (line 43) | func NewSizeRotateFile(logfile string, maxSize int, fns ...ConfigFn) (*S...
function NewSizeRotateFileHandler (line 48) | func NewSizeRotateFileHandler(logfile string, maxSize int, fns ...Config...
constant EveryDay (line 75) | EveryDay = rotatefile.EveryDay
constant EveryHour (line 76) | EveryHour = rotatefile.EveryDay
constant Every30Minutes (line 78) | Every30Minutes = rotatefile.Every30Min
constant Every15Minutes (line 79) | Every15Minutes = rotatefile.Every15Min
constant EveryMinute (line 81) | EveryMinute = rotatefile.EveryMinute
constant EverySecond (line 82) | EverySecond = rotatefile.EverySecond
function MustTimeRotateFile (line 86) | func MustTimeRotateFile(logfile string, rt rotatefile.RotateTime, fns .....
function NewTimeRotateFile (line 91) | func NewTimeRotateFile(logfile string, rt rotatefile.RotateTime, fns ......
function NewTimeRotateFileHandler (line 96) | func NewTimeRotateFileHandler(logfile string, rt rotatefile.RotateTime, ...
FILE: handler/rotatefile_test.go
function TestNewRotateFileHandler (line 18) | func TestNewRotateFileHandler(t *testing.T) {
function TestNewSizeRotateFileHandler (line 56) | func TestNewSizeRotateFileHandler(t *testing.T) {
function TestNewTimeRotateFileHandler_EveryDay (line 90) | func TestNewTimeRotateFileHandler_EveryDay(t *testing.T) {
function TestNewTimeRotateFileHandler_EveryHour (line 119) | func TestNewTimeRotateFileHandler_EveryHour(t *testing.T) {
function TestNewTimeRotateFileHandler_someSeconds (line 149) | func TestNewTimeRotateFileHandler_someSeconds(t *testing.T) {
function checkLogFileContents (line 170) | func checkLogFileContents(t *testing.T, logfile string) {
FILE: handler/syslog.go
type SysLogOpt (line 12) | type SysLogOpt struct
type SysLogHandler (line 24) | type SysLogHandler struct
method Handle (line 54) | func (h *SysLogHandler) Handle(record *slog.Record) error {
method Close (line 82) | func (h *SysLogHandler) Close() error {
method Flush (line 87) | func (h *SysLogHandler) Flush() error {
function NewSysLogHandler (line 30) | func NewSysLogHandler(priority syslog.Priority, tag string) (*SysLogHand...
function NewSysLog (line 38) | func NewSysLog(opt *SysLogOpt) (*SysLogHandler, error) {
FILE: handler/syslog_test.go
function TestNewSysLogHandler (line 13) | func TestNewSysLogHandler(t *testing.T) {
FILE: handler/write_close_flusher.go
type FlushCloseHandler (line 8) | type FlushCloseHandler struct
method Close (line 59) | func (h *FlushCloseHandler) Close() error {
method Flush (line 67) | func (h *FlushCloseHandler) Flush() error {
method Handle (line 72) | func (h *FlushCloseHandler) Handle(record *slog.Record) error {
function NewFlushCloserWithLF (line 14) | func NewFlushCloserWithLF(out FlushCloseWriter, lf slog.LevelFormattable...
function FlushCloserWithMaxLevel (line 27) | func FlushCloserWithMaxLevel(out FlushCloseWriter, maxLevel slog.Level) ...
function NewFlushCloser (line 36) | func NewFlushCloser(out FlushCloseWriter, levels []slog.Level) *FlushClo...
function FlushCloserWithLevels (line 41) | func FlushCloserWithLevels(out FlushCloseWriter, levels []slog.Level) *F...
function NewFlushCloseHandler (line 54) | func NewFlushCloseHandler(out FlushCloseWriter, levels []slog.Level) *Fl...
FILE: handler/write_close_syncer.go
type SyncCloseHandler (line 10) | type SyncCloseHandler struct
method Close (line 58) | func (h *SyncCloseHandler) Close() error {
method Flush (line 66) | func (h *SyncCloseHandler) Flush() error {
method Writer (line 71) | func (h *SyncCloseHandler) Writer() io.Writer {
method Handle (line 76) | func (h *SyncCloseHandler) Handle(record *slog.Record) error {
function NewSyncCloserWithLF (line 16) | func NewSyncCloserWithLF(out SyncCloseWriter, lf slog.LevelFormattable) ...
function SyncCloserWithMaxLevel (line 29) | func SyncCloserWithMaxLevel(out SyncCloseWriter, maxLevel slog.Level) *S...
function NewSyncCloser (line 38) | func NewSyncCloser(out SyncCloseWriter, levels []slog.Level) *SyncCloseH...
function SyncCloserWithLevels (line 43) | func SyncCloserWithLevels(out SyncCloseWriter, levels []slog.Level) *Syn...
function NewSyncCloseHandler (line 53) | func NewSyncCloseHandler(out SyncCloseWriter, levels []slog.Level) *Sync...
FILE: handler/write_closer.go
type WriteCloserHandler (line 10) | type WriteCloserHandler struct
method Close (line 59) | func (h *WriteCloserHandler) Close() error {
method Flush (line 64) | func (h *WriteCloserHandler) Flush() error {
method Handle (line 69) | func (h *WriteCloserHandler) Handle(record *slog.Record) error {
function NewWriteCloserWithLF (line 16) | func NewWriteCloserWithLF(out io.WriteCloser, lf slog.LevelFormattable) ...
function WriteCloserWithMaxLevel (line 25) | func WriteCloserWithMaxLevel(out io.WriteCloser, maxLevel slog.Level) *W...
function WriteCloserWithLevels (line 34) | func WriteCloserWithLevels(out io.WriteCloser, levels []slog.Level) *Wri...
function NewWriteCloser (line 41) | func NewWriteCloser(out io.WriteCloser, levels []slog.Level) *WriteClose...
function NewWriteCloserHandler (line 54) | func NewWriteCloserHandler(out io.WriteCloser, levels []slog.Level) *Wri...
FILE: handler/writer.go
type IOWriterHandler (line 10) | type IOWriterHandler struct
method TextFormatter (line 17) | func (h *IOWriterHandler) TextFormatter() *slog.TextFormatter {
method Handle (line 22) | func (h *IOWriterHandler) Handle(record *slog.Record) error {
function NewIOWriterWithLF (line 33) | func NewIOWriterWithLF(out io.Writer, lf slog.LevelFormattable) *IOWrite...
function IOWriterWithMaxLevel (line 53) | func IOWriterWithMaxLevel(out io.Writer, maxLevel slog.Level) *IOWriterH...
function NewIOWriter (line 62) | func NewIOWriter(out io.Writer, levels []slog.Level) *IOWriterHandler {
function IOWriterWithLevels (line 67) | func IOWriterWithLevels(out io.Writer, levels []slog.Level) *IOWriterHan...
function NewIOWriterHandler (line 80) | func NewIOWriterHandler(out io.Writer, levels []slog.Level) *IOWriterHan...
function NewHandler (line 88) | func NewHandler(out io.Writer, maxLevel slog.Level) *SimpleHandler {
function NewSimple (line 93) | func NewSimple(out io.Writer, maxLevel slog.Level) *SimpleHandler {
function SimpleWithLevels (line 98) | func SimpleWithLevels(out io.Writer, levels []slog.Level) *IOWriterHandl...
function NewSimpleHandler (line 111) | func NewSimpleHandler(out io.Writer, maxLevel slog.Level) *IOWriterHandl...
FILE: handler/writer_test.go
function TestNewIOWriter (line 15) | func TestNewIOWriter(t *testing.T) {
function TestNewSyncCloser (line 31) | func TestNewSyncCloser(t *testing.T) {
function TestNewWriteCloser (line 67) | func TestNewWriteCloser(t *testing.T) {
function TestNewFlushCloser (line 98) | func TestNewFlushCloser(t *testing.T) {
function TestNewSimpleHandler (line 136) | func TestNewSimpleHandler(t *testing.T) {
FILE: handler_test.go
function TestSafeToLevelMode (line 10) | func TestSafeToLevelMode(t *testing.T) {
function TestNewLvFormatter (line 33) | func TestNewLvFormatter(t *testing.T) {
function TestNewLvsFormatter (line 44) | func TestNewLvsFormatter(t *testing.T) {
function TestLevelFormatting (line 53) | func TestLevelFormatting(t *testing.T) {
FILE: internal/util.go
function AddSuffix2path (line 8) | func AddSuffix2path(filePath, suffix string) string {
function BuildGlobPattern (line 14) | func BuildGlobPattern(logfile string) string {
FILE: issues_test.go
function TestIssues_27 (line 20) | func TestIssues_27(t *testing.T) {
function TestIssues_31 (line 35) | func TestIssues_31(t *testing.T) {
function TestIssues_52 (line 54) | func TestIssues_52(t *testing.T) {
function TestIssues_75 (line 67) | func TestIssues_75(t *testing.T) {
function TestIssues_105 (line 79) | func TestIssues_105(t *testing.T) {
function TestIssues_108 (line 103) | func TestIssues_108(t *testing.T) {
function TestIssues_121 (line 133) | func TestIssues_121(t *testing.T) {
function TestIssues_137 (line 167) | func TestIssues_137(t *testing.T) {
function TestIssues_139 (line 187) | func TestIssues_139(t *testing.T) {
function TestIssues_144 (line 212) | func TestIssues_144(t *testing.T) {
function TestIssues_161 (line 234) | func TestIssues_161(t *testing.T) {
function TestIssues_163 (line 255) | func TestIssues_163(t *testing.T) {
FILE: logger.go
type Logger (line 14) | type Logger struct
method newRecord (line 98) | func (l *Logger) newRecord() *Record {
method releaseRecord (line 106) | func (l *Logger) releaseRecord(r *Record) {
method Config (line 140) | func (l *Logger) Config(fns ...LoggerFn) *Logger {
method Configure (line 148) | func (l *Logger) Configure(fn LoggerFn) *Logger { return l.Config(fn) }
method RegisterExitHandler (line 151) | func (l *Logger) RegisterExitHandler(handler func()) {
method PrependExitHandler (line 156) | func (l *Logger) PrependExitHandler(handler func()) {
method ResetExitHandlers (line 161) | func (l *Logger) ResetExitHandlers() { l.exitHandlers = make([]func(),...
method ExitHandlers (line 164) | func (l *Logger) ExitHandlers() []func() { return l.exitHandlers }
method SetName (line 167) | func (l *Logger) SetName(name string) { l.name = name }
method Name (line 170) | func (l *Logger) Name() string { return l.name }
method FlushDaemon (line 183) | func (l *Logger) FlushDaemon(onStops ...func()) {
method StopDaemon (line 209) | func (l *Logger) StopDaemon() {
method FlushTimeout (line 219) | func (l *Logger) FlushTimeout(timeout time.Duration) {
method Sync (line 236) | func (l *Logger) Sync() error { return Flush() }
method Flush (line 240) | func (l *Logger) Flush() error { return l.lockAndFlushAll() }
method MustFlush (line 243) | func (l *Logger) MustFlush() {
method FlushAll (line 250) | func (l *Logger) FlushAll() error { return l.lockAndFlushAll() }
method lockAndFlushAll (line 253) | func (l *Logger) lockAndFlushAll() error {
method flushAll (line 262) | func (l *Logger) flushAll() {
method MustClose (line 274) | func (l *Logger) MustClose() { goutil.PanicErr(l.Close()) }
method Close (line 281) | func (l *Logger) Close() error {
method VisitAll (line 299) | func (l *Logger) VisitAll(fn func(handler Handler) error) error {
method Reset (line 310) | func (l *Logger) Reset() {
method ResetProcessors (line 317) | func (l *Logger) ResetProcessors() { l.processors = make([]Processor, ...
method ResetHandlers (line 320) | func (l *Logger) ResetHandlers() { l.handlers = make([]Handler, 0) }
method Exit (line 323) | func (l *Logger) Exit(code int) {
method runExitHandlers (line 334) | func (l *Logger) runExitHandlers() {
method DoNothingOnPanicFatal (line 347) | func (l *Logger) DoNothingOnPanicFatal() {
method HandlersNum (line 353) | func (l *Logger) HandlersNum() int { return len(l.handlers) }
method LastErr (line 356) | func (l *Logger) LastErr() error {
method AddHandler (line 369) | func (l *Logger) AddHandler(h Handler) { l.PushHandlers(h) }
method AddHandlers (line 372) | func (l *Logger) AddHandlers(hs ...Handler) { l.PushHandlers(hs...) }
method PushHandler (line 375) | func (l *Logger) PushHandler(h Handler) { l.PushHandlers(h) }
method PushHandlers (line 378) | func (l *Logger) PushHandlers(hs ...Handler) {
method SetHandlers (line 385) | func (l *Logger) SetHandlers(hs []Handler) { l.handlers = hs }
method AddProcessor (line 388) | func (l *Logger) AddProcessor(p Processor) { l.processors = append(l.p...
method PushProcessor (line 391) | func (l *Logger) PushProcessor(p Processor) { l.processors = append(l....
method AddProcessors (line 394) | func (l *Logger) AddProcessors(ps ...Processor) { l.processors = appen...
method SetProcessors (line 397) | func (l *Logger) SetProcessors(ps []Processor) { l.processors = ps }
method NewSub (line 412) | func (l *Logger) NewSub() *SubLogger { return NewSubWith(l) }
method Record (line 421) | func (l *Logger) Record() *Record { return l.newRecord() }
method Reused (line 434) | func (l *Logger) Reused() *Record { return l.newRecord().Reused() }
method WithField (line 439) | func (l *Logger) WithField(name string, value any) *Record {
method WithFields (line 448) | func (l *Logger) WithFields(fields M) *Record {
method WithData (line 455) | func (l *Logger) WithData(data M) *Record {
method WithValue (line 460) | func (l *Logger) WithValue(key string, value any) *Record {
method WithExtra (line 465) | func (l *Logger) WithExtra(ext M) *Record {
method WithTime (line 470) | func (l *Logger) WithTime(t time.Time) *Record {
method WithCtx (line 477) | func (l *Logger) WithCtx(ctx context.Context) *Record { return l.WithC...
method WithContext (line 480) | func (l *Logger) WithContext(ctx context.Context) *Record {
method log (line 492) | func (l *Logger) log(level Level, args []any) {
method logf (line 499) | func (l *Logger) logf(level Level, format string, args []any) {
method logCtx (line 506) | func (l *Logger) logCtx(ctx context.Context, level Level, args []any) {
method logfCtx (line 514) | func (l *Logger) logfCtx(ctx context.Context, level Level, format stri...
method Log (line 522) | func (l *Logger) Log(level Level, args ...any) { l.log(level, args) }
method Logf (line 525) | func (l *Logger) Logf(level Level, format string, args ...any) { l.log...
method Print (line 528) | func (l *Logger) Print(args ...any) { l.log(PrintLevel, args) }
method Println (line 531) | func (l *Logger) Println(args ...any) { l.log(PrintLevel, args) }
method Printf (line 534) | func (l *Logger) Printf(format string, args ...any) { l.logf(PrintLeve...
method Trace (line 537) | func (l *Logger) Trace(args ...any) { l.log(TraceLevel, args) }
method Tracef (line 540) | func (l *Logger) Tracef(format string, args ...any) { l.logf(TraceLeve...
method TraceCtx (line 543) | func (l *Logger) TraceCtx(ctx context.Context, args ...any) { l.logCtx...
method TracefCtx (line 546) | func (l *Logger) TracefCtx(ctx context.Context, format string, args .....
method Debug (line 551) | func (l *Logger) Debug(args ...any) { l.log(DebugLevel, args) }
method Debugf (line 554) | func (l *Logger) Debugf(format string, args ...any) { l.logf(DebugLeve...
method DebugCtx (line 557) | func (l *Logger) DebugCtx(ctx context.Context, args ...any) { l.logCtx...
method DebugfCtx (line 560) | func (l *Logger) DebugfCtx(ctx context.Context, format string, args .....
method Info (line 565) | func (l *Logger) Info(args ...any) { l.log(InfoLevel, args) }
method Infof (line 568) | func (l *Logger) Infof(format string, args ...any) { l.logf(InfoLevel,...
method InfoCtx (line 571) | func (l *Logger) InfoCtx(ctx context.Context, args ...any) { l.logCtx(...
method InfofCtx (line 574) | func (l *Logger) InfofCtx(ctx context.Context, format string, args ......
method Notice (line 579) | func (l *Logger) Notice(args ...any) { l.log(NoticeLevel, args) }
method Noticef (line 582) | func (l *Logger) Noticef(format string, args ...any) { l.logf(NoticeLe...
method NoticeCtx (line 585) | func (l *Logger) NoticeCtx(ctx context.Context, args ...any) { l.logCt...
method NoticefCtx (line 588) | func (l *Logger) NoticefCtx(ctx context.Context, format string, args ....
method Warn (line 593) | func (l *Logger) Warn(args ...any) { l.log(WarnLevel, args) }
method Warnf (line 596) | func (l *Logger) Warnf(format string, args ...any) { l.logf(WarnLevel,...
method WarnCtx (line 599) | func (l *Logger) WarnCtx(ctx context.Context, args ...any) { l.logCtx(...
method WarnfCtx (line 602) | func (l *Logger) WarnfCtx(ctx context.Context, format string, args ......
method Warning (line 607) | func (l *Logger) Warning(args ...any) { l.log(WarnLevel, args) }
method Error (line 610) | func (l *Logger) Error(args ...any) { l.log(ErrorLevel, args) }
method Errorf (line 613) | func (l *Logger) Errorf(format string, args ...any) { l.logf(ErrorLeve...
method ErrorT (line 616) | func (l *Logger) ErrorT(err error) {
method ErrorCtx (line 623) | func (l *Logger) ErrorCtx(ctx context.Context, args ...any) { l.logCtx...
method ErrorfCtx (line 626) | func (l *Logger) ErrorfCtx(ctx context.Context, format string, args .....
method Fatal (line 634) | func (l *Logger) Fatal(args ...any) { l.log(FatalLevel, args) }
method Fatalf (line 637) | func (l *Logger) Fatalf(format string, args ...any) { l.logf(FatalLeve...
method Fatalln (line 640) | func (l *Logger) Fatalln(args ...any) { l.log(FatalLevel, args) }
method FatalCtx (line 643) | func (l *Logger) FatalCtx(ctx context.Context, args ...any) { l.logCtx...
method FatalfCtx (line 646) | func (l *Logger) FatalfCtx(ctx context.Context, format string, args .....
method Panic (line 651) | func (l *Logger) Panic(args ...any) { l.log(PanicLevel, args) }
method Panicf (line 654) | func (l *Logger) Panicf(format string, args ...any) { l.logf(PanicLeve...
method Panicln (line 657) | func (l *Logger) Panicln(args ...any) { l.log(PanicLevel, args) }
method PanicCtx (line 660) | func (l *Logger) PanicCtx(ctx context.Context, args ...any) { l.logCtx...
method PanicfCtx (line 663) | func (l *Logger) PanicfCtx(ctx context.Context, format string, args .....
function New (line 62) | func New(fns ...LoggerFn) *Logger { return NewWithName("logger", fns...) }
function NewWithHandlers (line 65) | func NewWithHandlers(hs ...Handler) *Logger {
function NewWithConfig (line 72) | func NewWithConfig(fns ...LoggerFn) *Logger { return NewWithName("logger...
function NewWithName (line 75) | func NewWithName(name string, fns ...LoggerFn) *Logger {
constant defaultFlushInterval (line 178) | defaultFlushInterval = 30 * time.Second
FILE: logger_sub.go
type SubLogger (line 16) | type SubLogger struct
method KeepCtx (line 33) | func (sub *SubLogger) KeepCtx(ctx context.Context) *SubLogger {
method KeepFields (line 39) | func (sub *SubLogger) KeepFields(fields M) *SubLogger {
method KeepField (line 45) | func (sub *SubLogger) KeepField(field string, value any) *SubLogger {
method KeepData (line 55) | func (sub *SubLogger) KeepData(data M) *SubLogger {
method KeepExtra (line 61) | func (sub *SubLogger) KeepExtra(extra M) *SubLogger {
method Release (line 67) | func (sub *SubLogger) Release() {
method withKeepCtx (line 75) | func (sub *SubLogger) withKeepCtx() *Record {
method Print (line 90) | func (sub *SubLogger) Print(args ...any) { sub.withKeepCtx().Print(arg...
method Printf (line 93) | func (sub *SubLogger) Printf(format string, args ...any) { sub.withKee...
method Trace (line 96) | func (sub *SubLogger) Trace(args ...any) { sub.withKeepCtx().Trace(arg...
method Tracef (line 99) | func (sub *SubLogger) Tracef(format string, args ...any) {
method Debug (line 104) | func (sub *SubLogger) Debug(args ...any) { sub.withKeepCtx().Debug(arg...
method Debugf (line 107) | func (sub *SubLogger) Debugf(format string, args ...any) {
method Info (line 112) | func (sub *SubLogger) Info(args ...any) { sub.withKeepCtx().Info(args....
method Infof (line 115) | func (sub *SubLogger) Infof(format string, args ...any) {
method Notice (line 120) | func (sub *SubLogger) Notice(args ...any) { sub.withKeepCtx().Notice(a...
method Noticef (line 123) | func (sub *SubLogger) Noticef(format string, args ...any) {
method Warn (line 128) | func (sub *SubLogger) Warn(args ...any) { sub.withKeepCtx().Warn(args....
method Warnf (line 131) | func (sub *SubLogger) Warnf(format string, args ...any) {
method Error (line 136) | func (sub *SubLogger) Error(args ...any) { sub.withKeepCtx().Error(arg...
method Errorf (line 139) | func (sub *SubLogger) Errorf(format string, args ...any) {
method Fatal (line 144) | func (sub *SubLogger) Fatal(args ...any) { sub.withKeepCtx().Fatal(arg...
method Fatalf (line 147) | func (sub *SubLogger) Fatalf(format string, args ...any) {
method Panic (line 152) | func (sub *SubLogger) Panic(args ...any) { sub.withKeepCtx().Panic(arg...
method Panicf (line 155) | func (sub *SubLogger) Panicf(format string, args ...any) {
function NewSubWith (line 30) | func NewSubWith(l *Logger) *SubLogger { return &SubLogger{l: l} }
FILE: logger_test.go
function TestLoggerBasic (line 18) | func TestLoggerBasic(t *testing.T) {
function TestLogger_PushHandler (line 27) | func TestLogger_PushHandler(t *testing.T) {
function TestLogger_ReportCaller (line 56) | func TestLogger_ReportCaller(t *testing.T) {
function TestLogger_Log (line 75) | func TestLogger_Log(t *testing.T) {
function TestLogger_WithContext (line 88) | func TestLogger_WithContext(t *testing.T) {
function TestLogger_panic (line 104) | func TestLogger_panic(t *testing.T) {
function TestLogger_error (line 128) | func TestLogger_error(t *testing.T) {
function TestLogger_panicLevel (line 144) | func TestLogger_panicLevel(t *testing.T) {
function TestLogger_log_allLevel (line 170) | func TestLogger_log_allLevel(t *testing.T) {
function TestLogger_logf_allLevel (line 180) | func TestLogger_logf_allLevel(t *testing.T) {
function TestLogger_write_error (line 191) | func TestLogger_write_error(t *testing.T) {
function TestLogger_AddWithCtx (line 203) | func TestLogger_AddWithCtx(t *testing.T) {
function TestLogger_GlobalFields (line 250) | func TestLogger_GlobalFields(t *testing.T) {
function TestLogger_option_BackupArgs (line 266) | func TestLogger_option_BackupArgs(t *testing.T) {
function TestLogger_FlushTimeout (line 301) | func TestLogger_FlushTimeout(t *testing.T) {
function TestLogger_rewrite_record (line 321) | func TestLogger_rewrite_record(t *testing.T) {
function TestLogger_Sub (line 356) | func TestLogger_Sub(t *testing.T) {
FILE: logger_write.go
method Init (line 25) | func (r *Record) Init(lowerLevelName bool) {
method beforeHandle (line 44) | func (r *Record) beforeHandle(l *Logger) {
method writeRecord (line 60) | func (l *Logger) writeRecord(level Level, r *Record) {
FILE: processor.go
type Processor (line 17) | type Processor interface
type ProcessorFunc (line 23) | type ProcessorFunc
method Process (line 26) | func (fn ProcessorFunc) Process(record *Record) {
type ProcessableHandler (line 31) | type ProcessableHandler interface
type Processable (line 39) | type Processable struct
method AddProcessor (line 44) | func (p *Processable) AddProcessor(processor Processor) {
method ProcessRecord (line 49) | func (p *Processable) ProcessRecord(r *Record) {
function AddHostname (line 61) | func AddHostname() Processor {
function AddUniqueID (line 69) | func AddUniqueID(fieldName string) Processor {
function AppendCtxKeys (line 90) | func AppendCtxKeys(keys ...string) Processor {
function CtxKeysProcessor (line 106) | func CtxKeysProcessor(dist string, keys ...string) Processor {
FILE: processor_test.go
function TestLogger_AddProcessor (line 14) | func TestLogger_AddProcessor(t *testing.T) {
function TestCtxKeysProcessor (line 65) | func TestCtxKeysProcessor(t *testing.T) {
function TestProcessable_AddProcessor (line 79) | func TestProcessable_AddProcessor(t *testing.T) {
FILE: record.go
type Record (line 14) | type Record struct
method Reused (line 85) | func (r *Record) Reused() *Record {
method Release (line 91) | func (r *Record) Release() {
method WithTime (line 105) | func (r *Record) WithTime(t time.Time) *Record {
method WithCtx (line 112) | func (r *Record) WithCtx(ctx context.Context) *Record { return r.WithC...
method WithContext (line 115) | func (r *Record) WithContext(ctx context.Context) *Record {
method WithError (line 122) | func (r *Record) WithError(err error) *Record {
method WithData (line 127) | func (r *Record) WithData(data M) *Record {
method WithField (line 136) | func (r *Record) WithField(name string, val any) *Record {
method WithFields (line 143) | func (r *Record) WithFields(fields M) *Record {
method Copy (line 156) | func (r *Record) Copy() *Record {
method SetCtx (line 195) | func (r *Record) SetCtx(ctx context.Context) *Record { return r.SetCon...
method SetContext (line 198) | func (r *Record) SetContext(ctx context.Context) *Record {
method SetData (line 204) | func (r *Record) SetData(data M) *Record {
method AddData (line 210) | func (r *Record) AddData(data M) *Record {
method WithValue (line 223) | func (r *Record) WithValue(key string, value any) *Record {
method AddValue (line 228) | func (r *Record) AddValue(key string, value any) *Record {
method Value (line 238) | func (r *Record) Value(key string) any {
method SetExtra (line 246) | func (r *Record) SetExtra(data M) *Record {
method AddExtra (line 252) | func (r *Record) AddExtra(data M) *Record {
method SetExtraValue (line 265) | func (r *Record) SetExtraValue(k string, v any) {
method SetTime (line 273) | func (r *Record) SetTime(t time.Time) *Record {
method AddField (line 279) | func (r *Record) AddField(name string, val any) *Record {
method AddFields (line 289) | func (r *Record) AddFields(fields M) *Record {
method SetFields (line 302) | func (r *Record) SetFields(fields M) *Record {
method Field (line 308) | func (r *Record) Field(key string) any {
method log (line 348) | func (r *Record) log(level Level, args []any) {
method logf (line 361) | func (r *Record) logf(level Level, format string, args []any) {
method Log (line 374) | func (r *Record) Log(level Level, args ...any) { r.log(level, args) }
method Logf (line 377) | func (r *Record) Logf(level Level, format string, args ...any) {
method Info (line 382) | func (r *Record) Info(args ...any) { r.log(InfoLevel, args) }
method Infof (line 385) | func (r *Record) Infof(format string, args ...any) {
method Trace (line 390) | func (r *Record) Trace(args ...any) { r.log(TraceLevel, args) }
method Tracef (line 393) | func (r *Record) Tracef(format string, args ...any) {
method Error (line 398) | func (r *Record) Error(args ...any) { r.log(ErrorLevel, args) }
method Errorf (line 401) | func (r *Record) Errorf(format string, args ...any) {
method Warn (line 406) | func (r *Record) Warn(args ...any) { r.log(WarnLevel, args) }
method Warnf (line 409) | func (r *Record) Warnf(format string, args ...any) {
method Notice (line 414) | func (r *Record) Notice(args ...any) { r.log(NoticeLevel, args) }
method Noticef (line 417) | func (r *Record) Noticef(format string, args ...any) {
method Debug (line 422) | func (r *Record) Debug(args ...any) { r.log(DebugLevel, args) }
method Debugf (line 425) | func (r *Record) Debugf(format string, args ...any) {
method Print (line 430) | func (r *Record) Print(args ...any) { r.log(PrintLevel, args) }
method Println (line 433) | func (r *Record) Println(args ...any) { r.log(PrintLevel, args) }
method Printf (line 436) | func (r *Record) Printf(format string, args ...any) {
method Fatal (line 441) | func (r *Record) Fatal(args ...any) { r.log(FatalLevel, args) }
method Fatalln (line 444) | func (r *Record) Fatalln(args ...any) { r.log(FatalLevel, args) }
method Fatalf (line 447) | func (r *Record) Fatalf(format string, args ...any) {
method Panic (line 452) | func (r *Record) Panic(args ...any) { r.log(PanicLevel, args) }
method Panicln (line 455) | func (r *Record) Panicln(args ...any) { r.log(PanicLevel, args) }
method Panicf (line 458) | func (r *Record) Panicf(format string, args ...any) {
method LevelName (line 467) | func (r *Record) LevelName() string { return r.levelName }
method GoString (line 470) | func (r *Record) GoString() string {
method timestamp (line 474) | func (r *Record) timestamp() string {
function newRecord (line 69) | func newRecord(logger *Logger) *Record {
FILE: record_test.go
function TestRecord_AddData (line 19) | func TestRecord_AddData(t *testing.T) {
function TestRecord_AddExtra (line 69) | func TestRecord_AddExtra(t *testing.T) {
function TestRecord_SetContext (line 97) | func TestRecord_SetContext(t *testing.T) {
function TestRecord_WithError (line 117) | func TestRecord_WithError(t *testing.T) {
function TestRecord_WithTime (line 136) | func TestRecord_WithTime(t *testing.T) {
function TestRecord_AddFields (line 149) | func TestRecord_AddFields(t *testing.T) {
function TestRecord_WithFields (line 169) | func TestRecord_WithFields(t *testing.T) {
function TestRecord_SetFields (line 185) | func TestRecord_SetFields(t *testing.T) {
function TestRecord_allLevel (line 194) | func TestRecord_allLevel(t *testing.T) {
function TestRecord_useMultiTimes (line 228) | func TestRecord_useMultiTimes(t *testing.T) {
FILE: rotatefile/cleanup.go
constant defaultCheckInterval (line 12) | defaultCheckInterval = 60 * time.Second
type CConfig (line 15) | type CConfig struct
method AddDirPath (line 57) | func (c *CConfig) AddDirPath(dirPaths ...string) *CConfig {
method AddPattern (line 68) | func (c *CConfig) AddPattern(patterns ...string) *CConfig {
method WithConfigFn (line 74) | func (c *CConfig) WithConfigFn(fns ...CConfigFunc) *CConfig {
type CConfigFunc (line 54) | type CConfigFunc
function NewCConfig (line 84) | func NewCConfig() *CConfig {
type FilesClear (line 98) | type FilesClear struct
method Config (line 116) | func (r *FilesClear) Config() *CConfig {
method WithConfig (line 121) | func (r *FilesClear) WithConfig(cfg *CConfig) *FilesClear {
method WithConfigFn (line 127) | func (r *FilesClear) WithConfigFn(fns ...CConfigFunc) *FilesClear {
method StopDaemon (line 139) | func (r *FilesClear) StopDaemon() {
method DaemonClean (line 167) | func (r *FilesClear) DaemonClean(onStop func()) {
method prepare (line 190) | func (r *FilesClear) prepare() {
method Clean (line 203) | func (r *FilesClear) Clean() error {
method cleanByPattern (line 218) | func (r *FilesClear) cleanByPattern(filePattern string) (err error) {
method remove (line 268) | func (r *FilesClear) remove(filePath string) (err error) {
function NewFilesClear (line 110) | func NewFilesClear(fns ...CConfigFunc) *FilesClear {
FILE: rotatefile/cleanup_test.go
function TestFilesClear_Clean (line 18) | func TestFilesClear_Clean(t *testing.T) {
function TestFilesClear_DaemonClean (line 56) | func TestFilesClear_DaemonClean(t *testing.T) {
function makeWaitCleanFiles (line 111) | func makeWaitCleanFiles(nameTpl string, makeNum int) {
FILE: rotatefile/config.go
type rotateLevel (line 17) | type rotateLevel
constant levelDay (line 20) | levelDay rotateLevel = iota
constant levelHour (line 21) | levelHour
constant levelMin (line 22) | levelMin
constant levelSec (line 23) | levelSec
type RotateTime (line 35) | type RotateTime
method Interval (line 49) | func (rt RotateTime) Interval() int64 {
method FirstCheckTime (line 55) | func (rt RotateTime) FirstCheckTime(now time.Time) time.Time {
method level (line 83) | func (rt RotateTime) level() rotateLevel {
method TimeFormat (line 105) | func (rt RotateTime) TimeFormat() (suffixFormat string) {
method MarshalJSON (line 121) | func (rt RotateTime) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 126) | func (rt *RotateTime) UnmarshalJSON(data []byte) error {
method String (line 137) | func (rt RotateTime) String() string {
constant EveryMonth (line 39) | EveryMonth RotateTime = 30 * timex.OneDaySec
constant EveryDay (line 40) | EveryDay RotateTime = timex.OneDaySec
constant EveryHour (line 41) | EveryHour RotateTime = timex.OneHourSec
constant Every30Min (line 42) | Every30Min RotateTime = 30 * timex.OneMinSec
constant Every15Min (line 43) | Every15Min RotateTime = 15 * timex.OneMinSec
constant EveryMinute (line 44) | EveryMinute RotateTime = timex.OneMinSec
constant EverySecond (line 45) | EverySecond RotateTime = 1
function StringToRotateTime (line 151) | func StringToRotateTime(s string) (RotateTime, error) {
type RotateMode (line 174) | type RotateMode
method String (line 193) | func (m RotateMode) String() string {
method MarshalJSON (line 205) | func (m RotateMode) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 210) | func (m *RotateMode) UnmarshalJSON(data []byte) error {
constant ModeRename (line 183) | ModeRename RotateMode = iota
constant ModeCreate (line 189) | ModeCreate
function StringToRotateMode (line 221) | func StringToRotateMode(s string) (RotateMode, error) {
type Clocker (line 244) | type Clocker interface
type ClockFn (line 249) | type ClockFn
method Now (line 252) | func (fn ClockFn) Now() time.Time {
type ConfigFn (line 257) | type ConfigFn
type Config (line 260) | type Config struct
method backupDuration (line 323) | func (c *Config) backupDuration() time.Duration {
method With (line 331) | func (c *Config) With(fns ...ConfigFn) *Config {
method Create (line 339) | func (c *Config) Create() (*Writer, error) { return NewWriter(c) }
method IsMode (line 342) | func (c *Config) IsMode(m RotateMode) bool { return c.RotateMode == m }
function NewDefaultConfig (line 357) | func NewDefaultConfig() *Config {
function NewConfig (line 370) | func NewConfig(filePath string, fns ...ConfigFn) *Config {
function NewConfigWith (line 378) | func NewConfigWith(fns ...ConfigFn) *Config {
function EmptyConfigWith (line 383) | func EmptyConfigWith(fns ...ConfigFn) *Config {
function WithFilepath (line 394) | func WithFilepath(logfile string) ConfigFn {
function WithDebugMode (line 399) | func WithDebugMode(c *Config) { c.DebugMode = true }
function WithCompress (line 402) | func WithCompress(c *Config) { c.Compress = true }
function WithBackupNum (line 405) | func WithBackupNum(num uint) ConfigFn {
FILE: rotatefile/config_test.go
function TestNewDefaultConfig (line 15) | func TestNewDefaultConfig(t *testing.T) {
function TestNewConfig (line 23) | func TestNewConfig(t *testing.T) {
function TestRotateMode_cases (line 54) | func TestRotateMode_cases(t *testing.T) {
function TestRotateTime_encode (line 93) | func TestRotateTime_encode(t *testing.T) {
function TestRotateTime_TimeFormat (line 113) | func TestRotateTime_TimeFormat(t *testing.T) {
function TestRotateTime_String (line 135) | func TestRotateTime_String(t *testing.T) {
function TestRotateTime_FirstCheckTime_Round (line 148) | func TestRotateTime_FirstCheckTime_Round(t *testing.T) {
FILE: rotatefile/issues_test.go
function TestIssues_138 (line 16) | func TestIssues_138(t *testing.T) {
function TestIssues_150 (line 56) | func TestIssues_150(t *testing.T) {
FILE: rotatefile/rotatefile.go
type RotateWriter (line 9) | type RotateWriter interface
constant OneMByte (line 19) | OneMByte uint64 = 1024 * 1024
constant DefaultMaxSize (line 22) | DefaultMaxSize = 20 * OneMByte
constant DefaultBackNum (line 24) | DefaultBackNum uint = 20
constant DefaultBackTime (line 26) | DefaultBackTime uint = 24 * 7
FILE: rotatefile/rotatefile_test.go
function TestMain (line 13) | func TestMain(m *testing.M) {
function ExampleNewWriter_on_other_logger (line 19) | func ExampleNewWriter_on_other_logger() {
FILE: rotatefile/util.go
constant compressSuffix (line 16) | compressSuffix = ".gz"
function printErrln (line 18) | func printErrln(pfx string, err error) {
function compressFile (line 24) | func compressFile(srcPath, dstPath string) error {
type fileInfo (line 56) | type fileInfo struct
method Path (line 62) | func (fi *fileInfo) Path() string {
function newFileInfo (line 66) | func newFileInfo(filePath string, fi fs.FileInfo) fileInfo {
type modTimeFInfos (line 72) | type modTimeFInfos
method Less (line 75) | func (fis modTimeFInfos) Less(i, j int) bool {
method Swap (line 80) | func (fis modTimeFInfos) Swap(i, j int) {
method Len (line 85) | func (fis modTimeFInfos) Len() int {
type MockClocker (line 90) | type MockClocker struct
method Now (line 101) | func (mt *MockClocker) Now() time.Time {
method Add (line 106) | func (mt *MockClocker) Add(d time.Duration) {
method Datetime (line 111) | func (mt *MockClocker) Datetime() string {
function NewMockClock (line 95) | func NewMockClock(datetime string) *MockClocker {
FILE: rotatefile/util_test.go
function TestPrintErrln (line 8) | func TestPrintErrln(t *testing.T) {
FILE: rotatefile/writer.go
type Writer (line 24) | type Writer struct
method init (line 74) | func (d *Writer) init() error {
method Config (line 108) | func (d *Writer) Config() Config {
method Flush (line 113) | func (d *Writer) Flush() error {
method Sync (line 118) | func (d *Writer) Sync() error {
method Close (line 124) | func (d *Writer) Close() error {
method MustClose (line 134) | func (d *Writer) MustClose() {
method close (line 138) | func (d *Writer) close(closeStopCh bool) error {
method WriteString (line 159) | func (d *Writer) WriteString(s string) (n int, err error) {
method Write (line 164) | func (d *Writer) Write(p []byte) (n int, err error) {
method doWrite (line 179) | func (d *Writer) doWrite(p []byte) (n int, err error) {
method Rotate (line 195) | func (d *Writer) Rotate() error {
method doRotate (line 206) | func (d *Writer) doRotate() (err error) {
method rotatingByTime (line 229) | func (d *Writer) rotatingByTime() error {
method rotatingBySize (line 246) | func (d *Writer) rotatingBySize() error {
method rotatingFile (line 274) | func (d *Writer) rotatingFile(bakFile string, rename bool) error {
method shouldClean (line 313) | func (d *Writer) shouldClean(withRand bool) bool {
method asyncClean (line 324) | func (d *Writer) asyncClean() {
method notifyClean (line 369) | func (d *Writer) notifyClean() {
method Clean (line 379) | func (d *Writer) Clean() (err error) {
method doClean (line 391) | func (d *Writer) doClean(skipSeconds ...int) (err error) {
method removeOldGzFiles (line 456) | func (d *Writer) removeOldGzFiles(remNum int, gzFiles []fileInfo) (rn ...
method removeOldFiles (line 480) | func (d *Writer) removeOldFiles(remNum int, oldFiles []fileInfo) (file...
method openFile (line 514) | func (d *Writer) openFile(logfile string) error {
method buildFilePath (line 526) | func (d *Writer) buildFilePath(suffix string) string {
method buildFilterFns (line 531) | func (d *Writer) buildFilterFns(fileName string) []fsutil.FilterFunc {
method compressFiles (line 570) | func (d *Writer) compressFiles(oldFiles []fileInfo) error {
method debugLog (line 587) | func (d *Writer) debugLog(vs ...any) {
function NewWriter (line 59) | func NewWriter(c *Config) (*Writer, error) {
function NewWriterWith (line 69) | func NewWriterWith(fns ...ConfigFn) (*Writer, error) {
FILE: rotatefile/writer_test.go
function TestNewWriter (line 16) | func TestNewWriter(t *testing.T) {
function TestWriter_Rotate_modeCreate (line 40) | func TestWriter_Rotate_modeCreate(t *testing.T) {
function TestWriter_rotateByTime (line 70) | func TestWriter_rotateByTime(t *testing.T) {
function TestWriter_Clean (line 96) | func TestWriter_Clean(t *testing.T) {
function TestWriter_Compress (line 140) | func TestWriter_Compress(t *testing.T) {
type constantClock (line 185) | type constantClock
method Now (line 187) | func (c constantClock) Now() time.Time { return time.Time(c) }
method NewTicker (line 188) | func (c constantClock) NewTicker(d time.Duration) *time.Ticker {
FILE: slog.go
function Std (line 45) | func Std() *SugaredLogger { return std }
function Reset (line 48) | func Reset() {
function Configure (line 55) | func Configure(fn func(l *SugaredLogger)) { std.Config(fn) }
function SetExitFunc (line 58) | func SetExitFunc(fn func(code int)) { std.ExitFunc = fn }
function Exit (line 61) | func Exit(code int) { std.Exit(code) }
function Close (line 66) | func Close() error { return std.Close() }
function MustClose (line 71) | func MustClose() { goutil.PanicErr(Close()) }
function Flush (line 74) | func Flush() error { return std.Flush() }
function MustFlush (line 77) | func MustFlush() { goutil.PanicErr(Flush()) }
function FlushTimeout (line 80) | func FlushTimeout(timeout time.Duration) { std.FlushTimeout(timeout) }
function FlushDaemon (line 85) | func FlushDaemon(onStops ...func()) { std.FlushDaemon(onStops...) }
function StopDaemon (line 88) | func StopDaemon() { std.StopDaemon() }
function SetLogLevel (line 91) | func SetLogLevel(l Level) { std.Level = l }
function SetLevelByName (line 94) | func SetLevelByName(name string) { std.Level = LevelByName(name) }
function SetFormatter (line 97) | func SetFormatter(f Formatter) { std.Formatter = f }
function GetFormatter (line 100) | func GetFormatter() Formatter { return std.Formatter }
function AddHandler (line 103) | func AddHandler(h Handler) { std.AddHandler(h) }
function PushHandler (line 106) | func PushHandler(h Handler) { std.AddHandler(h) }
function AddHandlers (line 109) | func AddHandlers(hs ...Handler) { std.AddHandlers(hs...) }
function PushHandlers (line 112) | func PushHandlers(hs ...Handler) { std.PushHandlers(hs...) }
function AddProcessor (line 115) | func AddProcessor(p Processor) { std.AddProcessor(p) }
function AddProcessors (line 118) | func AddProcessors(ps ...Processor) { std.AddProcessors(ps...) }
function NewSub (line 123) | func NewSub() *SubLogger { return NewSubWith(std.Logger) }
function WithExtra (line 128) | func WithExtra(ext M) *Record { return std.WithExtra(ext) }
function WithData (line 131) | func WithData(data M) *Record { return std.WithData(data) }
function WithValue (line 134) | func WithValue(key string, value any) *Record { return std.WithValue(key...
function WithField (line 139) | func WithField(name string, value any) *Record { return std.WithField(na...
function WithFields (line 144) | func WithFields(fields M) *Record { return std.WithFields(fields) }
function WithContext (line 147) | func WithContext(ctx context.Context) *Record { return std.WithContext(c...
function Log (line 153) | func Log(level Level, args ...any) { std.log(level, args) }
function Print (line 156) | func Print(args ...any) { std.log(PrintLevel, args) }
function Println (line 159) | func Println(args ...any) { std.log(PrintLevel, args) }
function Printf (line 162) | func Printf(format string, args ...any) { std.logf(PrintLevel, format, a...
function Trace (line 165) | func Trace(args ...any) { std.log(TraceLevel, args) }
function Tracef (line 168) | func Tracef(format string, args ...any) { std.logf(TraceLevel, format, a...
function TraceCtx (line 171) | func TraceCtx(ctx context.Context, args ...any) { std.logCtx(ctx, TraceL...
function TracefCtx (line 174) | func TracefCtx(ctx context.Context, format string, args ...any) {
function Debug (line 179) | func Debug(args ...any) { std.log(DebugLevel, args) }
function Debugf (line 182) | func Debugf(format string, args ...any) { std.logf(DebugLevel, format, a...
function DebugCtx (line 185) | func DebugCtx(ctx context.Context, args ...any) { std.logCtx(ctx, DebugL...
function DebugfCtx (line 188) | func DebugfCtx(ctx context.Context, format string, args ...any) {
function Info (line 193) | func Info(args ...any) { std.log(InfoLevel, args) }
function Infof (line 196) | func Infof(format string, args ...any) { std.logf(InfoLevel, format, arg...
function InfoCtx (line 199) | func InfoCtx(ctx context.Context, args ...any) { std.logCtx(ctx, InfoLev...
function InfofCtx (line 202) | func InfofCtx(ctx context.Context, format string, args ...any) {
function Notice (line 207) | func Notice(args ...any) { std.log(NoticeLevel, args) }
function Noticef (line 210) | func Noticef(format string, args ...any) { std.logf(NoticeLevel, format,...
function NoticeCtx (line 213) | func NoticeCtx(ctx context.Context, args ...any) { std.logCtx(ctx, Notic...
function NoticefCtx (line 216) | func NoticefCtx(ctx context.Context, format string, args ...any) {
function Warn (line 221) | func Warn(args ...any) { std.log(WarnLevel, args) }
function Warnf (line 224) | func Warnf(format string, args ...any) { std.logf(WarnLevel, format, arg...
function WarnCtx (line 227) | func WarnCtx(ctx context.Context, args ...any) { std.logCtx(ctx, WarnLev...
function WarnfCtx (line 230) | func WarnfCtx(ctx context.Context, format string, args ...any) {
function Error (line 235) | func Error(args ...any) { std.log(ErrorLevel, args) }
function Errorf (line 238) | func Errorf(format string, args ...any) { std.logf(ErrorLevel, format, a...
function ErrorT (line 241) | func ErrorT(err error) {
function ErrorCtx (line 248) | func ErrorCtx(ctx context.Context, args ...any) { std.logCtx(ctx, ErrorL...
function ErrorfCtx (line 251) | func ErrorfCtx(ctx context.Context, format string, args ...any) {
function Fatal (line 262) | func Fatal(args ...any) { std.log(FatalLevel, args) }
function Fatalf (line 265) | func Fatalf(format string, args ...any) { std.logf(FatalLevel, format, a...
function FatalErr (line 268) | func FatalErr(err error) {
function FatalCtx (line 275) | func FatalCtx(ctx context.Context, args ...any) { std.logCtx(ctx, FatalL...
function FatalfCtx (line 278) | func FatalfCtx(ctx context.Context, format string, args ...any) {
function Panic (line 283) | func Panic(args ...any) { std.log(PanicLevel, args) }
function Panicf (line 286) | func Panicf(format string, args ...any) { std.logf(PanicLevel, format, a...
function PanicErr (line 289) | func PanicErr(err error) {
function PanicCtx (line 296) | func PanicCtx(ctx context.Context, args ...any) { std.logCtx(ctx, PanicL...
function PanicfCtx (line 299) | func PanicfCtx(ctx context.Context, format string, args ...any) {
FILE: slog_test.go
function TestStd (line 26) | func TestStd(t *testing.T) {
function TestTextFormatNoColor (line 52) | func TestTextFormatNoColor(t *testing.T) {
function TestFlushDaemon (line 68) | func TestFlushDaemon(t *testing.T) {
function TestFlushTimeout (line 101) | func TestFlushTimeout(t *testing.T) {
function TestNewSugaredLogger (line 109) | func TestNewSugaredLogger(t *testing.T) {
type logTest (line 130) | type logTest struct
method testPrint (line 134) | func (l logTest) testPrint() {
function TestTextFormatWithColor (line 138) | func TestTextFormatWithColor(t *testing.T) {
function printLogs (line 168) | func printLogs(msg string) {
function printfLogs (line 186) | func printfLogs(msg string, args ...any) {
function TestSetFormatter_jsonFormat (line 198) | func TestSetFormatter_jsonFormat(t *testing.T) {
function TestAddHandler (line 266) | func TestAddHandler(t *testing.T) {
function TestWithExtra (line 283) | func TestWithExtra(t *testing.T) {
function TestAddProcessor (line 301) | func TestAddProcessor(t *testing.T) {
function TestPrependExitHandler (line 336) | func TestPrependExitHandler(t *testing.T) {
function TestRegisterExitHandler (line 357) | func TestRegisterExitHandler(t *testing.T) {
function TestExitHandlerWithError (line 382) | func TestExitHandlerWithError(t *testing.T) {
function TestLogger_ExitHandlerWithError (line 398) | func TestLogger_ExitHandlerWithError(t *testing.T) {
function TestLogger_PrependExitHandler (line 415) | func TestLogger_PrependExitHandler(t *testing.T) {
function TestSugaredLogger_Close (line 432) | func TestSugaredLogger_Close(t *testing.T) {
function TestSugaredLogger_Handle (line 447) | func TestSugaredLogger_Handle(t *testing.T) {
function TestAddWithCtx (line 461) | func TestAddWithCtx(t *testing.T) {
FILE: sugared.go
type SugaredLoggerFn (line 11) | type SugaredLoggerFn
type SugaredLogger (line 15) | type SugaredLogger struct
method Config (line 79) | func (sl *SugaredLogger) Config(fns ...SugaredLoggerFn) *SugaredLogger {
method Reset (line 87) | func (sl *SugaredLogger) Reset() {
method IsHandling (line 94) | func (sl *SugaredLogger) IsHandling(level Level) bool {
method Handle (line 99) | func (sl *SugaredLogger) Handle(record *Record) error {
method Close (line 114) | func (sl *SugaredLogger) Close() error {
method Flush (line 129) | func (sl *SugaredLogger) Flush() error { return sl.FlushAll() }
method FlushAll (line 132) | func (sl *SugaredLogger) FlushAll() error {
function NewStd (line 28) | func NewStd(fns ...SugaredLoggerFn) *SugaredLogger {
function NewStdLogger (line 33) | func NewStdLogger(fns ...SugaredLoggerFn) *SugaredLogger {
function NewSugared (line 51) | func NewSugared(out io.Writer, level Level, fns ...SugaredLoggerFn) *Sug...
function NewSugaredLogger (line 56) | func NewSugaredLogger(output io.Writer, level Level, fns ...SugaredLogge...
function NewJSONSugared (line 71) | func NewJSONSugared(out io.Writer, level Level, fns ...SugaredLoggerFn) ...
FILE: util.go
function FormatLevelName (line 28) | func FormatLevelName(name string, length int) string {
function buildLowerLevelName (line 35) | func buildLowerLevelName() map[Level]string {
function getCaller (line 44) | func getCaller(callerSkip int) (fr runtime.Frame, ok bool) {
function formatCaller (line 54) | func formatCaller(rf *runtime.Frame, flag uint8, userFn CallerFormatFn) ...
function formatArgsWithSpaces (line 91) | func formatArgsWithSpaces(vs []any) string {
function EncodeToString (line 124) | func EncodeToString(v any) string {
function mapToString (line 131) | func mapToString(mp map[string]any) string {
function parseTemplateToFields (line 155) | func parseTemplateToFields(tplStr string) []string {
function printStderr (line 175) | func printStderr(args ...any) {
FILE: util_test.go
function revertTemplateString (line 12) | func revertTemplateString(ss []string) string {
function TestInner_parseTemplateToFields (line 29) | func TestInner_parseTemplateToFields(t *testing.T) {
function TestUtil_EncodeToString (line 47) | func TestUtil_EncodeToString(t *testing.T) {
function TestUtil_formatArgsWithSpaces (line 51) | func TestUtil_formatArgsWithSpaces(t *testing.T) {
Condensed preview — 90 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (414K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 628,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: inhere\n\n---\n\n**System (pl"
},
{
"path": ".github/changelog.yml",
"chars": 828,
"preview": "title: '## Change Log'\n# style allow: simple, markdown(mkdown), ghr(gh-release)\nstyle: gh-release\n# group names\nnames: ["
},
{
"path": ".github/dependabot.yml",
"chars": 273,
"preview": "version: 2\nupdates:\n- package-ecosystem: gomod\n directory: \"/\"\n schedule:\n interval: daily\n open-pull-requests-lim"
},
{
"path": ".github/revive.toml",
"chars": 979,
"preview": "ignoreGeneratedHeader = false\n# Sets the default severity to \"warning\"\n#severity = \"error\"\nseverity = \"warning\"\nconfiden"
},
{
"path": ".github/workflows/go.yml",
"chars": 2348,
"preview": "name: Unit-Tests\non:\n pull_request:\n paths:\n - 'go.mod'\n - '**.go'\n - '**.yml'\n push:\n paths:\n "
},
{
"path": ".github/workflows/release.yml",
"chars": 1263,
"preview": "name: Tag-release\n\non:\n push:\n tags:\n - v*\n\njobs:\n release:\n name: Release new version\n runs-on: ubuntu-"
},
{
"path": ".gitignore",
"chars": 364,
"preview": "*.log\n*.swp\n.idea\n*.patch\n*.tmp\n\n# Go template\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Te"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 inhere\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "Makefile",
"chars": 7595,
"preview": "# Make does not offer a recursive wildcard function, so here's one:\n# from https://github.com/jenkins-x-plugins/jx-gitop"
},
{
"path": "README.md",
"chars": 24344,
"preview": "# slog\n\n\n[\n[![Go"
},
{
"path": "_example/bench_loglibs.md",
"chars": 13168,
"preview": "# Log libs benchmarks\n\nRun benchmark: `make test-bench`\n\n> **Note**: on each test will update all package to latest.\n\n##"
},
{
"path": "_example/bench_loglibs_test.go",
"chars": 4788,
"preview": "package main\n\nimport (\n\t\"io\"\n\tgoslog \"log/slog\"\n\t\"testing\"\n\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/handler\"\n"
},
{
"path": "_example/demos/demo1.go",
"chars": 299,
"preview": "package main\n\nimport (\n\tlog \"github.com/gookit/slog\"\n)\n\nconst simplestTemplate = \"[{{datetime}}] [{{level}}] {{message}}"
},
{
"path": "_example/demos/simple.go",
"chars": 548,
"preview": "package main\n\nimport \"github.com/gookit/slog\"\n\n// profile run:\n//\n// go build -gcflags '-m -l' simple.go\nfunc main() {\n\t"
},
{
"path": "_example/demos/slog_all_level.go",
"chars": 681,
"preview": "package main\n\nimport (\n\t\"errors\"\n\n\t\"github.com/gookit/goutil/errorx\"\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/"
},
{
"path": "_example/diff-with-zap-zerolog.md",
"chars": 274,
"preview": "# diff with zap, zerolog\n\n是的,zap 非常快速。\n\n但是有一点问题:\n\n- 配置起来稍显复杂\n- 没有内置切割文件处理和文件清理\n- 自定义扩展性不是很好\n\nYes, zap is very fast.\n\nBut"
},
{
"path": "_example/go.mod",
"chars": 920,
"preview": "module slog_example\n\ngo 1.19\n\nrequire (\n\tgithub.com/golang/glog v1.2.5\n\tgithub.com/gookit/goutil v0.7.4\n\tgithub.com/gook"
},
{
"path": "_example/handler/grouped.go",
"chars": 1404,
"preview": "package handler\n\nimport \"github.com/gookit/slog\"\n\n/*********************************************************************"
},
{
"path": "_example/issue100/issue100_test.go",
"chars": 3791,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/handler\"\n\t\"go.uber."
},
{
"path": "_example/issue111/main.go",
"chars": 932,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/syncs\"\n\t\"github.com/gookit/goutil/timex\"\n\t\"githu"
},
{
"path": "_example/issue137/main.go",
"chars": 4634,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"time\"\n\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/handler\"\n\t\"github.com/"
},
{
"path": "_example/pprof/main.go",
"chars": 925,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"runtime/pprof\"\n\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/ha"
},
{
"path": "_example/refer/main.go",
"chars": 1362,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/golang/glog\"\n\t\"github.com/gookit/slog\"\n\t\"github.com/sirupsen"
},
{
"path": "benchmark2_test.go",
"chars": 2906,
"preview": "package slog\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/dump\"\n)\n\nfunc TestLogger_newRecord_AllocTimes"
},
{
"path": "benchmark_test.go",
"chars": 1845,
"preview": "package slog_test\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/dump\"\n\t\"github.com/gookit/slog\"\n\t\"github.com/go"
},
{
"path": "bufwrite/bufio_writer.go",
"chars": 901,
"preview": "// Package bufwrite provides buffered io.Writer with sync and close methods.\npackage bufwrite\n\nimport (\n\t\"bufio\"\n\t\"io\"\n)"
},
{
"path": "bufwrite/bufwrite_test.go",
"chars": 5107,
"preview": "package bufwrite_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/errorx\"\n\t\"github.com/gookit/goutil/testu"
},
{
"path": "bufwrite/line_writer.go",
"chars": 3834,
"preview": "package bufwrite\n\nimport (\n\t\"io\"\n)\n\nconst (\n\tdefaultBufSize = 1024 * 8\n)\n\n// LineWriter implements buffering for an io.W"
},
{
"path": "common.go",
"chars": 8807,
"preview": "package slog\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/envutil\"\n\t\"github.com/gookit/"
},
{
"path": "common_test.go",
"chars": 6785,
"preview": "package slog_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/byteutil\"\n\t\"github.com/gookit/goutil/"
},
{
"path": "example_test.go",
"chars": 1763,
"preview": "package slog_test\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/handler\"\n)\n\nfunc "
},
{
"path": "formatter.go",
"chars": 1798,
"preview": "package slog\n\nimport \"runtime\"\n\n//\n// Formatter interface\n//\n\n// Formatter interface\ntype Formatter interface {\n\t// Form"
},
{
"path": "formatter_json.go",
"chars": 3140,
"preview": "package slog\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\nvar (\n\t// DefaultFields default log exp"
},
{
"path": "formatter_test.go",
"chars": 5331,
"preview": "package slog_test\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/byteutil\"\n\t\"github.com/g"
},
{
"path": "formatter_text.go",
"chars": 6500,
"preview": "package slog\n\nimport (\n\t\"github.com/gookit/color\"\n\t\"github.com/gookit/goutil/arrutil\"\n\t\"github.com/valyala/bytebufferpoo"
},
{
"path": "go.mod",
"chars": 428,
"preview": "module github.com/gookit/slog\n\ngo 1.19\n\nrequire (\n\tgithub.com/gookit/color v1.6.0\n\tgithub.com/gookit/goutil v0.7.4\n\tgith"
},
{
"path": "go.sum",
"chars": 1695,
"preview": "github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=\ngithub.com/gookit/color v1.6.0 h1:JjJXBT"
},
{
"path": "handler/README.md",
"chars": 6688,
"preview": "# Handlers\n\nPackage handler provide useful common log handlers. eg: file, console, multi_file, rotate_file, stream, sysl"
},
{
"path": "handler/buffer.go",
"chars": 1728,
"preview": "package handler\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/bufwrite\"\n)\n\n// NewBuffered cr"
},
{
"path": "handler/buffer_test.go",
"chars": 2733,
"preview": "package handler_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/fsutil\"\n\t\"github.com/gookit/goutil/testutil/"
},
{
"path": "handler/builder.go",
"chars": 3278,
"preview": "package handler\n\nimport (\n\t\"io\"\n\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/rotatefile\"\n)\n\n//\n// ---------------"
},
{
"path": "handler/config.go",
"chars": 9991,
"preview": "package handler\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"io/fs\"\n\t\"strings\"\n\n\t\"github.com/gookit/goutil/errorx\"\n\t\"github.com/go"
},
{
"path": "handler/config_test.go",
"chars": 6411,
"preview": "package handler_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/dump\"\n\t\"github.com/gookit/goutil/errorx\"\n"
},
{
"path": "handler/console.go",
"chars": 1535,
"preview": "package handler\n\nimport (\n\t\"os\"\n\n\t\"github.com/gookit/color\"\n\t\"github.com/gookit/slog\"\n)\n\n/******************************"
},
{
"path": "handler/console_test.go",
"chars": 403,
"preview": "package handler_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/testutil/assert\"\n\t\"github.com/gookit/slog\"\n\t\"githu"
},
{
"path": "handler/email.go",
"chars": 1153,
"preview": "package handler\n\nimport (\n\t\"net/smtp\"\n\t\"strconv\"\n\n\t\"github.com/gookit/slog\"\n)\n\n// EmailOption struct\ntype EmailOption st"
},
{
"path": "handler/example_test.go",
"chars": 949,
"preview": "package handler_test\n\nimport (\n\t\"github.com/gookit/slog\"\n\t\"github.com/gookit/slog/handler\"\n)\n\nfunc Example_fileHandler()"
},
{
"path": "handler/file.go",
"chars": 1829,
"preview": "package handler\n\nimport (\n\t\"github.com/gookit/goutil/x/basefn\"\n\t\"github.com/gookit/slog\"\n)\n\n// JSONFileHandler create ne"
},
{
"path": "handler/file_test.go",
"chars": 3798,
"preview": "package handler_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/fsutil\"\n\t\"github.com/gookit/goutil/testutil/"
},
{
"path": "handler/handler.go",
"chars": 2720,
"preview": "// Package handler provide useful common log handlers.\n//\n// eg: file, console, multi_file, rotate_file, stream, syslog,"
},
{
"path": "handler/handler_test.go",
"chars": 4725,
"preview": "package handler_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil\"\n\t\"github.com/gookit/goutil/errorx\"\n\t\"githu"
},
{
"path": "handler/rotatefile.go",
"chars": 3401,
"preview": "package handler\n\nimport (\n\t\"github.com/gookit/goutil/x/basefn\"\n\t\"github.com/gookit/slog/rotatefile\"\n)\n\n// NewRotateFileH"
},
{
"path": "handler/rotatefile_test.go",
"chars": 4822,
"preview": "package handler_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/fsutil\"\n\t\"github.com/gookit/g"
},
{
"path": "handler/syslog.go",
"chars": 1723,
"preview": "//go:build !windows && !plan9\n\npackage handler\n\nimport (\n\t\"log/syslog\"\n\n\t\"github.com/gookit/slog\"\n)\n\n// SysLogOpt for sy"
},
{
"path": "handler/syslog_test.go",
"chars": 429,
"preview": "//go:build !windows && !plan9\n\npackage handler_test\n\nimport (\n\t\"log/syslog\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/testu"
},
{
"path": "handler/write_close_flusher.go",
"chars": 2104,
"preview": "package handler\n\nimport (\n\t\"github.com/gookit/slog\"\n)\n\n// FlushCloseHandler definition\ntype FlushCloseHandler struct {\n\t"
},
{
"path": "handler/write_close_syncer.go",
"chars": 2095,
"preview": "package handler\n\nimport (\n\t\"io\"\n\n\t\"github.com/gookit/slog\"\n)\n\n// SyncCloseHandler definition\ntype SyncCloseHandler struc"
},
{
"path": "handler/write_closer.go",
"chars": 2022,
"preview": "package handler\n\nimport (\n\t\"io\"\n\n\t\"github.com/gookit/slog\"\n)\n\n// WriteCloserHandler definition\ntype WriteCloserHandler s"
},
{
"path": "handler/writer.go",
"chars": 2989,
"preview": "package handler\n\nimport (\n\t\"io\"\n\n\t\"github.com/gookit/slog\"\n)\n\n// IOWriterHandler definition\ntype IOWriterHandler struct "
},
{
"path": "handler/writer_test.go",
"chars": 4167,
"preview": "package handler_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/fsutil\"\n\t\"github.com/gookit/goutil"
},
{
"path": "handler.go",
"chars": 5402,
"preview": "package slog\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/gookit/goutil/strutil\"\n)\n\n//\n// Handler interface\n//\n\n// Ha"
},
{
"path": "handler_test.go",
"chars": 2049,
"preview": "package slog_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/testutil/assert\"\n\t\"github.com/gookit/slog\"\n)\n\nfunc Te"
},
{
"path": "internal/util.go",
"chars": 481,
"preview": "package internal\n\nimport \"path/filepath\"\n\n// AddSuffix2path add suffix to file path.\n//\n// eg: \"/path/to/error.log\" => \""
},
{
"path": "issues_test.go",
"chars": 7387,
"preview": "package slog_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/byteutil\"\n\t\"github."
},
{
"path": "logger.go",
"chars": 18863,
"preview": "package slog\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil\"\n)\n\n// Logger log dispatcher definition.\n/"
},
{
"path": "logger_sub.go",
"chars": 5359,
"preview": "package slog\n\nimport \"context\"\n\n// SubLogger is a sub-logger, It can be used to keep a certain amount of contextual info"
},
{
"path": "logger_test.go",
"chars": 9893,
"preview": "package slog_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/dump\"\n\t\"github.com"
},
{
"path": "logger_write.go",
"chars": 2043,
"preview": "package slog\n\n//\n// ---------------------------------------------------------------------------\n// Do write log message\n"
},
{
"path": "processor.go",
"chars": 2565,
"preview": "package slog\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"os\"\n\t\"runtime\"\n\n\t\"github.com/gookit/goutil/strutil\"\n)\n\n//\n// Proc"
},
{
"path": "processor_test.go",
"chars": 2465,
"preview": "package slog_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/byteutil\"\n\t\"github.com/gookit"
},
{
"path": "record.go",
"chars": 11076,
"preview": "package slog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/strutil\"\n)\n\n// Record"
},
{
"path": "record_test.go",
"chars": 6641,
"preview": "package slog_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/byteutil\"\n\t\"g"
},
{
"path": "rotatefile/README.md",
"chars": 4241,
"preview": "# Rotate File\n\n `rotatefile` provides simple file rotation, compression and cleanup.\n\n## Features\n\n- Rotate file by size"
},
{
"path": "rotatefile/cleanup.go",
"chars": 6180,
"preview": "package rotatefile\n\nimport (\n\t\"os\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/errorx\"\n\t\"github.com/gookit/goutil/fsutil"
},
{
"path": "rotatefile/cleanup_test.go",
"chars": 2997,
"preview": "package rotatefile_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil\"\n\t\"github.com/gook"
},
{
"path": "rotatefile/config.go",
"chars": 10582,
"preview": "package rotatefile\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/strutil\"\n\t\"github.com/gookit/go"
},
{
"path": "rotatefile/config_test.go",
"chars": 4791,
"preview": "package rotatefile_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/dump\"\n\t\"github.com/gookit/goutil/jsonut"
},
{
"path": "rotatefile/issues_test.go",
"chars": 2557,
"preview": "package rotatefile_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/fsutil\"\n\t\"github.com/gookit/goutil/math"
},
{
"path": "rotatefile/rotatefile.go",
"chars": 584,
"preview": "// Package rotatefile provides simple file rotation, compression and cleanup.\npackage rotatefile\n\nimport (\n\t\"io\"\n)\n\n// R"
},
{
"path": "rotatefile/rotatefile_test.go",
"chars": 572,
"preview": "package rotatefile_test\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil\"\n\t\"github.com/gookit/goutil/fsuti"
},
{
"path": "rotatefile/util.go",
"chars": 2247,
"preview": "package rotatefile\n\nimport (\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil\"\n\t\"github."
},
{
"path": "rotatefile/util_test.go",
"chars": 160,
"preview": "package rotatefile\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestPrintErrln(t *testing.T) {\n\tprintErrln(\"test\", nil)\n\tprint"
},
{
"path": "rotatefile/writer.go",
"chars": 14988,
"preview": "package rotatefile\n\nimport (\n\t\"fmt\"\n\t\"io/fs\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"g"
},
{
"path": "rotatefile/writer_test.go",
"chars": 4505,
"preview": "package rotatefile_test\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gookit/goutil/dump\"\n\t\"github.com/goo"
},
{
"path": "slog.go",
"chars": 9525,
"preview": "/*\nPackage slog Lightweight, extensible, configurable logging library written in Go.\n\nSource code and other details for "
},
{
"path": "slog_test.go",
"chars": 12167,
"preview": "package slog_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/goo"
},
{
"path": "sugared.go",
"chars": 3404,
"preview": "package slog\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/gookit/color\"\n)\n\n// SugaredLoggerFn func type.\ntype SugaredLoggerFn fun"
},
{
"path": "util.go",
"chars": 4133,
"preview": "package slog\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/gookit/goutil/byteut"
},
{
"path": "util_test.go",
"chars": 1976,
"preview": "package slog\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gookit/goutil/errorx\"\n\t\"github.com/gookit/goutil/testutil/ass"
}
]
About this extraction
This page contains the full source code of the gookit/slog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 90 files (366.7 KB), approximately 107.4k tokens, and a symbol index with 932 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.