25 Commits

Author SHA1 Message Date
9ccc7030f2 Update README.md
All checks were successful
Go Action / goaction (push) Successful in 19m15s
2025-12-23 22:48:17 +03:00
9f8f38e43f add lint action (#19)
All checks were successful
Go Action / goaction (push) Successful in 19m15s
Reviewed-on: #19
2025-12-23 22:01:14 +03:00
859a8d88f7 Merge pull request 'update ling and go version' (#17) from format into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #17
2024-04-04 12:19:45 +03:00
4baf4b36e7 set golang version
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-04-04 12:17:02 +03:00
andrey
2fd0c1f9ec update ling and go version
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-04-04 12:12:09 +03:00
c7090b5067 Merge pull request 'use dependency format log' (#16) from format into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #16
2024-04-04 12:08:06 +03:00
andrey
16d3e04fd9 use dependency format log 2024-04-04 12:04:45 +03:00
57a908f894 add bracket format (#15)
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #15
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-03 21:35:21 +03:00
acaa46b73f update source (#14)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #14
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-03 18:44:40 +03:00
24a5d3dd88 Merge pull request 'add source with func name' (#13) from source into master
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
Reviewed-on: #13
2024-01-02 20:23:08 +03:00
andrey
1de7cc0034 add source with func name
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-01-02 20:22:35 +03:00
9a61d4b9d3 Merge pull request 'add handler zap' (#12) from zap into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #12
2024-01-02 17:33:24 +03:00
andrey
4aef5329c7 add handler zap
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-01-02 17:18:18 +03:00
eb1708a296 Merge pull request 'add logrus handler' (#11) from logrus into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #11
2024-01-02 17:14:38 +03:00
andrey
50cfee751d add logrus handler
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-01-02 17:03:49 +03:00
722669f094 add otel as separate module (#10)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #10
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-02 16:56:49 +03:00
d365c5b36b Merge pull request 'remove handlers' (#9) from handlers into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #9
2024-01-02 15:59:46 +03:00
andrey
e7774808fd remove handlers
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
continuous-integration/drone/pr Build is failing
2024-01-02 15:58:24 +03:00
abbcf0b1a0 update field (#8)
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
Reviewed-on: #8
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-02 15:45:34 +03:00
a3091c4eb6 update level (#7)
Some checks failed
continuous-integration/drone/push Build is failing
- add TextUnmarshaler
- add TextMarshaler
- update json.Marshaler
- update json.Unmarshaler
- update string name Emergency and Critical
- add test json Unmarshaler and Marshaler

Reviewed-on: #7
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-01 17:00:37 +03:00
andrey
45d51e4825 update libs
Some checks failed
continuous-integration/drone/push Build is failing
2023-09-19 10:24:42 +03:00
81eaf8c8b2 update opentelemetry version (#6)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Co-authored-by: andrey1s <andrey@4devs.pro>
Reviewed-on: #6
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2022-09-06 22:25:04 +03:00
7570ade82a upddate caller write method (#5)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: andrey1s <andrey@4devs.pro>
Reviewed-on: #5
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2022-03-14 09:58:23 +03:00
012e2ce197 Merge pull request 'update golang' (#4) from fields into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #4
2022-03-13 13:17:01 +03:00
andrey1s
51b844d50b update golang
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-03-13 12:01:36 +03:00
57 changed files with 2823 additions and 2208 deletions

View File

@@ -1,9 +1,11 @@
---
kind: pipeline
name: default
type: docker
name: logger
steps:
- name: test
image: golang:1.17.6
image: golang:1.22.2
volumes:
- name: deps
path: /go/src/mod
@@ -11,7 +13,7 @@ steps:
- go test
- name: golangci-lint
image: golangci/golangci-lint:v1.42
image: golangci/golangci-lint:v1.57
commands:
- golangci-lint run

View File

@@ -0,0 +1,26 @@
name: Go Action
on: [push, pull_request]
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
- name: Run go test
run: go test ./...

View File

@@ -1,6 +1,30 @@
linters-settings:
version: "2"
linters:
default: all
disable:
- wsl
- noinlineerr
settings:
depguard:
rules:
main:
allow:
- $gostd
- gitoa.ru
funcorder:
constructor: false
recvcheck:
disable-builtin: false
exclusions:
- "*.String"
- "*.UnmarshalText"
- "*.UnmarshalJSON"
- "*.UnmarshalBinary"
dupl:
threshold: 100
exhaustive:
default-signifies-exhaustive: true
funlen:
lines: 100
statements: 50
@@ -9,33 +33,61 @@ linters-settings:
min-occurrences: 2
gocyclo:
min-complexity: 15
govet:
check-shadowing: true
lll:
line-length: 140
fieldalignment:
suggest-new: true
misspell:
locale: US
exhaustive:
default-signifies-exhaustive: true
locale: US
tagliatelle:
case:
use-field-name: true
rules:
json: snake
yaml: camel
xml: camel
bson: camel
avro: snake
linters:
enable-all: true
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- path: _test\.go
linters:
- gomnd
- exhaustivestruct
bson: camel
json: snake
xml: camel
yaml: camel
use-field-name: true
varnamelen:
min-name-length: 2
ignore-names:
- err
- "n"
- i
- w
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- exhaustruct
- gochecknoglobals
- ireturn
- mnd
path: _test\.go
- linters:
- err113
- lll
path: _example_test\.go
- linters:
- lll
- mnd
path: example/*
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View File

@@ -1,4 +1,4 @@
MIT License Copyright (c) 2020 go-4devs
MIT License Copyright (c) 2020-2022 4devs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,5 +1,5 @@
# log
[![Build Status](https://drone.gitoa.ru/api/badges/go-4devs/log/status.svg)](https://drone.gitoa.ru/go-4devs/log)
![Build Status](https://gitoa.ru/go-4devs/log/actions/workflows/goaction.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/gitoa.ru/go-4devs/log)](https://goreportcard.com/report/gitoa.ru/go-4devs/log)
[![GoDoc](https://godoc.org/gitoa.ru/go-4devs/log?status.svg)](http://godoc.org/gitoa.ru/go-4devs/log)

View File

@@ -4,7 +4,7 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"io"
"testing"
"time"
@@ -12,7 +12,6 @@ import (
"gitoa.ru/go-4devs/log/field"
)
// nolint: gochecknoglobals
var (
errExample = errors.New("fail")
_messages = fakeMessages(1000)
@@ -70,10 +69,10 @@ func getMessage(iter int) string {
return _messages[iter%1000]
}
func fakeFmtArgs() []interface{} {
func fakeFmtArgs() []any {
// Need to keep this a function instead of a package-global var so that we
// pay the cast-to-interface{} penalty on each call.
return []interface{}{
return []any{
_tenInts[0],
_tenInts,
_tenStrings[0],
@@ -102,8 +101,8 @@ func fakeFields() []field.Field {
}
}
func fakeSugarFields() []interface{} {
return []interface{}{
func fakeSugarFields() []any {
return []any{
"int", _tenInts[0],
"ints", _tenInts,
"string", _tenStrings[0],
@@ -118,7 +117,7 @@ func fakeSugarFields() []interface{} {
}
func NewLogger() log.Logger {
return log.New(log.WithWriter(ioutil.Discard))
return log.New(log.WithWriter(io.Discard))
}
func BenchmarkDisabledWithoutFields(b *testing.B) {
@@ -167,7 +166,9 @@ func BenchmarkDisabledAccumulatedContext(b *testing.B) {
b.Run("4devs/log.Context", func(b *testing.B) {
b.ResetTimer()
logger := NewLogger().With(log.GoVersion("goversion"))
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.InfoKV(ctx, getMessage(0), fakeFields()...)

View File

@@ -7,14 +7,15 @@ import (
)
func Caller(depth int, full bool) string {
const offset = 4
_, file, line, ok := runtime.Caller(depth + offset)
const offset = 3
if !ok {
_, file, line, has := runtime.Caller(depth + offset)
if !has {
file, line = "???", 0
}
if !full && ok {
if !full && has {
file = filepath.Base(file)
}

View File

@@ -1,6 +1,7 @@
package entry
import (
"fmt"
"strings"
"gitoa.ru/go-4devs/log/field"
@@ -27,7 +28,14 @@ func WithFields(fields ...field.Field) Option {
func WithMessage(msg string) Option {
return func(e *Entry) {
e.msg = msg
e.format = msg
}
}
func WithMessagef(format string, args ...any) Option {
return func(e *Entry) {
e.format = format
e.args = args
}
}
@@ -38,28 +46,32 @@ func WithLevel(lvl level.Level) Option {
}
func New(opts ...Option) *Entry {
e := &Entry{
entry := &Entry{
fields: make(field.Fields, 0, defaultCap+1),
level: level.Debug,
msg: "",
format: "",
args: make([]any, 0, defaultCap+1),
}
for _, opt := range opts {
opt(e)
opt(entry)
}
return e
return entry
}
// Entry slice field.
type Entry struct {
msg string
format string
args []any
level level.Level
fields field.Fields
}
func (e *Entry) Reset() {
e.fields = e.fields[:0]
e.args = e.args[:0]
e.format = ""
}
func (e *Entry) Fields() field.Fields {
@@ -73,7 +85,7 @@ func (e *Entry) String() string {
}
str := make([]string, len(e.fields)+1)
str[0] = e.msg
str[0] = e.Message()
for i, field := range e.fields {
str[i+1] = field.String()
@@ -83,7 +95,14 @@ func (e *Entry) String() string {
}
func (e *Entry) Message() string {
return e.msg
switch {
case len(e.args) > 0 && e.format != "":
return fmt.Sprintf(e.format, e.args...)
case len(e.args) > 0:
return fmt.Sprint(e.args...)
default:
return e.format
}
}
func (e *Entry) Level() level.Level {
@@ -109,7 +128,18 @@ func (e *Entry) SetMessage(msg string) *Entry {
return New().SetMessage(msg)
}
e.msg = msg
e.format = msg
return e
}
func (e *Entry) SetMessagef(format string, args ...any) *Entry {
if e == nil {
return New().SetMessagef(format, args...)
}
e.format = format
e.args = append(e.args[:0], args...)
return e
}
@@ -124,10 +154,31 @@ func (e *Entry) Add(fields ...field.Field) *Entry {
return e
}
func (e *Entry) AddAny(key string, value interface{}) *Entry {
func (e *Entry) AddAny(key string, value any) *Entry {
return e.Add(field.Any(key, value))
}
func (e *Entry) AddString(key, value string) *Entry {
return e.Add(field.String(key, value))
}
func (e *Entry) Replace(key string, value field.Value) *Entry {
has := false
e.fields.Fields(func(f field.Field) bool {
if f.Key == key {
f.Value = value
has = true
return false
}
return true
})
if !has {
e.AddAny(key, value)
}
return e
}

View File

@@ -2,14 +2,14 @@ package entry
import "sync"
// nolint: gochecknoglobals
//nolint:gochecknoglobals
var pool = sync.Pool{
New: func() interface{} {
New: func() any {
return New()
},
}
//nolint: forcetypeassert
//nolint:forcetypeassert
func Get() *Entry {
e := pool.Get().(*Entry)
e.Reset()

38
example/log.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"context"
"time"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/field"
)
func main() {
ctx := context.Background()
log.DebugKV(ctx, "debug message")
log.ErrKV(ctx, "error message")
log.Errf(ctx, "format error message:%v", 42)
log.Err(ctx, "error message", 42)
service(ctx, log.Log())
logger := log.New(log.WithJSONFormat()).With(log.WithSource(10, log.TrimPath), log.WithTime(log.KeyTime, time.RFC3339))
logger.AlertKV(ctx, "alert message new logger", field.String("string", "value"))
service(ctx, logger)
strLogger := log.New(log.WithFormat(log.FormatWithBracket(field.NewEncoderText()))).With(log.WithSource(10, log.TrimPath), log.WithTime(log.KeyTime, time.RFC3339))
strLogger.AlertKV(ctx, "alert message new txt logger", field.String("string", "value"))
service(ctx, strLogger)
}
func service(ctx context.Context, logger log.Logger) {
logger = logger.With(log.WithName("service"))
logger.WarnKV(ctx, "warn service message")
otherService(ctx, logger)
}
func otherService(ctx context.Context, logger log.Logger) {
logger = logger.With(log.WithName("other_service"))
logger.WarnKV(ctx, "warn other service message")
}

View File

@@ -5,7 +5,7 @@ import (
)
// Field create field.
func Field(key string, value interface{}) field.Field {
func Field(key string, value any) field.Field {
return field.Any(key, value)
}

View File

@@ -1,31 +1,284 @@
package field
import "time"
import (
"fmt"
"strconv"
"time"
"unicode"
"unicode/utf8"
)
type Encoder interface {
// Built-in types.
AddArray(key string, value Value)
AddAny(key string, value Value)
AddNil(key string)
AddBool(key string, value bool)
AddBinary(key string, value []byte)
AddInt(key string, value int)
AddInt8(key string, value int8)
AddInt16(key string, value int16)
AddInt32(key string, value int32)
AddInt64(key string, value int64)
AddUint(key string, value uint)
AddUint8(key string, value uint8)
AddUint16(key string, value uint16)
AddUint32(key string, value uint32)
AddUint64(key string, value uint64)
AddUintptr(key string, value uintptr)
AddTime(key string, value time.Time)
AddDuration(key string, value time.Duration)
AddFloat32(key string, value float32)
AddFloat64(key string, value float64)
AddComplex64(key string, value complex64)
AddComplex128(key string, value complex128)
AddString(key, value string)
AddError(key string, value error)
AppendField(dst []byte, field Field) []byte
AppendValue(dst []byte, val Value) []byte
}
func WithAppendString(fn func(dst []byte, in string) []byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.AppendString = fn
}
}
func WithNullValue(in string) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.nullValue = []byte(in)
}
}
func WithDelimeter(in byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.delimeter = in
}
}
func WithGropuConfig(start, end, deli byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.group = groupConfig{
start: start,
end: end,
deli: deli,
}
}
}
func WithDefaultValue(fn func(dst []byte, e Encoder, val Value) []byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.DefaultValue = fn
}
}
func NewEncoder(opts ...func(*BaseEncoder)) BaseEncoder {
be := BaseEncoder{
nullValue: []byte("null"),
group: groupConfig{
start: '{',
end: '}',
deli: ',',
},
array: groupConfig{
start: '[',
end: ']',
deli: ',',
},
timeFormat: time.RFC3339,
AppendString: AppendString,
delimeter: '=',
DefaultValue: func(dst []byte, e Encoder, val Value) []byte {
return e.AppendValue(dst, StringValue(fmt.Sprintf("%+v", val.Any())))
},
}
for _, opt := range opts {
opt(&be)
}
return be
}
type groupConfig struct {
start byte
end byte
deli byte
}
type BaseEncoder struct {
nullValue []byte
group groupConfig
array groupConfig
timeFormat string
AppendString func(dst []byte, in string) []byte
delimeter byte
DefaultValue func(dst []byte, e Encoder, val Value) []byte
}
func (b BaseEncoder) AppendValue(dst []byte, val Value) []byte {
return b.appendValue(dst, val, "", 0)
}
func (b BaseEncoder) AppendDelimiter(dst []byte, deli byte) []byte {
if deli == 0 {
return dst
}
return append(dst, deli)
}
func (b BaseEncoder) AppendDuration(dst []byte, d time.Duration) []byte {
return b.AppendString(dst, d.String())
}
func (b BaseEncoder) AppendTime(dst []byte, t time.Time) []byte {
return b.AppendString(dst, t.Format(b.timeFormat))
}
func AppendString(dst []byte, in string) []byte {
if needsQuoting(in) {
return strconv.AppendQuote(dst, in)
}
return append(dst, in...)
}
//nolint:cyclop
func needsQuoting(in string) bool {
if len(in) == 0 {
return true
}
for i := 0; i < len(in); {
char := in[i]
if char < utf8.RuneSelf {
// Quote anything except a backslash that would need quoting in a
// JSON string, as well as space and '='
if char != '\\' && (char == ' ' || char == '=' || !safeSet[char]) {
return true
}
i++
continue
}
decodeRune, size := utf8.DecodeRuneInString(in[i:])
if decodeRune == utf8.RuneError || unicode.IsSpace(decodeRune) || !unicode.IsPrint(decodeRune) {
return true
}
i += size
}
return false
}
func (b BaseEncoder) AppendField(dst []byte, field Field) []byte {
prefix := ""
if len(dst) != 0 {
prew := dst[len(dst)-1]
if prew != '{' && prew != '.' {
prefix = string(b.group.deli)
}
}
return b.appendField(dst, field, prefix, b.delimeter)
}
func (b BaseEncoder) AppendKey(dst []byte, key string, prefix string) []byte {
if prefix != "" {
dst = append(dst, prefix...)
}
return b.AppendString(dst, key)
}
//nolint:mnd
func (b BaseEncoder) AppendComplex(dst []byte, c complex128) []byte {
cmplx := strconv.FormatComplex(c, 'g', -1, 128)
return b.AppendString(dst, cmplx)
}
func (b BaseEncoder) AppendFloat(dst []byte, f float64, bitSize int) []byte {
return strconv.AppendFloat(dst, f, 'g', -1, bitSize)
}
//nolint:mnd
func (b BaseEncoder) AppendUint(dst []byte, u uint64) []byte {
return strconv.AppendUint(dst, u, 10)
}
func (b BaseEncoder) AppendNull(dst []byte) []byte {
return append(dst, b.nullValue...)
}
//nolint:mnd
func (b BaseEncoder) AppendInt(dst []byte, val int64) []byte {
return strconv.AppendInt(dst, val, 10)
}
func (b BaseEncoder) AppendBool(dst []byte, val bool) []byte {
return strconv.AppendBool(dst, val)
}
func (b BaseEncoder) AppendGroup(dst []byte, fields []Field) []byte {
dst = append(dst, b.group.start)
dst = b.appendGroup(dst, fields, "")
return append(dst, b.group.end)
}
func (b BaseEncoder) AppendArray(dst []byte, in []Value) []byte {
dst = append(dst, b.array.start)
if len(in) > 0 {
dst = b.appendValue(dst, in[0], "", 0)
for _, value := range in[1:] {
dst = b.appendValue(append(dst, b.array.deli), value, "", 0)
}
}
return append(dst, b.array.end)
}
func (b BaseEncoder) AppendBytes(dst, in []byte) []byte {
dst = append(dst, '"')
dst = append(dst, in...)
return append(dst, '"')
}
func (b BaseEncoder) appendGroup(dst []byte, fields []Field, prefix string) []byte {
if len(fields) > 0 {
dst = b.appendField(dst, fields[0], ".", b.delimeter)
for _, field := range fields[1:] {
dst = b.appendField(append(dst, b.group.deli), field, prefix, b.delimeter)
}
}
return dst
}
func (b BaseEncoder) appendField(dst []byte, field Field, prefix string, deli byte) []byte {
dst = b.AppendKey(dst, field.Key, prefix)
return b.appendValue(dst, field.Value, field.Key+".", deli)
}
//nolint:mnd,gocyclo,cyclop
func (b BaseEncoder) appendValue(dst []byte, val Value, prefix string, deli byte) []byte {
switch val.Kind {
case KindGroup:
return b.appendGroup(dst, val.AsGroup(), prefix)
case KindClosure:
return b.appendValue(dst, AnyValue(val.Resolve()), prefix, deli)
case KindArray:
return b.AppendArray(b.AppendDelimiter(dst, deli), val.AsArray())
case KindNil:
return b.AppendNull(b.AppendDelimiter(dst, deli))
case KindBool:
return b.AppendBool(b.AppendDelimiter(dst, deli), val.AsBool())
case KindBinary:
return b.AppendBytes(b.AppendDelimiter(dst, deli), val.AsBinary())
case KindComplex128:
return b.AppendComplex(b.AppendDelimiter(dst, deli), val.AsComplex128())
case KindInt64:
return b.AppendInt(b.AppendDelimiter(dst, deli), val.AsInt64())
case KindFloat32:
return b.AppendFloat(b.AppendDelimiter(dst, deli), float64(val.AsFloat32()), 32)
case KindFloat64:
return b.AppendFloat(b.AppendDelimiter(dst, deli), val.AsFloat64(), 64)
case KindUint64:
return b.AppendUint(b.AppendDelimiter(dst, deli), val.AsUint64())
case KindError:
return b.AppendString(b.AppendDelimiter(dst, deli), val.AsError().Error())
case KindString:
return b.AppendString(b.AppendDelimiter(dst, deli), val.AsString())
case KindDuration:
return b.AppendDuration(b.AppendDelimiter(dst, deli), val.AsDuration())
case KindTime:
return b.AppendTime(b.AppendDelimiter(dst, deli), val.AsTime())
case KindAny:
return b.DefaultValue(b.AppendDelimiter(dst, deli), b, val)
}
return b.DefaultValue(b.AppendDelimiter(dst, deli), b, val)
}

23
field/encoder_json.go Normal file
View File

@@ -0,0 +1,23 @@
package field
import (
"encoding/json"
"strconv"
)
func NewEncoderJSON(opts ...func(*BaseEncoder)) BaseEncoder {
opts = append([]func(*BaseEncoder){
WithAppendString(strconv.AppendQuote),
WithDelimeter(':'),
WithDefaultValue(func(dst []byte, e Encoder, val Value) []byte {
js, err := json.Marshal(val.Any())
if err != nil {
return e.AppendValue(dst, ErrorValue(err))
}
return append(dst, js...)
}),
}, opts...)
return NewEncoder(opts...)
}

View File

@@ -0,0 +1,39 @@
package field_test
import (
"testing"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/internal/buffer"
)
func TestEncoderJSONAppendField_string(t *testing.T) {
t.Parallel()
const expect = `"array":["value","other"],"str":"value","nullableStr":"value","nullstr":null`
encode := field.NewEncoderJSON()
buf := buffer.New()
defer func() {
buf.Free()
}()
val := "value"
strs := field.Strings("array", val, "other")
*buf = encode.AppendField(*buf, strs)
str := field.String("str", val)
*buf = encode.AppendField(*buf, str)
strp := field.Stringp("nullableStr", &val)
*buf = encode.AppendField(*buf, strp)
nullStr := field.Stringp("nullstr", nil)
*buf = encode.AppendField(*buf, nullStr)
if buf.String() != expect {
t.Errorf("json string expect:%v got:%s", expect, buf)
}
}

28
field/encoder_text.go Normal file
View File

@@ -0,0 +1,28 @@
package field
import (
"encoding"
"fmt"
)
func NewEncoderText(opts ...func(*BaseEncoder)) BaseEncoder {
opts = append([]func(*BaseEncoder){
WithGropuConfig(0, 0, ' '),
WithNullValue("<nil>"),
WithDefaultValue(func(dst []byte, enc Encoder, val Value) []byte {
switch value := val.Any().(type) {
case encoding.TextMarshaler:
data, err := value.MarshalText()
if err != nil {
return enc.AppendValue(dst, ErrorValue(err))
}
return enc.AppendValue(dst, StringValue(string(data)))
default:
return fmt.Appendf(dst, "%+v", val.Any())
}
}),
}, opts...)
return NewEncoder(opts...)
}

5
field/errors.go Normal file
View File

@@ -0,0 +1,5 @@
package field
import "errors"
var ErrUndefined = errors.New("indefined")

View File

@@ -2,332 +2,507 @@ package field
import (
"fmt"
"slices"
"time"
)
func Any(key string, value interface{}) Field {
return Key(key).Any(value)
func Any(key string, value any) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func String(key, value string) Field {
return Key(key).String(value)
return Field{
Key: key,
Value: StringValue(value),
}
}
func Stringp(key string, value *string) Field {
return Key(key).Stringp(value)
return Field{
Key: key,
Value: StringpValue(value),
}
}
func Strings(key string, value ...string) Field {
return Key(key).Strings(value...)
return Field{
Key: key,
Value: StringsValue(value),
}
}
func Bool(key string, value bool) Field {
return Key(key).Bool(value)
return Field{
Key: key,
Value: BoolValue(value),
}
}
func Bools(key string, value ...bool) Field {
return Key(key).Bools(value...)
return Field{
Key: key,
Value: BoolsValue(value),
}
}
func Boolp(key string, value *bool) Field {
return Key(key).Boolp(value)
return Field{
Key: key,
Value: BoolpValue(value),
}
}
func Uint(key string, value uint) Field {
return Key(key).Uint(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uints(key string, value ...uint) Field {
return Key(key).Uints(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uintp(key string, value *uint) Field {
return Key(key).Uintp(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint8(key string, value uint8) Field {
return Key(key).Uint8(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint8s(key string, value ...uint8) Field {
return Key(key).Uint8s(value...)
return Field{
Key: key,
Value: Uint8sValue(value),
}
}
func Uint8p(key string, value *uint8) Field {
return Key(key).Uint8p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint16(key string, value uint16) Field {
return Key(key).Uint16(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint16s(key string, value ...uint16) Field {
return Key(key).Uint16s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint16p(key string, value *uint16) Field {
return Key(key).Uint16p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint32(key string, value uint32) Field {
return Key(key).Uint32(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint32s(key string, value ...uint32) Field {
return Key(key).Uint32s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint32p(key string, value *uint32) Field {
return Key(key).Uint32p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint64(key string, value uint64) Field {
return Key(key).Uint64(value)
return Field{
Key: key,
Value: Uint64Value(value),
}
}
func Uint64s(key string, value ...uint64) Field {
return Key(key).Uint64s(value...)
return Field{
Key: key,
Value: Uint64sValue(value),
}
}
func Uint64p(key string, value *uint64) Field {
return Key(key).Uint64p(value)
return Field{
Key: key,
Value: Uint64pValue(value),
}
}
func Int(key string, value int) Field {
return Key(key).Int(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Ints(key string, value ...int) Field {
return Key(key).Ints(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Intp(key string, value *int) Field {
return Key(key).Intp(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int8(key string, value int8) Field {
return Key(key).Int8(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int8s(key string, value ...int8) Field {
return Key(key).Int8s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int8p(key string, value *int8) Field {
return Key(key).Int8p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int16(key string, value int16) Field {
return Key(key).Int16(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int16s(key string, value ...int16) Field {
return Key(key).Int16s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int16p(key string, value *int16) Field {
return Key(key).Int16p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int32(key string, value int32) Field {
return Key(key).Int32(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int32s(key string, value ...int32) Field {
return Key(key).Int32s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int32p(key string, value *int32) Field {
return Key(key).Int32p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int64(key string, value int64) Field {
return Key(key).Int64(value)
return Field{
Key: key,
Value: Int64Value(value),
}
}
func Int64s(key string, value ...int64) Field {
return Key(key).Int64s(value...)
return Field{
Key: key,
Value: Int64sValue(value),
}
}
func Int64p(key string, value *int64) Field {
return Key(key).Int64p(value)
return Field{
Key: key,
Value: Int64pValue(value),
}
}
func Float32(key string, value float32) Field {
return Key(key).Float32(value)
return Field{
Key: key,
Value: Float64Value(float64(value)),
}
}
func Float32s(key string, value ...float32) Field {
return Key(key).Float32s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Float32p(key string, value *float32) Field {
return Key(key).Float32p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Float64(key string, value float64) Field {
return Key(key).Float64(value)
return Field{
Key: key,
Value: Float64Value(value),
}
}
func Float64s(key string, value ...float64) Field {
return Key(key).Float64s(value...)
return Field{
Key: key,
Value: Float64sValue(value),
}
}
func Float64p(key string, value *float64) Field {
return Key(key).Float64p(value)
return Field{
Key: key,
Value: Float64pValue(value),
}
}
func Complex64(key string, value complex64) Field {
return Key(key).Complex64(value)
return Field{
Key: key,
Value: Complex128Value(complex128(value)),
}
}
func Complex64s(key string, value ...complex64) Field {
return Key(key).Complex64s(value...)
return Field{
Key: key,
Value: Complex64sValue(value),
}
}
func Complex64p(key string, value *complex64) Field {
return Key(key).Complex64p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Complex128(key string, value complex128) Field {
return Field{
Key: key,
Value: Complex128Value(value),
}
}
func Complex128s(key string, value ...complex128) Field {
return Field{
Key: key,
Value: Complex128sValue(value),
}
}
func Complex128p(key string, value *complex128) Field {
return Field{
Key: key,
Value: Complex128pValue(value),
}
}
func Uintptr(key string, value uintptr) Field {
return Key(key).Uintptr(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uintptrs(key string, value ...uintptr) Field {
return Key(key).Uintptrs(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uintptrp(key string, value *uintptr) Field {
return Key(key).Uintptrp(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Bytes(key string, value []byte) Field {
return Key(key).Bytes(value)
return Field{
Key: key,
Value: BytesValue(value),
}
}
func Duration(key string, value time.Duration) Field {
return Key(key).Dureation(value)
return Field{
Key: key,
Value: DurationValue(value),
}
}
func Durations(key string, value ...time.Duration) Field {
return Key(key).Dureations(value)
return Field{
Key: key,
Value: DurationsValue(value),
}
}
func Durationp(key string, value *time.Duration) Field {
return Key(key).Dureationp(value)
return Field{
Key: key,
Value: DurationpValue(value),
}
}
func Time(key string, value time.Time) Field {
return Key(key).Time(value)
return Field{
Key: key,
Value: TimeValue(value),
}
}
func Times(key string, value ...time.Time) Field {
return Key(key).Times(value...)
return Field{
Key: key,
Value: TimesValue(value),
}
}
func Timep(key string, value *time.Time) Field {
return Key(key).Timep(value)
return Field{
Key: key,
Value: TimepValue(value),
}
}
func FormatTime(key, format string, value time.Time) Field {
return Key(key).FormatTime(format, value)
return Field{
Key: key,
Value: ClosureValue(func() any {
return value.Format(format)
}),
}
}
func FormatTimes(key, foramt string, value ...time.Time) Field {
return Key(key).FormatTimes(foramt, value...)
func FormatTimes(key, format string, value ...time.Time) Field {
return Field{
Key: key,
Value: ClosureValue(func() any {
times := make([]any, len(value))
for idx, val := range value {
times[idx] = val.Format(format)
}
return times
}),
}
}
func FormatTimep(key, foramt string, value *time.Time) Field {
return Key(key).FormatTimep(foramt, value)
func FormatTimep(key, format string, value *time.Time) Field {
isNill := value == nil
return Field{
Key: key,
Value: ClosureValue(func() any {
if isNill {
return NilValue()
}
return value.Format(format)
}),
}
}
func Error(key string, value error) Field {
return Key(key).Error(value)
return Field{
Key: key,
Value: ErrorValue(value),
}
}
func Errors(key string, value ...error) Field {
return Key(key).Errors(value...)
return Field{
Key: key,
Value: ErrorsValue(value),
}
}
func Groups(key string, value ...Field) Field {
return Field{
Key: key,
Value: GroupValue(value...),
}
}
func Valuer(key string, value LogValuer) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func ValuerFn(key string, value ClosureFn) Field {
return Field{
Key: key,
Value: ClosureValue(value),
}
}
// Field struct.
type Field struct {
key Key
value Value
}
//nolint: gocyclo,cyclop
func (f Field) AddTo(enc Encoder) {
key := string(f.key)
switch {
case f.value.IsArray():
enc.AddAny(key, f.value)
case f.value.IsNil():
enc.AddNil(key)
case f.value.IsBool():
enc.AddBool(key, f.value.asBool())
case f.value.IsBinary():
enc.AddBinary(key, f.value.asBinary())
case f.value.IsInt():
enc.AddInt(key, f.value.asInt())
case f.value.IsInt8():
enc.AddInt8(key, f.value.asInt8())
case f.value.IsInt16():
enc.AddInt16(key, f.value.asInt16())
case f.value.IsInt32():
enc.AddInt32(key, f.value.asInt32())
case f.value.IsInt64():
enc.AddInt64(key, f.value.asInt64())
case f.value.IsUint():
enc.AddUint(key, f.value.asUint())
case f.value.IsUint8():
enc.AddUint8(key, f.value.asUint8())
case f.value.IsUint16():
enc.AddUint16(key, f.value.asUint16())
case f.value.IsUint32():
enc.AddUint32(key, f.value.asUint32())
case f.value.IsUint64():
enc.AddUint64(key, f.value.asUint64())
case f.value.IsUintptr():
enc.AddUintptr(key, f.value.asUintptr())
case f.value.IsTime():
enc.AddTime(key, f.value.asTime())
case f.value.IsDuration():
enc.AddDuration(key, f.value.asDuration())
case f.value.IsFloat32():
enc.AddFloat32(key, f.value.asFloat32())
case f.value.IsFloat64():
enc.AddFloat64(key, f.value.asFloat64())
case f.value.IsComplex64():
enc.AddComplex64(key, f.value.asComplex64())
case f.value.IsComplex128():
enc.AddComplex128(key, f.value.asComplex128())
case f.value.IsString():
enc.AddString(key, f.value.asString())
case f.value.IsError():
enc.AddError(key, f.value.asError())
default:
enc.AddAny(key, f.value)
}
}
func (f Field) Type() Type {
return f.value.vtype
}
func (f Field) Key() Key {
return f.key
}
func (f Field) Value() Value {
return f.value
}
func (f Field) AsInterface() interface{} {
return f.value.AsInterface()
Key string
Value Value
}
// String implent stringer.
func (f Field) String() string {
return fmt.Sprintf("%s=%+v", f.key, f.value.AsInterface())
return fmt.Sprintf("%s=%+v", f.Key, f.Value)
}
func (f Field) IsKey(keys ...string) bool {
return slices.Contains(keys, f.Key)
}

View File

@@ -2,7 +2,22 @@ package field
type Fields []Field
type MapField map[Key]Value
func (f Fields) Fields(fn func(Field) bool) {
for idx := range f {
if !fn(f[idx]) {
return
}
}
}
func (f Fields) Any() any {
fields := make(map[string]any)
for idx := range f {
fields[f[idx].Key] = f[idx].Value.Any()
}
return fields
}
func (f Fields) Append(fields ...Field) Fields {
f = append(f, fields...)
@@ -17,13 +32,3 @@ func (f Fields) Set(idx int, field Field) {
func (f Fields) Len() int {
return len(f)
}
func (f Fields) AsMap() MapField {
m := make(MapField, len(f))
for _, field := range f {
m[field.Key()] = field.Value()
}
return m
}

View File

@@ -3,13 +3,16 @@ package field_test
import (
"testing"
"github.com/stretchr/testify/require"
"gitoa.ru/go-4devs/log/field"
)
func TestFields_Append(t *testing.T) {
t.Parallel()
fields := field.Fields{field.Any("any", "value")}
fields = fields.Append(field.String("string", "value"))
require.Len(t, fields, 2)
if len(fields) != 2 {
t.Fatalf("require 2 field got %v", len(fields))
}
}

View File

@@ -1,570 +0,0 @@
package field
import (
"time"
)
type Key string
//nolint: gocyclo,funlen,cyclop
func (k Key) Any(value interface{}) Field {
switch v := value.(type) {
case string:
return k.String(v)
case *string:
return k.Stringp(v)
case []string:
return k.Strings(v...)
case bool:
return k.Bool(v)
case *bool:
return k.Boolp(v)
case []bool:
return k.Bools(v...)
case int8:
return k.Int8(v)
case []int8:
return k.Int8s(v...)
case *int8:
return k.Int8p(v)
case int16:
return k.Int16(v)
case []int16:
return k.Int16s(v...)
case *int16:
return k.Int16p(v)
case int32:
return k.Int32(v)
case []int32:
return k.Int32s(v...)
case *int32:
return k.Int32p(v)
case int64:
return k.Int64(v)
case []int64:
return k.Int64s(v...)
case *int64:
return k.Int64p(v)
case uint:
return k.Uint(v)
case []uint:
return k.Uints(v...)
case *uint:
return k.Uintp(v)
case uint8:
return k.Uint8(v)
case *uint8:
return k.Uint8p(v)
case uint16:
return k.Uint16(v)
case []uint16:
return k.Uint16s(v...)
case *uint16:
return k.Uint16p(v)
case uint32:
return k.Uint32(v)
case []uint32:
return k.Uint32s(v...)
case *uint32:
return k.Uint32p(v)
case uint64:
return k.Uint64(v)
case []uint64:
return k.Uint64s(v...)
case *uint64:
return k.Uint64p(v)
case float32:
return k.Float32(v)
case []float32:
return k.Float32s(v...)
case *float32:
return k.Float32p(v)
case float64:
return k.Float64(v)
case []float64:
return k.Float64s(v...)
case *float64:
return k.Float64p(v)
case complex64:
return k.Complex64(v)
case []complex64:
return k.Complex64s(v...)
case *complex64:
return k.Complex64p(v)
case uintptr:
return k.Uintptr(v)
case []uintptr:
return k.Uintptrs(v...)
case *uintptr:
return k.Uintptrp(v)
case []byte:
return k.Bytes(v)
case time.Duration:
return k.Dureation(v)
case []time.Duration:
return k.Dureations(v)
case *time.Duration:
return k.Dureationp(v)
case time.Time:
return k.Time(v)
case []time.Time:
return k.Times(v...)
case *time.Time:
return k.Timep(v)
case error:
return k.Error(v)
case []error:
return k.Errors(v...)
}
return Field{
key: k,
value: Value{
value: value,
vtype: TypeAny,
numeric: 0,
stringly: "",
},
}
}
func (k Key) String(value string) Field {
return Field{
key: k,
value: stringValue(value),
}
}
func (k Key) Strings(value ...string) Field {
return Field{
key: k,
value: stringsValue(value),
}
}
func (k Key) Stringp(value *string) Field {
return Field{
key: k,
value: stringpValue(value),
}
}
func (k Key) Bool(value bool) Field {
return Field{
key: k,
value: boolValue(value),
}
}
func (k Key) Bools(value ...bool) Field {
return Field{
key: k,
value: boolsValue(value),
}
}
func (k Key) Boolp(value *bool) Field {
return Field{
key: k,
value: boolpValue(value),
}
}
func (k Key) Int(value int) Field {
return Field{
key: k,
value: intValue(value),
}
}
func (k Key) Ints(value ...int) Field {
return Field{
key: k,
value: intsValue(value),
}
}
func (k Key) Intp(value *int) Field {
return Field{
key: k,
value: intpValue(value),
}
}
func (k Key) Int8(value int8) Field {
return Field{
key: k,
value: int8Value(value),
}
}
func (k Key) Int8s(value ...int8) Field {
return Field{
key: k,
value: int8sValue(value),
}
}
func (k Key) Int8p(value *int8) Field {
return Field{
key: k,
value: int8pValue(value),
}
}
func (k Key) Int16(value int16) Field {
return Field{
key: k,
value: int16Value(value),
}
}
func (k Key) Int16s(value ...int16) Field {
return Field{
key: k,
value: int16sValue(value),
}
}
func (k Key) Int16p(value *int16) Field {
return Field{
key: k,
value: int16pValue(value),
}
}
func (k Key) Int32(value int32) Field {
return Field{
key: k,
value: int32Value(value),
}
}
func (k Key) Int32s(value ...int32) Field {
return Field{
key: k,
value: int32sValue(value),
}
}
func (k Key) Int32p(value *int32) Field {
return Field{
key: k,
value: int32pValue(value),
}
}
func (k Key) Int64(value int64) Field {
return Field{
key: k,
value: int64Value(value),
}
}
func (k Key) Int64s(value ...int64) Field {
return Field{
key: k,
value: int64sValue(value),
}
}
func (k Key) Int64p(value *int64) Field {
return Field{
key: k,
value: int64pValue(value),
}
}
func (k Key) Uint(value uint) Field {
return Field{
key: k,
value: uintValue(value),
}
}
func (k Key) Uints(value ...uint) Field {
return Field{
key: k,
value: uintsValue(value),
}
}
func (k Key) Uintp(value *uint) Field {
return Field{
key: k,
value: uintpValue(value),
}
}
func (k Key) Uint8(value uint8) Field {
return Field{
key: k,
value: uint8Value(value),
}
}
func (k Key) Uint8s(value ...uint8) Field {
return Field{
key: k,
value: uint8sValue(value),
}
}
func (k Key) Uint8p(value *uint8) Field {
return Field{
key: k,
value: uint8pValue(value),
}
}
func (k Key) Uint16(value uint16) Field {
return Field{
key: k,
value: uint16Value(value),
}
}
func (k Key) Uint16s(value ...uint16) Field {
return Field{
key: k,
value: uint16sValue(value),
}
}
func (k Key) Uint16p(value *uint16) Field {
return Field{
key: k,
value: uint16pValue(value),
}
}
func (k Key) Uint32(value uint32) Field {
return Field{
key: k,
value: uint32Value(value),
}
}
func (k Key) Uint32s(value ...uint32) Field {
return Field{
key: k,
value: uint32sValue(value),
}
}
func (k Key) Uint32p(value *uint32) Field {
return Field{
key: k,
value: uint32pValue(value),
}
}
func (k Key) Uint64(value uint64) Field {
return Field{
key: k,
value: uint64Value(value),
}
}
func (k Key) Uint64s(value ...uint64) Field {
return Field{
key: k,
value: uint64sValue(value),
}
}
func (k Key) Uint64p(value *uint64) Field {
return Field{
key: k,
value: uint64pValue(value),
}
}
func (k Key) Float32(value float32) Field {
return Field{
key: k,
value: float32Value(value),
}
}
func (k Key) Float32s(value ...float32) Field {
return Field{
key: k,
value: float32sValue(value),
}
}
func (k Key) Float32p(value *float32) Field {
return Field{
key: k,
value: float32pValue(value),
}
}
func (k Key) Float64(value float64) Field {
return Field{
key: k,
value: float64Value(value),
}
}
func (k Key) Float64s(value ...float64) Field {
return Field{
key: k,
value: float64sValue(value),
}
}
func (k Key) Float64p(value *float64) Field {
return Field{
key: k,
value: float64pValue(value),
}
}
func (k Key) Complex64(value complex64) Field {
return Field{
key: k,
value: complex64Value(value),
}
}
func (k Key) Complex64s(value ...complex64) Field {
return Field{
key: k,
value: complex64sValue(value),
}
}
func (k Key) Complex64p(value *complex64) Field {
return Field{
key: k,
value: complex64pValue(value),
}
}
func (k Key) Complex128(value complex128) Field {
return Field{
key: k,
value: complex128Value(value),
}
}
func (k Key) Complex128s(value []complex128) Field {
return Field{
key: k,
value: complex128sValue(value),
}
}
func (k Key) Complex128p(value *complex128) Field {
return Field{
key: k,
value: complex128pValue(value),
}
}
func (k Key) Uintptr(value uintptr) Field {
return Field{
key: k,
value: uintptrValue(value),
}
}
func (k Key) Uintptrs(value ...uintptr) Field {
return Field{
key: k,
value: uintptrsValue(value),
}
}
func (k Key) Uintptrp(value *uintptr) Field {
return Field{
key: k,
value: uintptrpValue(value),
}
}
func (k Key) Bytes(value []byte) Field {
return Field{
key: k,
value: bytesValue(value),
}
}
func (k Key) Dureation(value time.Duration) Field {
return Field{
key: k,
value: durationValue(value),
}
}
func (k Key) Dureations(value []time.Duration) Field {
return Field{
key: k,
value: durationsValue(value),
}
}
func (k Key) Dureationp(value *time.Duration) Field {
return Field{
key: k,
value: durationpValue(value),
}
}
func (k Key) Time(value time.Time) Field {
return Field{
key: k,
value: timeValue(value),
}
}
func (k Key) Times(value ...time.Time) Field {
return Field{
key: k,
value: timesValue(value),
}
}
func (k Key) Timep(value *time.Time) Field {
return Field{
key: k,
value: timepValue(value),
}
}
func (k Key) FormatTime(format string, value time.Time) Field {
return Field{
key: k,
value: formatTimeValue(format, value),
}
}
func (k Key) FormatTimes(format string, value ...time.Time) Field {
return Field{
key: k,
value: formatTimesValue(format, value),
}
}
func (k Key) FormatTimep(format string, value *time.Time) Field {
return Field{
key: k,
value: formatTimepValue(format, value),
}
}
func (k Key) Error(value error) Field {
return Field{
key: k,
value: errorValue(value),
}
}
func (k Key) Errors(value ...error) Field {
return Field{
key: k,
value: errorsValue(value),
}
}

86
field/kind.go Normal file
View File

@@ -0,0 +1,86 @@
package field
import "fmt"
//go:generate stringer -type=Kind -linecomment -output=kind_string.go
type Kind int
const (
KindAny Kind = iota // any
KindArray // array
KindNil // nil
KindString // string
KindBool // bool
KindInt64 // int64
KindUint64 // uint64
KindFloat32 // float32
KindFloat64 // float64
KindComplex128 // complex128
KindBinary // bytes
KindDuration // duration
KindTime // time
KindError // error
KindGroup // group
KindClosure // closure
)
func (l Kind) MarshalJSON() ([]byte, error) {
return []byte("\"" + l.String() + "\""), nil
}
func (l *Kind) UnmarshalJSON(in []byte) error {
return l.UnmarshalText(in[1 : len(in)-1])
}
func (l Kind) MarshalText() ([]byte, error) {
return []byte(l.String()), nil
}
//nolint:gocyclo,cyclop
func (l *Kind) UnmarshalText(in []byte) error {
switch string(in) {
case KindAny.String():
*l = KindAny
case KindArray.String():
*l = KindArray
case KindNil.String():
*l = KindNil
case KindString.String():
*l = KindString
case KindBool.String():
*l = KindBool
case KindInt64.String():
*l = KindInt64
case KindUint64.String():
*l = KindUint64
case KindFloat32.String():
*l = KindFloat32
case KindFloat64.String():
*l = KindFloat64
case KindComplex128.String():
*l = KindComplex128
case KindBinary.String():
*l = KindBinary
case KindDuration.String():
*l = KindDuration
case KindTime.String():
*l = KindTime
case KindError.String():
*l = KindError
case KindGroup.String():
*l = KindGroup
case KindClosure.String():
*l = KindClosure
}
return fmt.Errorf("%w:filed(%v)", ErrUndefined, string(in))
}
func (l Kind) MarshalBinary() ([]byte, error) {
return []byte(l.String()), nil
}
func (l *Kind) UnmarshalBinary(in []byte) error {
return l.UnmarshalText(in)
}

38
field/kind_string.go Normal file
View File

@@ -0,0 +1,38 @@
// Code generated by "stringer -type=Kind -linecomment -output=kind_string.go"; DO NOT EDIT.
package field
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[KindAny-0]
_ = x[KindArray-1]
_ = x[KindNil-2]
_ = x[KindString-3]
_ = x[KindBool-4]
_ = x[KindInt64-5]
_ = x[KindUint64-6]
_ = x[KindFloat32-7]
_ = x[KindFloat64-8]
_ = x[KindComplex128-9]
_ = x[KindBinary-10]
_ = x[KindDuration-11]
_ = x[KindTime-12]
_ = x[KindError-13]
_ = x[KindGroup-14]
_ = x[KindClosure-15]
}
const _Kind_name = "anyarraynilstringboolint64uint64float32float64complex128bytesdurationtimeerrorgroupclosure"
var _Kind_index = [...]uint8{0, 3, 8, 11, 17, 21, 26, 32, 39, 46, 56, 61, 69, 73, 78, 83, 90}
func (i Kind) String() string {
if i < 0 || i >= Kind(len(_Kind_index)-1) {
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
}

11
field/log_valuer.go Normal file
View File

@@ -0,0 +1,11 @@
package field
type LogValuer interface {
LogValue() any
}
type ClosureFn func() any
func (v ClosureFn) LogValue() any {
return v()
}

112
field/safe_set.go Normal file
View File

@@ -0,0 +1,112 @@
package field
import "unicode/utf8"
// Copied from encoding/json/tables.go.
//
// safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further
// escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\").
//
//nolint:gochecknoglobals
var safeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': true,
'=': true,
'>': true,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}

View File

@@ -1,126 +0,0 @@
package field
type Type uint32
const (
TypeAny Type = 1 << iota // any
TypeArray // array
TypeNil // nil
TypeString // string
TypeBool // bool
TypeInt // int
TypeInt8 // int8
TypeInt16 // int16
TypeInt32 // int32
TypeInt64 // int64
TypeUint // uint
TypeUint8 // uint8
TypeUint16 // uint16
TypeUint32 // uint32
TypeUint64 // uint64
TypeFloat32 // float32
TypeFloat64 // float64
TypeComplex64 // complex64
TypeComplex128 // complex128
TypeUintptr // uintptr
TypeBinary // bytes
TypeDuration // duration
TypeTime // time
TypeError // error
)
func (t Type) IsAny() bool {
return t&TypeAny > 0
}
func (t Type) IsArray() bool {
return t&TypeArray > 0
}
func (t Type) IsNil() bool {
return t&TypeNil > 0
}
func (t Type) IsBool() bool {
return t&TypeBool > 0
}
func (t Type) IsString() bool {
return t&TypeString > 0
}
func (t Type) IsInt() bool {
return t&TypeInt > 0
}
func (t Type) IsInt8() bool {
return t&TypeInt8 > 0
}
func (t Type) IsInt16() bool {
return t&TypeInt16 > 0
}
func (t Type) IsInt32() bool {
return t&TypeInt32 > 0
}
func (t Type) IsInt64() bool {
return t&TypeInt64 > 0
}
func (t Type) IsUint() bool {
return t&TypeUint > 0
}
func (t Type) IsUint8() bool {
return t&TypeUint8 > 0
}
func (t Type) IsUint16() bool {
return t&TypeUint16 > 0
}
func (t Type) IsUint32() bool {
return t&TypeUint32 > 0
}
func (t Type) IsUint64() bool {
return t&TypeUint64 > 0
}
func (t Type) IsFloat32() bool {
return t&TypeFloat32 > 0
}
func (t Type) IsFloat64() bool {
return t&TypeFloat64 > 0
}
func (t Type) IsComplex64() bool {
return t&TypeComplex64 > 0
}
func (t Type) IsComplex128() bool {
return t&TypeComplex128 > 0
}
func (t Type) IsUintptr() bool {
return t&TypeUintptr > 0
}
func (t Type) IsBinary() bool {
return t&TypeBinary > 0
}
func (t Type) IsDuration() bool {
return t&TypeDuration > 0
}
func (t Type) IsTime() bool {
return t&TypeTime > 0
}
func (t Type) IsError() bool {
return t&TypeError > 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ package log
import (
"context"
"io"
"time"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
@@ -10,8 +11,8 @@ import (
//nolint:gochecknoglobals
var global = With(New(),
WithCaller("caller", 1, false),
WithLevel("level", level.Debug),
WithTime(KeyTime, time.RFC3339),
WithLevel(KeyLevel, level.Debug),
WithExit(level.Alert),
WithPanic(level.Emergency),
)
@@ -27,112 +28,112 @@ func Log() Logger {
}
// Emerg log by emergency level.
func Emerg(ctx context.Context, args ...interface{}) {
func Emerg(ctx context.Context, args ...any) {
global.Emerg(ctx, args...)
}
// Alert log by alert level.
func Alert(ctx context.Context, args ...interface{}) {
func Alert(ctx context.Context, args ...any) {
global.Alert(ctx, args...)
}
// Crit log by critical level.
func Crit(ctx context.Context, args ...interface{}) {
func Crit(ctx context.Context, args ...any) {
global.Crit(ctx, args...)
}
// Err log by error level.
func Err(ctx context.Context, args ...interface{}) {
func Err(ctx context.Context, args ...any) {
global.Err(ctx, args...)
}
// Warn logs by warning level.
func Warn(ctx context.Context, args ...interface{}) {
func Warn(ctx context.Context, args ...any) {
global.Warn(ctx, args...)
}
// Notice log by notice level.
func Notice(ctx context.Context, args ...interface{}) {
func Notice(ctx context.Context, args ...any) {
global.Notice(ctx, args...)
}
// Info log by info level.
func Info(ctx context.Context, args ...interface{}) {
func Info(ctx context.Context, args ...any) {
global.Info(ctx, args...)
}
// Debug log by debug level.
func Debug(ctx context.Context, args ...interface{}) {
func Debug(ctx context.Context, args ...any) {
global.Debug(ctx, args...)
}
// Print log by info level and arguments.
func Print(args ...interface{}) {
func Print(args ...any) {
global.Print(args...)
}
// Fatal log by alert level and arguments.
func Fatal(args ...interface{}) {
func Fatal(args ...any) {
global.Fatal(args...)
}
// Panic log by emergency level and arguments.
func Panic(args ...interface{}) {
func Panic(args ...any) {
global.Panic(args...)
}
// Println log by info level and arguments.
func Println(args ...interface{}) {
func Println(args ...any) {
global.Println(args...)
}
// Fatalln log by alert level and arguments.
func Fatalln(args ...interface{}) {
func Fatalln(args ...any) {
global.Fatalln(args...)
}
// Panicln log by emergency level and arguments.
func Panicln(args ...interface{}) {
func Panicln(args ...any) {
global.Panicln(args...)
}
// EmergKVs sugared log by emergency level and key-values.
func EmergKVs(ctx context.Context, msg string, args ...interface{}) {
func EmergKVs(ctx context.Context, msg string, args ...any) {
global.EmergKVs(ctx, msg, args...)
}
// AlertKVs sugared log by alert level and key-values.
func AlertKVs(ctx context.Context, msg string, args ...interface{}) {
func AlertKVs(ctx context.Context, msg string, args ...any) {
global.AlertKVs(ctx, msg, args...)
}
// CritKVs sugared log by critcal level and key-values.
func CritKVs(ctx context.Context, msg string, args ...interface{}) {
func CritKVs(ctx context.Context, msg string, args ...any) {
global.CritKVs(ctx, msg, args...)
}
// ErrKVs sugared log by error level and key-values.
func ErrKVs(ctx context.Context, msg string, args ...interface{}) {
func ErrKVs(ctx context.Context, msg string, args ...any) {
global.ErrKVs(ctx, msg, args...)
}
// WarnKVs sugared log by warning level and key-values.
func WarnKVs(ctx context.Context, msg string, args ...interface{}) {
func WarnKVs(ctx context.Context, msg string, args ...any) {
global.WarnKVs(ctx, msg, args...)
}
// NoticeKVs sugared log by notice level and key-values.
func NoticeKVs(ctx context.Context, msg string, args ...interface{}) {
func NoticeKVs(ctx context.Context, msg string, args ...any) {
global.NoticeKVs(ctx, msg, args...)
}
// InfoKVs sugared log by info level and key-values.
func InfoKVs(ctx context.Context, msg string, args ...interface{}) {
func InfoKVs(ctx context.Context, msg string, args ...any) {
global.InfoKVs(ctx, msg, args...)
}
// DebugKVs sugared log by debug level and key-values.
func DebugKVs(ctx context.Context, msg string, args ...interface{}) {
func DebugKVs(ctx context.Context, msg string, args ...any) {
global.DebugKVs(ctx, msg, args...)
}
@@ -177,57 +178,57 @@ func DebugKV(ctx context.Context, msg string, args ...field.Field) {
}
// Emergf log by emergency level by format and arguments.
func Emergf(ctx context.Context, format string, args ...interface{}) {
func Emergf(ctx context.Context, format string, args ...any) {
global.Emergf(ctx, format, args...)
}
// Alertf log by alert level by format and arguments.
func Alertf(ctx context.Context, format string, args ...interface{}) {
func Alertf(ctx context.Context, format string, args ...any) {
global.Alertf(ctx, format, args...)
}
// Critf log by critical level by format and arguments.
func Critf(ctx context.Context, format string, args ...interface{}) {
func Critf(ctx context.Context, format string, args ...any) {
global.Critf(ctx, format, args...)
}
// Errf log by error level by format and arguments.
func Errf(ctx context.Context, format string, args ...interface{}) {
func Errf(ctx context.Context, format string, args ...any) {
global.Errf(ctx, format, args...)
}
// Warnf log by warning level by format and arguments.
func Warnf(ctx context.Context, format string, args ...interface{}) {
func Warnf(ctx context.Context, format string, args ...any) {
global.Warnf(ctx, format, args...)
}
// Noticef log by notice level by format and arguments.
func Noticef(ctx context.Context, format string, args ...interface{}) {
func Noticef(ctx context.Context, format string, args ...any) {
global.Noticef(ctx, format, args...)
}
// Infof log by info level by format and arguments.
func Infof(ctx context.Context, format string, args ...interface{}) {
func Infof(ctx context.Context, format string, args ...any) {
global.Noticef(ctx, format, args...)
}
// Debugf log by debug level by format and arguments.
func Debugf(ctx context.Context, format string, args ...interface{}) {
func Debugf(ctx context.Context, format string, args ...any) {
global.Debugf(ctx, format, args...)
}
// Printf log by info level by format and arguments without context.
func Printf(format string, args ...interface{}) {
func Printf(format string, args ...any) {
global.Printf(format, args...)
}
// Fatalf log by alert level by format and arguments without context.
func Fatalf(format string, args ...interface{}) {
func Fatalf(format string, args ...any) {
global.Fatalf(format, args...)
}
// Panicf log by emergency level and arguments without context.
func Panicf(format string, args ...interface{}) {
func Panicf(format string, args ...any) {
global.Panicf(format, args...)
}

25
global_example_test.go Normal file
View File

@@ -0,0 +1,25 @@
package log_test
import (
"context"
"path/filepath"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/level"
)
func ExampleDebug() {
logger := log.New(log.WithStdout()).With(
log.WithSource(2, filepath.Base),
log.WithLevel(log.KeyLevel, level.Debug),
log.WithExit(level.Alert),
log.WithPanic(level.Emergency),
)
log.SetLogger(logger)
ctx := context.Background()
log.Debug(ctx, "debug message")
// Output:
// msg="debug message" source=global_example_test.go:22 level=debug
}

21
go.mod
View File

@@ -1,22 +1,3 @@
module gitoa.ru/go-4devs/log
go 1.17
require (
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
go.opentelemetry.io/otel v0.20.0
go.opentelemetry.io/otel/sdk v0.20.0
go.opentelemetry.io/otel/trace v0.20.0
go.uber.org/zap v1.21.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel/metric v0.20.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
go 1.22

81
go.sum
View File

@@ -1,81 +0,0 @@
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0 h1:JsxtGXd06J8jrnya7fdI/U/MR6yXA5DtbZy+qoHQlr8=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

10
handler/logrus/go.mod Normal file
View File

@@ -0,0 +1,10 @@
module gitoa.ru/go-4devs/log/handler/logrus
go 1.20
require (
github.com/sirupsen/logrus v1.9.3
gitoa.ru/go-4devs/log v0.5.1
)
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

17
handler/logrus/go.sum Normal file
View File

@@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gitoa.ru/go-4devs/log v0.5.1 h1:rrIyjpUaw8AjDCf7ZuH0HgCRf370O3TV29yrU1xizWM=
gitoa.ru/go-4devs/log v0.5.1/go.mod h1:tREtjEH2cTHl0p3uCVcH9g5tlqtsVNI/tDQVfq53Ty4=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -6,37 +6,42 @@ import (
"github.com/sirupsen/logrus"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
// Standard create new standart logrus handler.
// Deprecated: delete after 0.7.0
func Standard() log.Logger {
return New(logrus.StandardLogger())
}
// New create new logrus handler.
// Deprecated: delete after 0.7.0
func New(log *logrus.Logger) log.Logger {
return func(ctx context.Context, e *entry.Entry) (int, error) {
lrgFields := make(logrus.Fields, e.Fields().Len())
for _, field := range e.Fields() {
lrgFields[string(field.Key())] = field.AsInterface()
}
return func(ctx context.Context, data *entry.Entry) (int, error) {
lrgFields := make(logrus.Fields, data.Fields().Len())
data.Fields().Fields(func(f field.Field) bool {
lrgFields[f.Key] = f.Value.Any()
return true
})
entry := log.WithContext(ctx).WithFields(lrgFields)
switch e.Level() {
switch data.Level() {
case level.Emergency:
entry.Panic(e.Message())
entry.Panic(data.Message())
case level.Alert:
entry.Fatal(e.Message())
entry.Fatal(data.Message())
case level.Critical, level.Error:
entry.Error(e.Message())
entry.Error(data.Message())
case level.Warning:
entry.Warn(e.Message())
entry.Warn(data.Message())
case level.Notice, level.Info:
entry.Info(e.Message())
entry.Info(data.Message())
case level.Debug:
entry.Debug(e.Message())
entry.Debug(data.Message())
}
return 0, nil

View File

@@ -1,6 +1,7 @@
package log_test
package logrus_test
import (
"context"
"io"
"os"
@@ -10,6 +11,7 @@ import (
)
func ExampleNew_logrusHandler() {
ctx := context.Background()
lgrs := slogrus.New()
lgrs.SetOutput(os.Stdout)
lgrs.SetFormatter(&slogrus.TextFormatter{

17
handler/otel/go.mod Normal file
View File

@@ -0,0 +1,17 @@
module gitoa.ru/go-4devs/log/handler/otel
go 1.21.5
require (
gitoa.ru/go-4devs/log v0.5.1
go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
go.opentelemetry.io/otel/trace v1.21.0
)
require (
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)

27
handler/otel/go.sum Normal file
View File

@@ -0,0 +1,27 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gitoa.ru/go-4devs/log v0.5.1 h1:rrIyjpUaw8AjDCf7ZuH0HgCRf370O3TV29yrU1xizWM=
gitoa.ru/go-4devs/log v0.5.1/go.mod h1:tREtjEH2cTHl0p3uCVcH9g5tlqtsVNI/tDQVfq53Ty4=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -4,6 +4,7 @@ import (
"context"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -38,19 +39,21 @@ func levels(lvl level.Level) Level {
return 0
}
func addEvent(ctx context.Context, e *entry.Entry) {
func addEvent(ctx context.Context, data *entry.Entry) {
span := trace.SpanFromContext(ctx)
attrs := make([]attribute.KeyValue, 0, e.Fields().Len()+levelFields)
attrs := make([]attribute.KeyValue, 0, data.Fields().Len()+levelFields)
lvl := levels(e.Level())
lvl := levels(data.Level())
attrs = append(attrs,
attribute.String(fieldSeverityText, lvl.String()),
attribute.Int(fieldSeverityNumber, int(lvl)),
)
for _, field := range e.Fields() {
attrs = append(attrs, attribute.String(string(field.Key()), field.Value().String()))
}
data.Fields().Fields(func(f field.Field) bool {
attrs = append(attrs, attribute.String(f.Key, f.Value.String()))
span.AddEvent(e.Message(), trace.WithAttributes(attrs...))
return true
})
span.AddEvent(data.Message(), trace.WithAttributes(attrs...))
}

View File

@@ -7,6 +7,7 @@ import (
"gitoa.ru/go-4devs/log/entry"
)
// Deprecated: delete after 0.7.0
func New() log.Logger {
return func(ctx context.Context, e *entry.Entry) (int, error) {
addEvent(ctx, e)

View File

@@ -1,4 +1,4 @@
package log_test
package otel_test
import (
"context"
@@ -13,6 +13,7 @@ import (
)
func ExampleNew_withTrace() {
ctx := context.Background()
logger := log.New(log.WithStdout()).With(otel.Middleware())
sctx, span := startSpan(ctx)
@@ -44,9 +45,9 @@ func (e exporter) Shutdown(_ context.Context) error {
return nil
}
func (e exporter) ExportSpans(ctx context.Context, spanData []*sdktrace.SpanSnapshot) error {
func (e exporter) ExportSpans(_ context.Context, spanData []sdktrace.ReadOnlySpan) error {
for _, data := range spanData {
for _, events := range data.MessageEvents {
for _, events := range data.Events() {
fmt.Print("event: ", events.Name)
for _, attr := range events.Attributes {

View File

@@ -7,6 +7,7 @@ import (
"gitoa.ru/go-4devs/log/entry"
)
// Deprecated: delete after 0.7.0
func Middleware() log.Middleware {
return func(ctx context.Context, e *entry.Entry, handler log.Logger) (int, error) {
addEvent(ctx, e)

10
handler/zap/go.mod Normal file
View File

@@ -0,0 +1,10 @@
module gitoa.ru/go-4devs/log/handler/zap
go 1.21.5
require (
gitoa.ru/go-4devs/log v0.5.1
go.uber.org/zap v1.26.0
)
require go.uber.org/multierr v1.10.0 // indirect

16
handler/zap/go.sum Normal file
View File

@@ -0,0 +1,16 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
gitoa.ru/go-4devs/log v0.5.1 h1:rrIyjpUaw8AjDCf7ZuH0HgCRf370O3TV29yrU1xizWM=
gitoa.ru/go-4devs/log v0.5.1/go.mod h1:tREtjEH2cTHl0p3uCVcH9g5tlqtsVNI/tDQVfq53Ty4=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -5,18 +5,22 @@ import (
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
"go.uber.org/zap"
)
// Deprecated: delete after 0.7.0
func Nop() log.Logger {
return New(zap.NewNop())
}
// Deprecated: delete after 0.7.0
func Example(options ...zap.Option) log.Logger {
return New(zap.NewExample(options...))
}
// Deprecated: delete after 0.7.0
func Production(options ...zap.Option) log.Logger {
z, err := zap.NewProduction(options...)
if err != nil {
@@ -26,6 +30,7 @@ func Production(options ...zap.Option) log.Logger {
return New(z)
}
// Deprecated: delete after 0.7.0
func Development(options ...zap.Option) log.Logger {
z, err := zap.NewDevelopment(options...)
if err != nil {
@@ -36,26 +41,28 @@ func Development(options ...zap.Option) log.Logger {
}
// New create handler by zap logger.
func New(z *zap.Logger) log.Logger {
return func(ctx context.Context, e *entry.Entry) (int, error) {
zf := make([]zap.Field, e.Fields().Len())
for i, field := range e.Fields() {
zf[i] = zap.Any(string(field.Key()), field.AsInterface())
}
func New(logger *zap.Logger) log.Logger {
return func(ctx context.Context, data *entry.Entry) (int, error) {
zf := make([]zap.Field, 0, data.Fields().Len())
data.Fields().Fields(func(f field.Field) bool {
zf = append(zf, zap.Any(f.Key, f.Value.Any()))
switch e.Level() {
return true
})
switch data.Level() {
case level.Emergency:
z.Fatal(e.Message(), zf...)
logger.Fatal(data.Message(), zf...)
case level.Alert:
z.Panic(e.Message(), zf...)
logger.Panic(data.Message(), zf...)
case level.Critical, level.Error:
z.Error(e.Message(), zf...)
logger.Error(data.Message(), zf...)
case level.Warning:
z.Warn(e.Message(), zf...)
logger.Warn(data.Message(), zf...)
case level.Notice, level.Info:
z.Info(e.Message(), zf...)
logger.Info(data.Message(), zf...)
case level.Debug:
z.Debug(e.Message(), zf...)
logger.Debug(data.Message(), zf...)
}
return 0, nil

View File

@@ -1,6 +1,7 @@
package log_test
package zap_test
import (
"context"
"io"
"gitoa.ru/go-4devs/log/field"
@@ -9,6 +10,7 @@ import (
)
func ExampleNew_zapHandler() {
ctx := context.Background()
log := zap.New(uzap.NewExample())
log.Err(ctx, "log zap")
log.ErrKV(ctx, "log zap kv", field.Int("int", 42))

42
internal/buffer/buffer.go Normal file
View File

@@ -0,0 +1,42 @@
package buffer
import "sync"
const bufferSize = 1024
type Buffer []byte
// Having an initial size gives a dramatic speedup.
//
//nolint:gochecknoglobals
var bufPool = sync.Pool{
New: func() any {
b := make([]byte, 0, bufferSize)
return (*Buffer)(&b)
},
}
//nolint:forcetypeassert
func New() *Buffer {
return bufPool.Get().(*Buffer)
}
func (b *Buffer) Free() {
// To reduce peak allocation, return only smaller buffers to the pool.
const maxBufferSize = 16 << 10
if cap(*b) <= maxBufferSize {
*b = (*b)[:0]
bufPool.Put(b)
}
}
func (b *Buffer) WriteString(s string) (int, error) {
*b = append(*b, s...)
return len(s), nil
}
func (b *Buffer) String() string {
return string(*b)
}

View File

@@ -1,16 +1,20 @@
package level
import (
"encoding"
"encoding/json"
"fmt"
"strings"
)
//go:generate stringer -type=Level -linecomment
var (
_ json.Marshaler = Level(0)
_ json.Unmarshaler = (*Level)(nil)
_ json.Marshaler = Level(0)
_ json.Unmarshaler = (*Level)(nil)
_ encoding.TextMarshaler = Level(0)
_ encoding.TextUnmarshaler = (*Level)(nil)
_ encoding.BinaryMarshaler = Level(0)
_ encoding.BinaryUnmarshaler = (*Level)(nil)
)
// Level log.
@@ -18,9 +22,9 @@ type Level uint32
// available log levels.
const (
Emergency Level = iota // emergency
Emergency Level = iota // emerg
Alert // alert
Critical // critical
Critical // crit
Error // error
Warning // warning
Notice // notice
@@ -28,15 +32,6 @@ const (
Debug // debug
)
func (l Level) MarshalJSON() ([]byte, error) {
b, err := json.Marshal(l.String())
if err != nil {
return nil, fmt.Errorf("marshal err: %w", err)
}
return b, nil
}
func (l Level) Is(level Level) bool {
return level == l
}
@@ -45,13 +40,34 @@ func (l Level) Enabled(level Level) bool {
return l <= level
}
func (l *Level) UnmarshalJSON(in []byte) error {
var v string
if err := json.Unmarshal(in, &v); err != nil {
return fmt.Errorf("unmarshal err: %w", err)
}
func (l Level) MarshalJSON() ([]byte, error) {
return []byte("\"" + l.String() + "\""), nil
}
lvl := Parse(v)
func (l *Level) UnmarshalJSON(in []byte) error {
lvl := Parse(string(in[1 : len(in)-1]))
*l = lvl
return nil
}
func (l Level) MarshalText() ([]byte, error) {
return []byte(l.String()), nil
}
func (l *Level) UnmarshalText(in []byte) error {
lvl := Parse(string(in))
*l = lvl
return nil
}
func (l Level) MarshalBinary() ([]byte, error) {
return []byte(l.String()), nil
}
func (l *Level) UnmarshalBinary(in []byte) error {
lvl := Parse(string(in))
*l = lvl
return nil
@@ -65,11 +81,11 @@ func Parse(lvl string) Level {
return Info
case "notice", "Notice", "NOTICE":
return Notice
case "warning", "Warning", "WARNING":
case "warning", "Warning", "WARNING", "warm", "Warm", "WARN":
return Warning
case "error", "Error", "ERROR":
case "error", "Error", "ERROR", "err", "Err", "ERR":
return Error
case "critical", "Critical", "CRITICAL":
case "critical", "Critical", "CRITICAL", "crit", "Crit", "CRIT":
return Critical
case "alert", "Alert", "ALERT":
return Alert

View File

@@ -18,9 +18,9 @@ func _() {
_ = x[Debug-7]
}
const _Level_name = "emergencyalertcriticalerrorwarningnoticeinfodebug"
const _Level_name = "emergalertcriterrorwarningnoticeinfodebug"
var _Level_index = [...]uint8{0, 9, 14, 22, 27, 34, 40, 44, 49}
var _Level_index = [...]uint8{0, 5, 10, 14, 19, 26, 32, 36, 41}
func (i Level) String() string {
if i >= Level(len(_Level_index)-1) {

67
level/level_test.go Normal file
View File

@@ -0,0 +1,67 @@
package level_test
import (
"testing"
"gitoa.ru/go-4devs/log/level"
)
func TestMarshalJSON(t *testing.T) {
t.Parallel()
levels := map[level.Level]string{
level.Emergency: `"emerg"`,
level.Alert: `"alert"`,
level.Critical: `"crit"`,
level.Error: `"error"`,
level.Warning: `"warning"`,
level.Notice: `"notice"`,
level.Info: `"info"`,
level.Debug: `"debug"`,
}
for level, expect := range levels {
actual, err := level.MarshalJSON()
if err != nil {
t.Errorf("%s got err: %s", level, err)
continue
}
if string(actual) != expect {
t.Errorf("%s got: %s expect: %s", level, actual, expect)
}
}
}
func TestUnmarshalJSON(t *testing.T) {
t.Parallel()
levels := map[level.Level][]string{
level.Emergency: {`"emerg"`, `"Emerg"`},
level.Alert: {`"alert"`, `"ALERT"`},
level.Critical: {`"crit"`, `"critical"`},
level.Error: {`"error"`, `"ERR"`},
level.Warning: {`"warning"`, `"Warning"`},
level.Notice: {`"notice"`},
level.Info: {`"info"`},
level.Debug: {`"debug"`, `"DEBUG"`},
}
for expect, actuals := range levels {
for _, actual := range actuals {
var level level.Level
err := level.UnmarshalJSON([]byte(actual))
if err != nil {
t.Errorf("%s got err: %s", level, err)
continue
}
if !level.Is(expect) {
t.Errorf("%s got: %s expect: %s", actual, level, expect)
}
}
}
}

277
logger.go
View File

@@ -2,7 +2,6 @@ package log
import (
"context"
"errors"
"fmt"
"io"
"os"
@@ -14,10 +13,7 @@ import (
var _ io.Writer = (Logger)(nil)
var (
ErrIgnoredKey = errors.New("ignored key without a value")
ErrNonStringKeys = errors.New("ignored key-value pairs with non-string keys")
)
const badKey = "!BADKEY"
func writeOutput(_ int, err error) {
if err != nil {
@@ -28,283 +24,218 @@ func writeOutput(_ int, err error) {
// Logger logged message.
type Logger func(ctx context.Context, entry *entry.Entry) (int, error)
func (l Logger) log(ctx context.Context, level level.Level, args ...interface{}) {
writeOutput(l.write(ctx, level, fmt.Sprint(args...)))
}
func (l Logger) Write(in []byte) (int, error) {
return l.write(context.Background(), level.Info, string(in))
}
func (l Logger) write(ctx context.Context, level level.Level, msg string, fields ...field.Field) (int, error) {
e := entry.Get()
defer func() {
entry.Put(e)
}()
return l(ctx, e.SetLevel(level).SetMessage(msg).Add(fields...))
}
func (l Logger) logKVs(ctx context.Context, level level.Level, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level, msg, l.kv(ctx, args...)...))
}
func (l Logger) logKV(ctx context.Context, level level.Level, msg string, fields ...field.Field) {
writeOutput(l.write(ctx, level, msg, fields...))
}
func (l Logger) logf(ctx context.Context, level level.Level, format string, args ...interface{}) {
writeOutput(l.write(ctx, level, fmt.Sprintf(format, args...)))
}
func (l Logger) logln(ctx context.Context, level level.Level, args ...interface{}) {
writeOutput(l.write(ctx, level, fmt.Sprintln(args...)))
}
func (l Logger) kv(ctx context.Context, args ...interface{}) field.Fields {
e := entry.Get()
defer func() {
entry.Put(e)
}()
for i := 0; i < len(args); i++ {
if f, ok := args[i].(field.Field); ok {
e = e.Add(f)
continue
}
if i == len(args)-1 {
l.logKV(ctx, level.Critical, fmt.Sprint("Ignored key without a value.", args[i]), e.Fields()...)
break
}
i++
key, val := args[i-1], args[i]
if keyStr, ok := key.(string); ok {
e = e.AddAny(keyStr, val)
continue
}
l.logKV(ctx, level.Critical, fmt.Sprint("Ignored key-value pairs with non-string keys.", key, val), e.Fields()...)
}
return e.Fields()
}
// With adds middlewares to logger.
func (l Logger) With(mw ...Middleware) Logger {
return With(l, mw...)
}
// Emerg log by emergency level.
func (l Logger) Emerg(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Emergency, args...)
func (l Logger) Emerg(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Emergency, "", args...))
}
// Alert log by alert level.
func (l Logger) Alert(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Alert, args...)
func (l Logger) Alert(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Alert, "", args...))
}
// Crit log by critical level.
func (l Logger) Crit(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Critical, args...)
func (l Logger) Crit(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Critical, "", args...))
}
// Err log by error level.
func (l Logger) Err(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Error, args...)
func (l Logger) Err(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Error, "", args...))
}
// Warn log by warning level.
func (l Logger) Warn(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Warning, args...)
func (l Logger) Warn(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Warning, "", args...))
}
// Notice log by notice level.
func (l Logger) Notice(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Notice, args...)
func (l Logger) Notice(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Notice, "", args...))
}
// Info log by info level.
func (l Logger) Info(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Info, args...)
func (l Logger) Info(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Info, "", args...))
}
// Debug log by debug level.
func (l Logger) Debug(ctx context.Context, args ...interface{}) {
l.log(ctx, level.Debug, args...)
func (l Logger) Debug(ctx context.Context, args ...any) {
writeOutput(l.writef(ctx, level.Debug, "", args...))
}
// Print log by info level and arguments.
func (l Logger) Print(args ...interface{}) {
l.log(context.Background(), level.Info, args...)
func (l Logger) Print(args ...any) {
writeOutput(l.writef(context.Background(), level.Info, "", args...))
}
// Fatal log by alert level and arguments.
func (l Logger) Fatal(args ...interface{}) {
l.log(context.Background(), level.Alert, args...)
func (l Logger) Fatal(args ...any) {
writeOutput(l.writef(context.Background(), level.Alert, "", args...))
}
// Panic log by emergency level and arguments.
func (l Logger) Panic(args ...interface{}) {
l.log(context.Background(), level.Emergency, args...)
func (l Logger) Panic(args ...any) {
writeOutput(l.writef(context.Background(), level.Emergency, "", args...))
}
// Println log by info level and arguments.
func (l Logger) Println(args ...interface{}) {
l.logln(context.Background(), level.Info, args...)
func (l Logger) Println(args ...any) {
writeOutput(l.write(context.Background(), level.Info, fmt.Sprintln(args...)))
}
// Fatalln log by alert level and arguments.
func (l Logger) Fatalln(args ...interface{}) {
l.logln(context.Background(), level.Alert, args...)
func (l Logger) Fatalln(args ...any) {
writeOutput(l.write(context.Background(), level.Alert, fmt.Sprintln(args...)))
}
// Panicln log by emergency level and arguments.
func (l Logger) Panicln(args ...interface{}) {
l.logln(context.Background(), level.Emergency, args...)
func (l Logger) Panicln(args ...any) {
writeOutput(l.write(context.Background(), level.Emergency, fmt.Sprintln(args...)))
}
// EmergKVs sugared log by emergency level and key-values.
func (l Logger) EmergKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Emergency, msg, args...)
func (l Logger) EmergKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Emergency, msg, l.kv(ctx, args...)...))
}
// AlertKVs sugared log by alert level and key-values.
func (l Logger) AlertKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Alert, msg, args...)
func (l Logger) AlertKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Alert, msg, l.kv(ctx, args...)...))
}
// CritKVs sugared log by critcal level and key-values.
func (l Logger) CritKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Critical, msg, args...)
func (l Logger) CritKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Critical, msg, l.kv(ctx, args...)...))
}
// ErrKVs sugared log by error level and key-values.
func (l Logger) ErrKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Error, msg, args...)
func (l Logger) ErrKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Error, msg, l.kv(ctx, args...)...))
}
// WarnKVs sugared log by warning level and key-values.
func (l Logger) WarnKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Warning, msg, args...)
func (l Logger) WarnKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Warning, msg, l.kv(ctx, args...)...))
}
// NoticeKVs sugared log by notice level and key-values.
func (l Logger) NoticeKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Notice, msg, args...)
func (l Logger) NoticeKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Notice, msg, l.kv(ctx, args...)...))
}
// InfoKVs sugared log by info level and key-values.
func (l Logger) InfoKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Info, msg, args...)
func (l Logger) InfoKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Info, msg, l.kv(ctx, args...)...))
}
// DebugKVs sugared log by debug level and key-values.
func (l Logger) DebugKVs(ctx context.Context, msg string, args ...interface{}) {
l.logKVs(ctx, level.Debug, msg, args...)
func (l Logger) DebugKVs(ctx context.Context, msg string, args ...any) {
writeOutput(l.write(ctx, level.Debug, msg, l.kv(ctx, args...)...))
}
// EmergKV log by emergency level and key-values.
func (l Logger) EmergKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Emergency, msg, args...)
writeOutput(l.write(ctx, level.Emergency, msg, args...))
}
// AlertKV log by alert level and key-values.
func (l Logger) AlertKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Alert, msg, args...)
writeOutput(l.write(ctx, level.Alert, msg, args...))
}
// CritKV log by critcal level and key-values.
func (l Logger) CritKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Critical, msg, args...)
writeOutput(l.write(ctx, level.Critical, msg, args...))
}
// ErrKV log by error level and key-values.
func (l Logger) ErrKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Error, msg, args...)
writeOutput(l.write(ctx, level.Error, msg, args...))
}
// WarnKV log by warning level and key-values.
func (l Logger) WarnKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Warning, msg, args...)
writeOutput(l.write(ctx, level.Warning, msg, args...))
}
// NoticeKV log by notice level and key-values.
func (l Logger) NoticeKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Notice, msg, args...)
writeOutput(l.write(ctx, level.Notice, msg, args...))
}
// InfoKV log by info level and key-values.
func (l Logger) InfoKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Info, msg, args...)
writeOutput(l.write(ctx, level.Info, msg, args...))
}
// DebugKV log by debug level and key-values.
func (l Logger) DebugKV(ctx context.Context, msg string, args ...field.Field) {
l.logKV(ctx, level.Debug, msg, args...)
writeOutput(l.write(ctx, level.Debug, msg, args...))
}
// Emergf log by emergency level by format and arguments.
func (l Logger) Emergf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Emergency, format, args...)
func (l Logger) Emergf(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Emergency, format, args...))
}
// Alertf log by alert level by format and arguments.
func (l Logger) Alertf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Alert, format, args...)
func (l Logger) Alertf(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Alert, format, args...))
}
// Critf log by critical level by format and arguments.
func (l Logger) Critf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Critical, format, args...)
func (l Logger) Critf(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Critical, format, args...))
}
// Errf log by error level by format and arguments.
func (l Logger) Errf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Error, format, args...)
func (l Logger) Errf(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Error, format, args...))
}
// Warnf log by warning level by format and arguments.
func (l Logger) Warnf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Warning, format, args...)
func (l Logger) Warnf(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Warning, format, args...))
}
// Noticef log by notice level by format and arguments.
func (l Logger) Noticef(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Notice, format, args...)
func (l Logger) Noticef(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Notice, format, args...))
}
// Infof log by info level by format and arguments.
func (l Logger) Infof(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Info, format, args...)
func (l Logger) Infof(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Info, format, args...))
}
// Debugf log by debug level by format and arguments.
func (l Logger) Debugf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, level.Debug, format, args...)
func (l Logger) Debugf(ctx context.Context, format string, args ...any) {
writeOutput(l.writef(ctx, level.Debug, format, args...))
}
// Printf log by info level by format and arguments without context.
func (l Logger) Printf(format string, args ...interface{}) {
l.logf(context.Background(), level.Info, format, args...)
func (l Logger) Printf(format string, args ...any) {
writeOutput(l.writef(context.Background(), level.Info, format, args...))
}
// Fatalf log by alert level by format and arguments without context.
func (l Logger) Fatalf(format string, args ...interface{}) {
l.logf(context.Background(), level.Alert, format, args...)
func (l Logger) Fatalf(format string, args ...any) {
writeOutput(l.writef(context.Background(), level.Alert, format, args...))
}
// Panicf log by emergency level and arguments without context.
func (l Logger) Panicf(format string, args ...interface{}) {
l.logf(context.Background(), level.Emergency, format, args...)
func (l Logger) Panicf(format string, args ...any) {
writeOutput(l.writef(context.Background(), level.Emergency, format, args...))
}
func (l Logger) Writer(ctx context.Context, level level.Level, fields ...field.Field) io.Writer {
@@ -316,11 +247,67 @@ func (l Logger) Writer(ctx context.Context, level level.Level, fields ...field.F
}
}
func (l Logger) kv(_ context.Context, args ...any) field.Fields {
kvEntry := entry.Get()
defer func() {
entry.Put(kvEntry)
}()
for i := 0; i < len(args); i++ {
if f, ok := args[i].(field.Field); ok {
kvEntry = kvEntry.Add(f)
continue
}
if i == len(args)-1 {
kvEntry = kvEntry.AddAny(badKey, args[i])
break
}
key, val := args[i], args[i+1]
if keyStr, ok := key.(string); ok {
kvEntry = kvEntry.AddAny(keyStr, val)
i++
continue
}
kvEntry = kvEntry.AddAny(badKey, args[i])
}
return kvEntry.Fields()
}
func (l Logger) write(ctx context.Context, level level.Level, msg string, fields ...field.Field) (int, error) {
data := entry.Get()
defer func() {
entry.Put(data)
}()
return l(ctx, data.SetLevel(level).SetMessage(msg).Add(fields...))
}
func (l Logger) writef(ctx context.Context, level level.Level, format string, args ...any) (int, error) {
data := entry.Get()
defer func() {
entry.Put(data)
}()
return l(ctx, data.SetLevel(level).SetMessagef(format, args...))
}
//nolint:containedctx
type writer struct {
Logger
ctx context.Context
level level.Level
fields []field.Field
Logger
}
func (w writer) WithLevel(level level.Level) writer {

View File

@@ -1,20 +1,23 @@
package log_test
import (
"path/filepath"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/level"
)
func ExampleNew_withCaller() {
logger := log.With(
log.New(log.WithStdout()),
log.WithLevel("level", level.Debug),
log.WithCaller("caller", 2, false),
logger := log.New(log.WithStdout()).With(
log.WithLevel(log.KeyLevel, level.Debug),
log.WithSource(3, filepath.Base),
)
logger.Err(ctx, "same error message")
logger.InfoKVs(ctx, "same info message", "api-version", 0.1)
_, _ = logger.Write([]byte("same write message"))
// Output:
// msg="same error message" level=error caller=logger_example_caller_test.go:14
// msg="same info message" api-version=0.1 level=info caller=logger_example_caller_test.go:15
// msg="same error message" level=error source=logger_example_caller_test.go:15
// msg="same info message" api-version=0.1 level=info source=logger_example_caller_test.go:16
// msg="same write message" level=info source=logger_example_caller_test.go:17
}

View File

@@ -2,9 +2,12 @@ package log_test
import (
"context"
"errors"
"fmt"
"math"
"os"
"sync/atomic"
"time"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
@@ -12,12 +15,11 @@ import (
"gitoa.ru/go-4devs/log/level"
)
//nolint:gochecknoglobals
var ctx = context.Background()
func setStdout() {
// set stout for example by default stderror
log.SetLogger(log.New(log.WithStdout()).With(log.WithLevel("level", level.Debug)))
log.SetLogger(log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Debug)))
}
func ExampleNew() {
@@ -35,7 +37,7 @@ func ExampleInfo() {
func ExampleErrKV() {
setStdout()
log.ErrKVs(ctx, "same message", "key", "addition value")
// Output: msg="same message" key=addition value level=error
// Output: msg="same message" key="addition value" level=error
}
func ExampleNew_errf() {
@@ -45,42 +47,188 @@ func ExampleNew_errf() {
}
func ExampleNew_debugKV() {
logger := log.New(log.WithStdout()).With(log.WithLevel("level", level.Debug))
logger := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Debug))
logger.DebugKVs(ctx, "same message", "error", os.ErrNotExist)
// Output: msg="same message" error=file does not exist level=debug
// Output: msg="same message" error="file does not exist" level=debug
}
func ExampleNew_level() {
logger := log.New(log.WithStdout()).With(log.WithLevel("level", level.Error))
logger.Info(ctx, "same message")
// Output:
logger := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Error))
logger.Err(ctx, "same error message")
// Output: msg="same error message" level=error
}
func ExampleNew_level_info() {
logger := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Error))
logger.Info(ctx, "same message")
// Output:
}
type Obj struct {
Name string
IsEnable bool
}
var (
obj = Obj{
Name: "test obj",
}
str = "test str"
boolsVal = true
intVal = int(math.MaxInt)
int8Val = int8(math.MaxInt8)
int16Val = int16(math.MaxInt16)
int32Val = int32(math.MaxInt32)
int64Val = int64(math.MaxInt64)
uintVal = uint(math.MaxUint)
uint8Val = uint8(math.MaxUint8)
uint16Val = uint16(math.MaxInt16)
uint32Val = uint32(math.MaxInt32)
uint64Val = uint64(math.MaxInt64)
float32Val = float32(math.MaxFloat32)
float64Val = float64(math.MaxFloat64)
minute = time.Minute
timeVal = time.Unix(0, math.MaxInt32).In(time.UTC)
)
func ExampleNew_anyField() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "any info message",
field.Any("obj", Obj{Name: "obj name"}),
field.Any("obj", &obj),
field.Any("int", intVal),
field.Any("uint", uintVal),
field.Any("float", float64Val),
field.Any("time", timeVal),
field.Any("duration", time.Hour),
field.Any("error", errors.New("error")),
)
// Output:
// {"msg":"any info message","obj":{"Name":"obj name","IsEnable":false},"obj":{"Name":"test obj","IsEnable":false},"int":9223372036854775807,"uint":18446744073709551615,"float":1.7976931348623157e+308,"time":"1970-01-01T00:00:02Z","duration":"1h0m0s","error":"error"}
}
func ExampleNew_arrayField() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "array info message",
field.Strings("strings", "string", str),
field.Bools("bools", true, false),
field.Ints("ints", 42, 24),
field.Int8s("int8s", 42, 24),
field.Int16s("int16s", 42, 24),
field.Int32s("int32s", 42, 24),
field.Int64s("int64s", 42, 24),
field.Uint8s("uint8s", uint8Val, 0),
field.Uint16s("uint16s", 42, 24),
field.Uint32s("uint32s", 42, 24),
field.Uint64s("uint64s", 42, 24),
field.Float32s("float32s", 42, 24),
field.Float64s("float64s", 42, 24),
field.Complex64s("complex64s", 42, 24),
field.Complex128s("complex128s", 42, 24),
field.Durations("durations", time.Minute, time.Second),
field.Times("times", time.Unix(0, 42).In(time.UTC), time.Unix(0, 24).In(time.UTC)),
field.Errors("errors", errors.New("error"), errors.New("error2")),
)
// Output:
// {"msg":"array info message","strings":["string","test str"],"bools":[true,false],"ints":[42,24],"int8s":[42,24],"int16s":[42,24],"int32s":[42,24],"int64s":[42,24],"uint8s":[255,0],"uint16s":[42,24],"uint32s":[42,24],"uint64s":[42,24],"float32s":[42,24],"float64s":[42,24],"complex64s":["(42+0i)","(24+0i)"],"complex128s":["(42+0i)","(24+0i)"],"durations":["1m0s","1s"],"times":["1970-01-01T00:00:00Z","1970-01-01T00:00:00Z"],"errors":["error","error2"]}
}
func ExampleNew_pointerField() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "pointer info message",
field.Stringp("stringp", &str),
field.Stringp("stringp", nil),
field.Boolp("boolp", &boolsVal),
field.Boolp("boolp", nil),
field.Intp("intp", &intVal),
field.Intp("intp", nil),
field.Int8p("int8p", &int8Val),
field.Int8p("int8p", nil),
field.Int16p("int16p", &int16Val),
field.Int16p("int16p", nil),
field.Int32p("int32p", &int32Val),
field.Int32p("int32p", nil),
field.Int64p("int64p", &int64Val),
field.Int64p("int64p", nil),
field.Uintp("uintp", &uintVal),
field.Uintp("uintp", nil),
field.Uint8p("uint8p", &uint8Val),
field.Uint8p("uint8p", nil),
field.Uint16p("uint16p", &uint16Val),
field.Uint16p("uint16p", nil),
field.Uint32p("uint32p", &uint32Val),
field.Uint32p("uint32p", nil),
field.Uint64p("uint64p", &uint64Val),
field.Uint64p("uint64p", nil),
field.Float32p("float32p", &float32Val),
field.Float32p("float32p", nil),
field.Float64p("float64p", &float64Val),
field.Float64p("float64p", nil),
field.Durationp("durationp", &minute),
field.Durationp("durationp", nil),
field.Timep("timep", &timeVal),
field.Timep("timep", nil),
)
// Output:
// {"msg":"pointer info message","stringp":"test str","stringp":null,"boolp":true,"boolp":null,"intp":9223372036854775807,"intp":null,"int8p":127,"int8p":null,"int16p":32767,"int16p":null,"int32p":2147483647,"int32p":null,"int64p":9223372036854775807,"int64p":null,"uintp":18446744073709551615,"uintp":null,"uint8p":255,"uint8p":null,"uint16p":32767,"uint16p":null,"uint32p":2147483647,"uint32p":null,"uint64p":9223372036854775807,"uint64p":null,"float32p":3.4028235e+38,"float32p":null,"float64p":1.7976931348623157e+308,"float64p":null,"durationp":"1m0s","durationp":null,"timep":"1970-01-01T00:00:02Z","timep":null}
}
func ExampleNew_fields() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "info message",
field.String("string", str),
field.Bool("bool", true),
field.Int("int", 42),
field.Int8("int8", 42),
field.Int16("int16", 42),
field.Int32("int32", 42),
field.Int64("int64", 42),
field.Uint8("uint8", uint8Val),
field.Uint16("uint16", 42),
field.Uint32("uint32", 42),
field.Uint64("uint64", 42),
field.Float32("float32", 42),
field.Float64("float64", 42),
field.Complex64("complex16", 42),
field.Complex128("complex128", 42),
field.Duration("duration", time.Minute),
field.Time("time", timeVal),
field.FormatTime("format_time", time.UnixDate, timeVal),
field.Error("error", errors.New("error")),
)
// Output:
// {"msg":"info message","string":"test str","bool":true,"int":42,"int8":42,"int16":42,"int32":42,"int64":42,"uint8":255,"uint16":42,"uint32":42,"uint64":42,"float32":42,"float64":42,"complex16":"(42+0i)","complex128":"(42+0i)","duration":"1m0s","time":"1970-01-01T00:00:02Z","format_time":"Thu Jan 1 00:00:02 UTC 1970","error":"error"}
}
func ExampleNew_jsonFormat() {
logger := log.New(log.WithStdout(), log.WithJSONFormat()).
With(
log.WithLevel("level", level.Debug),
log.WithLevel(log.KeyLevel, level.Debug),
log.GoVersion("go-version"),
)
logger.Err(ctx, "same error message")
// Output: {"go-version":"go1.17.6","level":"error","msg":"same error message"}
logger.WarnKVs(ctx, "same warn message", "obj", Obj{Name: "obj name"})
// Output:
// {"msg":"same error message","level":"error","go-version":"go1.25.5"}
// {"msg":"same warn message","obj":{"Name":"obj name","IsEnable":false},"level":"warning","go-version":"go1.25.5"}
}
func ExampleNew_textEncoding() {
logger := log.With(
log.New(log.WithStdout()),
log.WithLevel("level", level.Debug),
log.GoVersion("go-version"),
)
logger := log.New(log.WithStdout()).
With(
log.WithLevel(log.KeyLevel, level.Debug),
log.GoVersion("go-version"),
)
logger.Err(ctx, "same error message")
logger.InfoKVs(ctx, "same info message", "api-version", 0.1)
logger.InfoKVs(ctx, "same info message", "api-version", 0.1, "obj", Obj{Name: "text value", IsEnable: true})
// Output:
// msg="same error message" level=error go-version=go1.17.6
// msg="same info message" api-version=0.1 level=info go-version=go1.17.6
// msg="same error message" level=error go-version=go1.25.5
// msg="same info message" api-version=0.1 obj={Name:text value IsEnable:true} level=info go-version=go1.25.5
}
type ctxKey string
@@ -90,28 +238,33 @@ func (c ctxKey) String() string {
}
func levelInfo(ctx context.Context, entry *entry.Entry, handler log.Logger) (int, error) {
return handler(ctx, entry.Add(field.String("level", entry.Level().String())))
return handler(ctx, entry.Add(field.String(log.KeyLevel, entry.Level().String())))
}
func ExampleWith() {
var requestID ctxKey = "requestID"
vctx := context.WithValue(ctx, requestID, "6a5fa048-7181-11ea-bc55-0242ac130003")
logger := log.With(
log.New(log.WithStdout()),
levelInfo, log.WithContextValue(requestID), log.KeyValue("api", "0.1.0"), log.GoVersion("go"),
logger := log.New(log.WithStdout()).With(
levelInfo,
log.WithContextValue(requestID),
log.KeyValue("api", "0.1.0"),
log.GoVersion("go"),
)
logger.Info(vctx, "same message")
// Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.17.6
// Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.25.5
}
func ExampleLogger_Print() {
logger := log.With(
log.New(log.WithStdout()),
levelInfo, log.KeyValue("client", "http"), log.KeyValue("api", "0.1.0"), log.GoVersion("go"),
logger := log.New(log.WithStdout()).With(
levelInfo,
log.KeyValue("client", "http"),
log.KeyValue("api", "0.1.0"),
log.GoVersion("go"),
)
logger.Print("same message")
// Output: msg="same message" level=info client=http api=0.1.0 go=go1.17.6
// Output: msg="same message" level=info client=http api=0.1.0 go=go1.25.5
}
func ExamplePrint() {
@@ -120,22 +273,45 @@ func ExamplePrint() {
// Output: msg="same message" level=info
}
func ExampleWithClosure() {
func Example_fieldClosureFn() {
cnt := int32(0)
closure := func() string {
closure := field.ClosureFn(func() any {
d := fmt.Sprintf("additional error data: %d", cnt)
atomic.AddInt32(&cnt, 1)
return d
}
})
log := log.With(log.New(log.WithStdout()), log.WithLevel("level", level.Info), log.WithClosure)
log := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Info))
log.DebugKVs(ctx, "debug message", "data", closure)
log.ErrKVs(ctx, "error message", "err", closure)
log.WarnKVs(ctx, "warn message", "warn", closure)
// Output:
// msg="error message" err=additional error data: 0 level=error
// msg="warn message" warn=additional error data: 1 level=warning
// msg="error message" err="additional error data: 0" level=error
// msg="warn message" warn="additional error data: 1" level=warning
}
func Example_withGroup() {
log := log.New(log.WithStdout()).With(
log.WithLevel(log.KeyLevel, level.Info),
)
log.ErrKVs(ctx, "error message",
field.Groups("grous_field",
field.Error("err", os.ErrDeadlineExceeded),
field.Bool("bool", false),
),
)
log.WarnKV(ctx, "error message", field.ValuerFn("valuer_field", func() any {
return field.Fields{
field.Int("int_value", math.MaxInt),
field.Uint8("uint8_value", math.MaxUint8),
}
}))
// Output:
// msg="error message" grous_field.err="i/o timeout" grous_field.bool=false level=error
// msg="error message" valuer_field.int_value=9223372036854775807 valuer_field.uint8_value=255 level=warning
}

View File

@@ -13,7 +13,6 @@ import (
"gitoa.ru/go-4devs/log/level"
)
//nolint:gochecknoglobals
var requestID ctxKey = "requestID"
func TestFields(t *testing.T) {
@@ -28,14 +27,14 @@ func TestFields(t *testing.T) {
ctx := context.Background()
buf := &bytes.Buffer{}
log := log.New(log.WithWriter(buf)).
With(log.WithLevel("level", level.Info), log.WithClosure)
success := "msg=\"message\" err=file already exists version=0.1.0 obj={id:uid} closure=some closure data level=info\n"
With(log.WithLevel(log.KeyLevel, level.Info))
success := "msg=message err=\"file already exists\" version=0.1.0 obj={id:uid} closure=\"some closure data\" level=info\n"
log.InfoKVs(ctx, "message",
"err", os.ErrExist,
"version", "0.1.0",
"obj", rObj{id: "uid"},
"closure", func() string {
"closure", func() any {
atomic.AddInt32(&cnt, 1)
return "some closure data"
@@ -43,11 +42,11 @@ func TestFields(t *testing.T) {
)
log.DebugKVs(ctx, "debug message",
"closure", func() string {
"closure", field.ClosureFn(func() any {
atomic.AddInt32(&cnt, 1)
return "some debug data"
},
}),
)
if success != buf.String() {
@@ -64,9 +63,9 @@ func TestWriter(t *testing.T) {
ctx := context.Background()
success := "msg=\"info message\" err=file already exists requestID=6a5fa048-7181-11ea-bc55-0242ac1311113 level=info\n"
success := "msg=\"info message\" err=\"file already exists\" requestID=6a5fa048-7181-11ea-bc55-0242ac1311113 level=info\n"
buf := &bytes.Buffer{}
logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel("level", level.Info))
logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel(log.KeyLevel, level.Info))
_, _ = logger.Writer(
context.WithValue(ctx, requestID, "6a5fa048-7181-11ea-bc55-0242ac1311113"),
@@ -92,7 +91,7 @@ func TestLogger(t *testing.T) {
ctx := context.Background()
buf := &bytes.Buffer{}
logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel("level", level.Info))
logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel(log.KeyLevel, level.Info))
_, err := logger(ctx, nil)
if err != nil {

View File

@@ -12,8 +12,6 @@ import (
"gitoa.ru/go-4devs/log/level"
)
var _ Middleware = WithClosure
// Middleware handle.
type Middleware func(ctx context.Context, e *entry.Entry, handler Logger) (int, error)
@@ -30,7 +28,7 @@ func With(logger Logger, mw ...Middleware) Logger {
lastI := len(mw) - 1
return func(ctx context.Context, e *entry.Entry) (int, error) {
return func(ctx context.Context, data *entry.Entry) (int, error) {
var (
chainHandler func(context.Context, *entry.Entry) (int, error)
curI int
@@ -40,6 +38,7 @@ func With(logger Logger, mw ...Middleware) Logger {
if curI == lastI {
return logger(currentCtx, currentEntry)
}
curI++
n, err := mw[curI](currentCtx, currentEntry, chainHandler)
curI--
@@ -47,7 +46,7 @@ func With(logger Logger, mw ...Middleware) Logger {
return n, err
}
return mw[0](ctx, e, chainHandler)
return mw[0](ctx, data, chainHandler)
}
}
@@ -62,20 +61,8 @@ func WithLevel(key string, lvl level.Level) Middleware {
}
}
func WithClosure(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
for i, field := range e.Fields() {
if field.Type().IsAny() {
if f, ok := field.AsInterface().(func() string); ok {
e.Fields().Set(i, field.Key().String(f()))
}
}
}
return handler(ctx, e)
}
// KeyValue add field by const key value.
func KeyValue(key string, value interface{}) Middleware {
func KeyValue(key string, value any) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
return handler(ctx, e.AddAny(key, value))
}
@@ -88,7 +75,7 @@ func GoVersion(key string) Middleware {
}
}
// WithContext add field by context key.
// WithContextValue add field by context key.
func WithContextValue(keys ...fmt.Stringer) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
for _, key := range keys {
@@ -99,7 +86,15 @@ func WithContextValue(keys ...fmt.Stringer) Middleware {
}
}
func WithName(name string) Middleware {
return func(ctx context.Context, data *entry.Entry, handler Logger) (int, error) {
return handler(ctx, data.Replace(KeyName, field.StringValue(name)))
}
}
// WithCaller adds called file.
//
// Deprecated: use WithSource.
func WithCaller(key string, depth int, full bool) Middleware {
const offset = 2
@@ -111,7 +106,7 @@ func WithCaller(key string, depth int, full bool) Middleware {
// WithTime adds time.
func WithTime(key, format string) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
return handler(ctx, e.Add(field.Time(key, time.Now())))
return handler(ctx, e.Add(field.FormatTime(key, format, time.Now())))
}
}

94
source.go Normal file
View File

@@ -0,0 +1,94 @@
package log
import (
"context"
"fmt"
"path/filepath"
"runtime"
"strings"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
)
func WithSource(items int, trimPath func(string) string) Middleware {
const (
skip = 4
funcPrefix = "gitoa.ru/go-4devs/log.Logger"
skipHelper = "gitoa.ru/go-4devs/log."
)
items += skip
return func(ctx context.Context, data *entry.Entry, handler Logger) (int, error) {
pc := make([]uintptr, items)
n := runtime.Callers(skip, pc)
if n == 0 {
return handler(ctx, data.Add(errSourceField(skip, items)))
}
pc = pc[:n] // pass only valid pcs to runtime.CallersFrames
frames := runtime.CallersFrames(pc)
prew := false
for {
frame, more := frames.Next()
has := strings.HasPrefix(frame.Function, funcPrefix)
if !has && prew {
if strings.HasPrefix(frame.Function, skipHelper) {
continue
}
return handler(ctx, data.AddAny(KeySource, Source{
Func: filepath.Base(frame.Function),
Line: frame.Line,
File: trimPath(frame.File),
}))
}
prew = has
if !more {
break
}
}
return handler(ctx, data.Add(errSourceField(skip, items)))
}
}
func TrimPath(file string) string {
idx := strings.LastIndexByte(file, '/')
if idx == -1 {
return filepath.Base(file)
}
// Find the penultimate separator.
idx = strings.LastIndexByte(file[:idx], '/')
if idx == -1 {
return filepath.Base(file)
}
return file[idx+1:]
}
// Source describes the location of a line of source code.
type Source struct {
Func string `json:"func"`
File string `json:"file"`
Line int `json:"line"`
}
func (l Source) MarshalText() ([]byte, error) {
return fmt.Appendf(nil, "%s:%d", l.File, l.Line), nil
}
func (l Source) MarshalJSON() ([]byte, error) {
return fmt.Appendf([]byte{}, `{"file":"%s","line":%d,"func":"%s"}`, l.File, l.Line, l.Func), nil
}
func errSourceField(skip, mframe int) field.Field {
return field.String(KeySource, fmt.Sprintf("source not found by frames[%d:%d]", skip, mframe))
}

26
source_example_test.go Normal file
View File

@@ -0,0 +1,26 @@
package log_test
import (
"context"
"path/filepath"
"gitoa.ru/go-4devs/log"
)
func ExampleWithSource() {
ctx := context.Background()
logger := log.New(log.WithStdout()).With(log.WithSource(1, filepath.Base))
logger.Debug(ctx, "debug message")
// Output:
// msg="debug message" source=source_example_test.go:14
}
func ExampleWithSource_json() {
ctx := context.Background()
logger := log.New(log.WithStdout(), log.WithJSONFormat()).With(log.WithSource(2, filepath.Base))
logger.Debug(ctx, "debug message")
// Output:
// {"msg":"debug message","source":{"file":"source_example_test.go","line":23,"func":"log_test.ExampleWithSource_json"}}
}

37
writer_example_test.go Normal file
View File

@@ -0,0 +1,37 @@
package log_test
import (
"context"
"math"
"path/filepath"
"time"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
func exampleWithTime(key, format string) log.Middleware {
return func(ctx context.Context, e *entry.Entry, handler log.Logger) (int, error) {
return handler(ctx, e.Add(field.FormatTime(key, format, time.Unix(math.MaxInt32, 0).In(time.UTC))))
}
}
func ExampleFormatWithBracket() {
ctx := context.Background()
logger := log.New(log.WithFormat(log.FormatWithBracket(field.NewEncoderText())), log.WithStdout()).With(
log.WithSource(10, filepath.Base),
// log.WithTime(log.KeyTime, time.RFC3339),
exampleWithTime(log.KeyTime, time.RFC3339),
log.WithLevel(log.KeyLevel, level.Info),
)
logger.InfoKV(ctx, "imfo message", field.Int64("num", 42))
serviceLogger := logger.With(log.WithName("service_name"))
serviceLogger.Err(ctx, "error message")
// Output:
// 2038-01-19T03:14:07Z [info] writer_example_test.go:30 "imfo message" num=42
// 2038-01-19T03:14:07Z [error][service_name] writer_example_test.go:33 "error message"
}

View File

@@ -1,119 +1,190 @@
package log
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"sync"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/internal/buffer"
)
// Keys for "built-in" attributes.
const (
// KeyTime is the key used by the built-in handlers for the time
// when the log method is called. The associated Value is a [time.Time].
KeyTime = "time"
// KeyLevel is the key used by the built-in handlers for the level
// of the log call. The associated value is a [Level].
KeyLevel = "level"
// KeyMessage is the key used by the built-in handlers for the
// message of the log call. The associated value is a string.
KeyMessage = "msg"
// KeySource is the key used by the built-in handlers for the source file
// and line of the log call. The associated value is a string.
KeySource = "source"
// KeyName logger name.
KeyName = "name"
)
func WithWriter(w io.Writer) func(*option) {
return func(o *option) {
o.out = w
}
}
func WithStdout() func(*option) {
return func(o *option) {
o.out = os.Stdout
}
}
// WithStringFormat sets format as simple string.
func WithStringFormat() func(*option) {
return WithFormat(FormatString(field.NewEncoderText()))
}
// WithJSONFormat sets json output format.
func WithJSONFormat() func(*option) {
return WithFormat(FormatJSON(field.NewEncoderJSON()))
}
// WithFormat sets custom output format.
func WithFormat(format func(io.Writer, *entry.Entry) (int, error)) func(*option) {
return func(o *option) {
o.format = format
}
}
type option struct {
format func(io.Writer, *entry.Entry) (int, error)
out io.Writer
}
// New creates standart logger.
func New(opts ...Option) Logger {
l := log{e: stringFormat(), w: os.Stderr}
func New(opts ...func(*option)) Logger {
log := option{
format: FormatString(field.NewEncoderText()),
out: os.Stderr,
}
for _, opt := range opts {
opt(&l)
opt(&log)
}
return func(_ context.Context, entry *entry.Entry) (int, error) {
b, err := l.e(entry)
if err != nil {
return 0, fmt.Errorf("enode err: %w", err)
}
return log.format(log.out, entry)
}
}
n, err := l.w.Write(b)
type Encoder interface {
AppendValue(dst []byte, val field.Value) []byte
AppendField(dst []byte, val field.Field) []byte
}
func FormatWithBracket(enc Encoder) func(io.Writer, *entry.Entry) (int, error) {
appendValue := func(buf *buffer.Buffer, data field.Fields, key, prefix, suffix string) *buffer.Buffer {
data.Fields(
func(f field.Field) bool {
if f.IsKey(key) {
_, _ = buf.WriteString(prefix)
*buf = enc.AppendValue(*buf, f.Value)
_, _ = buf.WriteString(suffix)
return false
}
return true
})
return buf
}
return func(w io.Writer, data *entry.Entry) (int, error) {
buf := buffer.New()
defer func() {
buf.Free()
}()
fields := data.Fields()
buf = appendValue(buf, fields, KeyTime, "", " ")
_, _ = buf.WriteString("[")
*buf = enc.AppendValue(*buf, field.StringValue(data.Level().String()))
_, _ = buf.WriteString("]")
buf = appendValue(buf, fields, KeyName, "[", "]")
buf = appendValue(buf, fields, KeySource, " ", " ")
*buf = enc.AppendValue(*buf, field.StringValue(data.Message()))
fields.Fields(func(f field.Field) bool {
if !f.IsKey(KeyTime, KeySource, KeyName, KeyLevel) {
*buf = enc.AppendField(*buf, f)
}
return true
})
_, _ = buf.WriteString("\n")
n, err := w.Write(*buf)
if err != nil {
return 0, fmt.Errorf("failed write: %w", err)
return 0, fmt.Errorf("format text:%w", err)
}
return n, nil
}
}
// Option configure log.
type Option func(*log)
// Encode sets formats and encode output message.
type Encode func(*entry.Entry) ([]byte, error)
type log struct {
w io.Writer
e Encode
}
// WithWriter sets writer logger.
func WithWriter(writer io.Writer) Option {
return func(l *log) {
l.w = writer
}
}
// WithStdout sets logged to os.Stdout.
func WithStdout() Option {
return WithWriter(os.Stdout)
}
// WithEncode sets format log.
func WithEncode(e Encode) Option {
return func(l *log) {
l.e = e
}
}
// WithStringFormat sets format as simple string.
func WithStringFormat() Option {
return WithEncode(stringFormat())
}
// WithJSONFormat sets json output format.
func WithJSONFormat() Option {
return WithEncode(jsonFormat)
}
//nolint: forcetypeassert
func stringFormat() func(entry *entry.Entry) ([]byte, error) {
pool := sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
return func(entry *entry.Entry) ([]byte, error) {
b := pool.Get().(*bytes.Buffer)
b.Reset()
func FormatString(enc Encoder) func(io.Writer, *entry.Entry) (int, error) {
return func(w io.Writer, entry *entry.Entry) (int, error) {
buf := buffer.New()
defer func() {
pool.Put(b)
buf.Free()
}()
b.WriteString("msg=\"")
b.WriteString(strings.TrimSpace(entry.Message()))
b.WriteString("\"")
*buf = enc.AppendField(*buf, field.String(KeyMessage, entry.Message()))
for _, field := range entry.Fields() {
b.WriteString(" ")
b.WriteString(string(field.Key()))
b.WriteString("=")
b.WriteString(field.Value().String())
*buf = enc.AppendField(*buf, field)
}
b.WriteString("\n")
_, _ = buf.WriteString("\n")
return b.Bytes(), nil
n, err := w.Write(*buf)
if err != nil {
return 0, fmt.Errorf("format text:%w", err)
}
return n, nil
}
}
func jsonFormat(entry *entry.Entry) ([]byte, error) {
res, err := json.Marshal(entry.AddString("msg", entry.Message()).Fields().AsMap())
if err != nil {
return nil, fmt.Errorf("marshal err: %w", err)
}
func FormatJSON(enc Encoder) func(w io.Writer, entry *entry.Entry) (int, error) {
return func(w io.Writer, entry *entry.Entry) (int, error) {
buf := buffer.New()
return append(res, []byte("\n")...), nil
defer func() {
buf.Free()
}()
_, _ = buf.WriteString("{")
*buf = enc.AppendField(*buf, field.String(KeyMessage, entry.Message()))
for _, field := range entry.Fields() {
*buf = enc.AppendField(*buf, field)
}
_, _ = buf.WriteString("}")
_, _ = buf.WriteString("\n")
n, err := w.Write(*buf)
if err != nil {
return 0, fmt.Errorf("format json:%w", err)
}
return n, nil
}
}