1
0
mirror of https://github.com/khorevaa/logos.git synced 2025-03-11 14:59:51 +02:00

first commit

This commit is contained in:
khorevaa 2021-02-01 17:50:15 +03:00
commit c714e3833d
50 changed files with 5283 additions and 0 deletions

25
.github/workflows/benchmark.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: benchmark
on:
push:
branches:
- master
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Benchmark
run: |
curl -sL https://github.com/${GITHUB_REPOSITORY}/raw/master/README.md | \
awk '{if($0 ~ "// go test -v"){a=1;b=1};if($0 ~ "```" && b=1){b=0};if (a&&b) {print}}' | \
tee log_test.go
go get github.com/${GITHUB_REPOSITORY}
go get github.com/phuslu/log
go get github.com/rs/zerolog
go get go.uber.org/zap
head -1 log_test.go | cut -b3- | sed -E 's#\r##' | bash -xe

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
.idea

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 v8platform
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

173
README.md Normal file
View File

@ -0,0 +1,173 @@
# Logos: like log4j, but for golang.
[![go.dev][pkg-img]][pkg] [![goreport][report-img]][report] [![build][build-img]][build] [![coverage][cov-img]][cov] ![stability-stable][stability-img]
## Features
This project is a wrapper around the excellent logging framework zap.
* Dependency
- `go.uber.org/zap` for logging
- `github.com/elastic/go-ucfg` for config logging system
* Simple and Clean Interface
* One log manager for all logs
* Hot config update from file or env
* Appenders
- `Console`, *write to console*
- `File`, *any log file*
- `gelfupd`, *greylog logger*
- `RollingFile`, *rolling file writing & compress*
* Encoders
- `Console`, *colorful & formatting text for console*
- `Gelf`, *gelf for greylog*
- `Json`, *standard json encoder*
* Useful utility function
- `Setlevel(LogName string, level int)`, *hot update logger level*
- `UpdateLogger(LogName string, logger *zap.Logger*)`, *hot update core logger*
- `RedirectStdLog()`, *redirect standard log package*
* High Performance
- [Significantly faster][high-performance] than all other json loggers.
## Interfaces
### Logger
```go
// DefaultLogger is the global logger.
```
### Json Writer
To log a machine-friendly, use `json`. [![playground][play-pretty-img]][play-pretty]
```go
```
### Pretty Console Writer
To log a human-friendly, colorized output, use `Console`. [![playground][play-pretty-img]][play-pretty]
```go
```
![Pretty logging][pretty-img]
> Note: pretty logging also works on windows console
### High Performance
A quick and simple benchmark with zap/zerolog, which runs on [github actions][benchmark]:
```go
// go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem log_test.go
package main
import (
"io/ioutil"
"testing"
"github.com/khorevaa/logos"
"github.com/phuslu/log"
"github.com/rs/zerolog"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var fakeMessage = "Test logging, but use a somewhat realistic message length. "
func BenchmarkLogos(b *testing.B) {
const newConfig = `
appenders:
console:
- name: CONSOLE
target: discard
encoder:
console:
loggers:
root:
level: info
appender_refs:
- CONSOLE
`
err := logos.InitWithConfigContent(newConfig)
if err != nil {
panic(err)
}
logger := logos.New("benchmark")
for i := 0; i < b.N; i++ {
logger.Info(fakeMessage, zap.String("foo", "bar"), zap.Int("int", 42))
}
}
func BenchmarkZap(b *testing.B) {
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(ioutil.Discard),
zapcore.InfoLevel,
))
for i := 0; i < b.N; i++ {
logger.Info(fakeMessage, zap.String("foo", "bar"), zap.Int("int", 42))
}
}
func BenchmarkZeroLog(b *testing.B) {
logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger()
for i := 0; i < b.N; i++ {
logger.Info().Str("foo", "bar").Int("int", 42).Msg(fakeMessage)
}
}
func BenchmarkPhusLog(b *testing.B) {
logger := log.Logger{
TimeFormat: "", // uses rfc3339 by default
Writer: log.IOWriter{ioutil.Discard},
}
for i := 0; i < b.N; i++ {
logger.Info().Str("foo", "bar").Int("int", 42).Msg(fakeMessage)
}
}
```
A Performance result as below, for daily benchmark results see [github actions][benchmark]
```
```
[pkg-img]: http://img.shields.io/badge/godoc-reference-5272B4.svg
[pkg]: https://godoc.org/github.com/khorevaa/logos
[report-img]: https://goreportcard.com/badge/github.com/khorevaa/logos
[report]: https://goreportcard.com/report/github.com/khorevaa/logos
[build-img]: https://github.com/khorevaa/logos/workflows/build/badge.svg
[build]: https://github.com/khorevaa/logos/actions
[cov-img]: http://gocover.io/_badge/github.com/khorevaa/logos
[cov]: https://gocover.io/github.com/khorevaa/logos
[stability-img]: https://img.shields.io/badge/stability-stable-green.svg
[high-performance]: https://github.com/khorevaa/logos#high-performance
[play-simple-img]: https://img.shields.io/badge/playground-NGV25aBKmYH-29BEB0?style=flat&logo=go
[play-simple]: https://play.golang.org/p/NGV25aBKmYH
[play-customize-img]: https://img.shields.io/badge/playground-emTsJJKUGXZ-29BEB0?style=flat&logo=go
[play-customize]: https://play.golang.org/p/emTsJJKUGXZ
[play-file-img]: https://img.shields.io/badge/playground-nS--ILxFyhHM-29BEB0?style=flat&logo=go
[play-file]: https://play.golang.org/p/nS-ILxFyhHM
[play-pretty-img]: https://img.shields.io/badge/playground-SCcXG33esvI-29BEB0?style=flat&logo=go
[play-pretty]: https://play.golang.org/p/SCcXG33esvI
[pretty-img]: https://user-images.githubusercontent.com/195836/101993218-cda82380-3cf3-11eb-9aa2-b8b1c832a72e.png
[play-formatting-img]: https://img.shields.io/badge/playground-UmJmLxYXwRO-29BEB0?style=flat&logo=go
[play-formatting]: https://play.golang.org/p/UmJmLxYXwRO
[play-context-img]: https://img.shields.io/badge/playground-oAVAo302faf-29BEB0?style=flat&logo=go
[play-context]: https://play.golang.org/p/oAVAo302faf
[play-marshal-img]: https://img.shields.io/badge/playground-NxMoqaiVxHM-29BEB0?style=flat&logo=go
[play-marshal]: https://play.golang.org/p/NxMoqaiVxHM
[play-interceptor]: https://play.golang.org/p/upmVP5cO62Y
[play-interceptor-img]: https://img.shields.io/badge/playground-upmVP5cO62Y-29BEB0?style=flat&logo=go
[benchmark]: https://github.com/khorevaa/logos/actions?query=workflow%3Abenchmark
[zerolog]: https://github.com/rs/zerolog
[glog]: https://github.com/golang/glog
[quicktemplate]: https://github.com/valyala/quicktemplate
[gjson]: https://github.com/tidwall/gjson
[zap]: https://github.com/uber-go/zap
[lumberjack]: https://github.com/natefinch/lumberjack

91
appender/appender.go Normal file
View File

@ -0,0 +1,91 @@
package appender
import (
"fmt"
"github.com/khorevaa/logos/appender/console"
"github.com/khorevaa/logos/appender/file"
"github.com/khorevaa/logos/appender/gelfudp"
"github.com/khorevaa/logos/appender/rollingfile"
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap/zapcore"
)
var (
writers = map[string]WriterFactory{}
encoders = map[string]EncoderFactory{}
)
type WriterFactory func(config *common.Config) (zapcore.WriteSyncer, error)
type EncoderFactory func(*common.Config) (zapcore.Encoder, error)
type Appender struct {
Writer zapcore.WriteSyncer
Encoder zapcore.Encoder
}
func init() {
RegisterWriterType("console", console.New)
RegisterWriterType("file", file.New)
RegisterWriterType("rolling_file", rollingfile.New)
RegisterWriterType("gelf_udp", gelfudp.New)
}
func CreateAppender(writerType string, config *common.Config) (*Appender, error) {
w, err := NewWriter(writerType, config)
if err != nil {
return nil, err
}
encoderConfig, err := config.Child("encoder", -1)
if err != nil {
return nil, err
}
ec := EncoderConfig{}
if err := encoderConfig.Unpack(&ec); err != nil {
return nil, err
}
e, err := CreateEncoder(ec)
if err != nil {
return nil, err
}
return &Appender{w, e}, nil
}
func RegisterWriterType(name string, f WriterFactory) {
if writers[name] != nil {
panic(fmt.Errorf("writer type '%v' exists already", name))
}
writers[name] = f
}
func NewWriter(name string, config *common.Config) (zapcore.WriteSyncer, error) {
factory := writers[name]
if factory == nil {
return nil, fmt.Errorf("writer type %v undefined", name)
}
return factory(config)
}
type EncoderConfig struct {
Namespace common.ConfigNamespace `logos-config:",inline"`
}
func RegisterEncoderType(name string, gen EncoderFactory) {
if _, exists := encoders[name]; exists {
panic(fmt.Sprintf("encoder %q already registered", name))
}
encoders[name] = gen
}
func CreateEncoder(cfg EncoderConfig) (zapcore.Encoder, error) {
// default to json encoder
encoder := "json"
if name := cfg.Namespace.Name(); name != "" {
encoder = name
}
factory := encoders[encoder]
if factory == nil {
return nil, fmt.Errorf("'%v' encoder is not available", encoder)
}
return factory(cfg.Namespace.Config())
}

View File

@ -0,0 +1,67 @@
package console
import (
"fmt"
"io/ioutil"
"os"
"github.com/khorevaa/logos/internal/common"
"github.com/mattn/go-colorable"
"go.uber.org/zap/zapcore"
)
type Console struct {
zapcore.WriteSyncer
colorable bool
}
type Config struct {
Target `logos-config:"target" logos-validate:"required,logos.oneof=stderr stdout discard"`
NoColor bool `logos-config:"no_color"`
}
type Target = string
const (
Discard Target = "discard"
Stdout Target = "stdout"
Stderr Target = "stderr"
)
var (
defaultConfig = Config{
Target: Stdout,
}
)
func DefaultConfig() Config {
return defaultConfig
}
func New(v *common.Config) (zapcore.WriteSyncer, error) {
cfg := DefaultConfig()
if err := v.Unpack(&cfg); err != nil {
return nil, err
}
switch cfg.Target {
case Stdout:
return NewConsole(cfg, os.Stdout), nil
case Stderr:
return NewConsole(cfg, os.Stderr), nil
case Discard:
return &Console{zapcore.AddSync(ioutil.Discard), false}, nil
default:
return nil, fmt.Errorf("unknown target %q", cfg.Target)
}
}
func NewConsole(config Config, file *os.File) zapcore.WriteSyncer {
if config.NoColor {
return &Console{zapcore.AddSync(colorable.NewNonColorable(file)), false}
}
return &Console{zapcore.AddSync(colorable.NewColorable(file)), true}
}

35
appender/file/file.go Normal file
View File

@ -0,0 +1,35 @@
package file
import (
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap/zapcore"
"os"
)
type File struct {
*os.File
}
type Config struct {
FileName string `logos-config:"file_name" logos-validate:"required"`
}
var (
defaultConfig = Config{}
)
func DefaultConfig() Config {
return defaultConfig
}
func New(v *common.Config) (zapcore.WriteSyncer, error) {
cfg := DefaultConfig()
if err := v.Unpack(&cfg); err != nil {
return nil, err
}
f, err := os.OpenFile(cfg.FileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return nil, err
}
return &File{f}, nil
}

View File

@ -0,0 +1,12 @@
package file
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDefaultConfig(t *testing.T) {
c := DefaultConfig()
assert.Empty(t, c.FileName)
}

193
appender/gelfudp/gelfudp.go Normal file
View File

@ -0,0 +1,193 @@
package gelfudp
import (
"bytes"
"compress/gzip"
"compress/zlib"
"encoding/binary"
"errors"
"fmt"
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap/zapcore"
"io"
"io/ioutil"
"net"
)
type Config struct {
Host string `logos-config:"host"`
Port int `logos-config:"port"`
CompressionType string `logos-config:"compression_type" logos-validate:"logos.oneof=none gzip zlib"`
CompressionLevel int `logos-config:"compression_level"`
}
var defaultConfig = Config{
Host: "127.0.0.1",
Port: 12201,
CompressionType: "gzip",
CompressionLevel: gzip.DefaultCompression,
}
const (
MaxDatagramSize = 1420
HeadSize = 12
MaxChunkSize = MaxDatagramSize - HeadSize
MaxChunks = 128
MaxMessageSize = MaxChunkSize * MaxChunks
CompressionNone = 0
CompressionGzip = 1
CompressionZlib = 2
)
var Magic = []byte{0x1e, 0x0f}
var ErrTooLargeMessageSize = errors.New("too large message size")
type UDPSender struct {
raddr *net.UDPAddr
conn *net.UDPConn
id IdGenerator
}
func NewUDPSender(address string) (*UDPSender, error) {
ip, err := GuessIP()
if err != nil {
return nil, err
}
id := NewDefaultIdGenerator(ip)
raddr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp", nil)
if err != nil {
return nil, err
}
return &UDPSender{
raddr: raddr,
conn: conn,
id: id,
}, nil
}
func (s *UDPSender) Send(message []byte) error {
if len(message) > MaxMessageSize {
return ErrTooLargeMessageSize
}
if len(message) <= MaxDatagramSize {
_, err := s.conn.WriteToUDP(message, s.raddr)
return err
}
chunks := len(message) / MaxChunkSize
if chunks*MaxChunkSize < len(message) {
chunks = chunks + 1
}
messageID := s.id.NextId()
chunk := make([]byte, MaxDatagramSize)
for i := 0; i < chunks; i++ {
copy(chunk[0:2], Magic)
binary.BigEndian.PutUint64(chunk[2:10], messageID)
chunk[10] = byte(i)
chunk[11] = byte(chunks)
begin, end := i*MaxChunkSize, (i+1)*MaxChunkSize
if end > len(message) {
end = len(message)
}
copy(chunk[12:12+end-begin], message[begin:end])
_, err := s.conn.WriteToUDP(chunk[0:12+end-begin], s.raddr)
if err != nil {
return err
}
}
return nil
}
func NewCompressor(compressionType string, compressionLevel int) (*Compressor, error) {
switch compressionType {
case "none":
return &Compressor{CompressionNone, compressionLevel}, nil
case "gzip":
if _, err := gzip.NewWriterLevel(ioutil.Discard, compressionLevel); err != nil {
return nil, err
}
return &Compressor{CompressionGzip, compressionLevel}, nil
case "zlib":
if _, err := zlib.NewWriterLevel(ioutil.Discard, compressionLevel); err != nil {
return nil, err
}
return &Compressor{CompressionZlib, compressionLevel}, nil
default:
return nil, fmt.Errorf("no compression type %q", compressionType)
}
}
type Compressor struct {
compressionType int
compressionLevel int
}
func (c *Compressor) Compress(buf []byte) (int, []byte, error) {
var (
cw io.WriteCloser
cBuf bytes.Buffer
err error
)
switch c.compressionType {
case CompressionNone:
return len(buf), buf, nil
case CompressionGzip:
cw, err = gzip.NewWriterLevel(&cBuf, c.compressionLevel)
case CompressionZlib:
cw, err = zlib.NewWriterLevel(&cBuf, c.compressionLevel)
}
if err != nil {
return 0, nil, err
}
n, err := cw.Write(buf)
if err != nil {
return 0, nil, err
}
if err := cw.Close(); err != nil {
return 0, nil, err
}
return n, cBuf.Bytes(), nil
}
type Writer struct {
sender *UDPSender
compressor *Compressor
}
func (w *Writer) Write(p []byte) (n int, err error) {
n, b, err := w.compressor.Compress(p)
if err != nil {
return 0, err
}
if err := w.sender.Send(b); err != nil {
return 0, err
}
return n, nil
}
func New(rawConfig *common.Config) (zapcore.WriteSyncer, error) {
config := defaultConfig
if err := rawConfig.Unpack(&config); err != nil {
return nil, err
}
c, err := NewCompressor(config.CompressionType, config.CompressionLevel)
if err != nil {
return nil, err
}
s, err := NewUDPSender(fmt.Sprintf("%s:%d", config.Host, config.Port))
if err != nil {
return nil, err
}
return zapcore.AddSync(&Writer{s, c}), nil
}

51
appender/gelfudp/id.go Normal file
View File

@ -0,0 +1,51 @@
package gelfudp
import (
"errors"
"net"
"os"
"strconv"
"strings"
"time"
)
type IdGenerator interface {
NextId() uint64
}
type DefaultIdGenerator struct {
ip uint64
}
func NewDefaultIdGenerator(ip uint64) *DefaultIdGenerator {
return &DefaultIdGenerator{ip}
}
func (g *DefaultIdGenerator) NextId() uint64 {
timestamp := uint64(time.Now().Nanosecond())
timestamp = timestamp << 32
timestamp = timestamp >> 32
return (g.ip << 32) | timestamp
}
func GuessIP() (uint64, error) {
hostname, err := os.Hostname()
if err != nil {
return 0, err
}
ips, err := net.LookupIP(hostname)
if err != nil {
return 0, err
}
for _, ip := range ips {
if ip.To4() != nil {
parts := strings.Split(ip.String(), ".")
a, _ := strconv.ParseUint(parts[0], 10, 64)
b, _ := strconv.ParseUint(parts[1], 10, 64)
c, _ := strconv.ParseUint(parts[2], 10, 64)
d, _ := strconv.ParseUint(parts[3], 10, 64)
return (a << 24) | (b << 16) | (c << 8) | d, nil
}
}
return 0, errors.New("cannot resolve ip by hostname")
}

View File

@ -0,0 +1,64 @@
package rollingfile
import (
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
type RollingFile struct {
zapcore.WriteSyncer
}
type Config struct {
// FileName is the file to write logs to. Backup log files will be retained
// in the same directory. It uses <processname>-lumberjack.log in
// os.TempDir() if empty.
FileName string `logos-config:"file_name" logos-validate:"required"`
// MaxSize is the maximum size in megabytes of the log file before it gets
// rotated. It defaults to 100 megabytes.
MaxSize int `logos-config:"max_size" logos-validate:"min=1"`
// MaxAge is the maximum number of days to retain old log files based on the
// timestamp encoded in their filename. Note that a day is defined as 24
// hours and may not exactly correspond to calendar days due to daylight
// savings, leap seconds, etc. The default is not to remove old log files
// based on age.
MaxAge int `logos-config:"max_age"`
// MaxBackups is the maximum number of old log files to retain. The default
// is to retain all old log files (though MaxAge may still cause them to get
// deleted.)
MaxBackups int `logos-config:"max_backups"`
// LocalTime determines if the time used for formatting the timestamps in
// backup files is the computer's local time. The default is to use UTC
// time.
LocalTime bool `logos-config:"local_time"`
// Compress determines if the rotated log files should be compressed
// using gzip. The default is not to perform compression.
Compress bool `logos-config:"compress"`
}
func New(v *common.Config) (zapcore.WriteSyncer, error) {
cfg := Config{
MaxSize: 500,
}
if err := v.Unpack(&cfg); err != nil {
return nil, err
}
if cfg.MaxAge == 0 {
cfg.MaxAge = 7
}
w := &lumberjack.Logger{
Filename: cfg.FileName,
MaxSize: cfg.MaxSize,
MaxAge: cfg.MaxAge,
MaxBackups: cfg.MaxBackups,
LocalTime: cfg.LocalTime,
Compress: cfg.Compress,
}
return &RollingFile{zapcore.AddSync(w)}, nil
}

View File

@ -0,0 +1,32 @@
package rollingfile
import (
"github.com/khorevaa/logos/internal/common"
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewRollingFile(t *testing.T) {
tests := []struct {
name string
config string
hasErr bool
}{
{"case1", `
file_name: /tmp/app.log
max_size: -1
encoder:
json:`, true},
{"case2", `
file_name: /tmp/app.log
encoder:
json:`, false},
}
for _, c := range tests {
cfg, err := common.NewConfigFrom(c.config)
assert.Nil(t, err, c.name)
_, err = New(cfg)
assert.Equal(t, c.hasErr, err != nil, c.name)
}
}

37
benchmarks/apex_test.go Normal file
View File

@ -0,0 +1,37 @@
package benchmarks
import (
"io/ioutil"
"github.com/apex/log"
"github.com/apex/log/handlers/json"
)
func newDisabledApexLog() *log.Logger {
return &log.Logger{
Handler: json.New(ioutil.Discard),
Level: log.ErrorLevel,
}
}
func newApexLog() *log.Logger {
return &log.Logger{
Handler: json.New(ioutil.Discard),
Level: log.DebugLevel,
}
}
func fakeApexFields() log.Fields {
return log.Fields{
"int": _tenInts[0],
"ints": _tenInts,
"string": _tenStrings[0],
"strings": _tenStrings,
"time": _tenTimes[0],
"times": _tenTimes,
"user1": _oneUser,
"user2": _oneUser,
"users": _tenUsers,
"error": errExample,
}
}

12
benchmarks/go.mod Normal file
View File

@ -0,0 +1,12 @@
module benchmarks
go 1.16
require (
github.com/apex/log v1.9.0
github.com/phuslu/log v1.0.60
github.com/rs/zerolog v1.20.0
github.com/sirupsen/logrus v1.7.0
go.uber.org/multierr v1.6.0
go.uber.org/zap v1.16.0
)

136
benchmarks/go.sum Normal file
View File

@ -0,0 +1,136 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
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/elastic/go-ucfg v0.8.3 h1:leywnFjzr2QneZZWhE6uWd+QN/UpP0sdJRHYyuFvkeo=
github.com/elastic/go-ucfg v0.8.3/go.mod h1:iaiY0NBIYeasNgycLyTvhJftQlQEUO2hpF+FX0JKxzo=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/phuslu/log v1.0.60 h1:9BsTv2UGYS003TMejH0/ycWdQGPrOqwrUSmnZgAgqhU=
github.com/phuslu/log v1.0.60/go.mod h1:kzJN3LRifrepxThMjufQwS7S35yFAB+jAV1qgA7eBW4=
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/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc=
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/khorevaa/logos v0.0.0-20210128201039-04350835b235 h1:bnt2KYugM3aTSE6VH4kRYFdFE0z18ch8kchQ4vtP0yU=
github.com/khorevaa/logos v0.0.0-20210128201039-04350835b235/go.mod h1:nhDpfkExY+70/vYt47VjnDulF6Tt5V17uNy3+Lsu7a8=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
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/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/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-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

159
benchmarks/logos_test.go Normal file
View File

@ -0,0 +1,159 @@
package benchmarks
import (
"errors"
"fmt"
"github.com/khorevaa/logos"
"time"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
errExample = errors.New("fail")
_messages = fakeMessages(1000)
_tenInts = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
_tenStrings = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
_tenTimes = []time.Time{
time.Unix(0, 0),
time.Unix(1, 0),
time.Unix(2, 0),
time.Unix(3, 0),
time.Unix(4, 0),
time.Unix(5, 0),
time.Unix(6, 0),
time.Unix(7, 0),
time.Unix(8, 0),
time.Unix(9, 0),
}
_oneUser = &user{
Name: "Jane Doe",
Email: "jane@test.com",
CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC),
}
_tenUsers = users{
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
}
)
func init() {
const newConfig = `
appenders:
console:
- name: CONSOLE
target: discard
encoder:
console:
loggers:
root:
level: info
appender_refs:
- CONSOLE
`
err := logos.InitWithConfigContent(newConfig)
if err != nil {
panic(err)
}
}
func fakeMessages(n int) []string {
messages := make([]string, n)
for i := range messages {
messages[i] = fmt.Sprintf("Test logging, but use a somewhat realistic message length. (#%v)", i)
}
return messages
}
func getMessage(iter int) string {
return _messages[iter%1000]
}
type users []*user
func (uu users) MarshalLogArray(arr zapcore.ArrayEncoder) error {
var err error
for i := range uu {
err = multierr.Append(err, arr.AppendObject(uu[i]))
}
return err
}
type user struct {
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
func (u *user) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("name", u.Name)
enc.AddString("email", u.Email)
enc.AddInt64("createdAt", u.CreatedAt.UnixNano())
return nil
}
func newZapLogger(lvl zapcore.Level) logos.Logger {
log := logos.New("test")
log.SetLLevel(lvl)
return log
}
func fakeFields() []interface{} {
return []interface{}{
zap.Int("int", _tenInts[0]),
zap.Ints("ints", _tenInts),
zap.String("string", _tenStrings[0]),
zap.Strings("strings", _tenStrings),
zap.Time("time", _tenTimes[0]),
zap.Times("times", _tenTimes),
zap.Object("user1", _oneUser),
zap.Object("user2", _oneUser),
zap.Array("users", _tenUsers),
zap.Error(errExample),
}
}
func fakeSugarFields() []interface{} {
return []interface{}{
"int", _tenInts[0],
"ints", _tenInts,
"string", _tenStrings[0],
"strings", _tenStrings,
"time", _tenTimes[0],
"times", _tenTimes,
"user1", _oneUser,
"user2", _oneUser,
"users", _tenUsers,
"error", errExample,
}
}
func fakeFmtArgs() []interface{} {
// 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{}{
_tenInts[0],
_tenInts,
_tenStrings[0],
_tenStrings,
_tenTimes[0],
_tenTimes,
_oneUser,
_oneUser,
_tenUsers,
errExample,
}
}

37
benchmarks/logus_test.go Normal file
View File

@ -0,0 +1,37 @@
package benchmarks
import (
"io/ioutil"
"github.com/sirupsen/logrus"
)
func newDisabledLogrus() *logrus.Logger {
logger := newLogrus()
logger.Level = logrus.ErrorLevel
return logger
}
func newLogrus() *logrus.Logger {
return &logrus.Logger{
Out: ioutil.Discard,
Formatter: new(logrus.JSONFormatter),
Hooks: make(logrus.LevelHooks),
Level: logrus.DebugLevel,
}
}
func fakeLogrusFields() logrus.Fields {
return logrus.Fields{
"int": _tenInts[0],
"ints": _tenInts,
"string": _tenStrings[0],
"strings": _tenStrings,
"time": _tenTimes[0],
"times": _tenTimes,
"user1": _oneUser,
"user2": _oneUser,
"users": _tenUsers,
"error": errExample,
}
}

View File

@ -0,0 +1,67 @@
package main
import (
"github.com/khorevaa/logos"
"github.com/phuslu/log"
"github.com/rs/zerolog"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io/ioutil"
"testing"
)
var fakeMessage = "Test logging, but use a somewhat realistic message length. "
func BenchmarkLogos(b *testing.B) {
const newConfig = `
appenders:
console:
- name: CONSOLE
target: discard
encoder:
console:
loggers:
root:
level: info
appender_refs:
- CONSOLE
`
err := logos.InitWithConfigContent(newConfig)
if err != nil {
//panic(err)
}
logger := logos.New("benchmark")
for i := 0; i < b.N; i++ {
logger.Info(fakeMessage, zap.String("foo", "bar"), zap.Int("int", 42))
}
}
func BenchmarkZap(b *testing.B) {
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(ioutil.Discard),
zapcore.InfoLevel,
))
for i := 0; i < b.N; i++ {
logger.Info(fakeMessage, zap.String("foo", "bar"), zap.Int("int", 42))
}
}
func BenchmarkZeroLog(b *testing.B) {
logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger()
for i := 0; i < b.N; i++ {
logger.Info().Str("foo", "bar").Int("int", 42).Msg(fakeMessage)
}
}
func BenchmarkPhusLog(b *testing.B) {
logger := log.Logger{
TimeFormat: "", // uses rfc3339 by default
Writer: log.IOWriter{ioutil.Discard},
}
for i := 0; i < b.N; i++ {
logger.Info().Str("foo", "bar").Int("int", 42).Msg(fakeMessage)
}
}

View File

@ -0,0 +1,182 @@
package benchmarks
import (
//"io/ioutil"
//"log"
"testing"
"go.uber.org/zap"
)
func BenchmarkDisabledWithoutFields(b *testing.B) {
b.Logf("Logging at a disabled level without any structured context.")
b.Run("Logos", func(b *testing.B) {
logger := newZapLogger(zap.ErrorLevel)
i := 0
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
i++
logger.Info(getMessage(i))
}
})
})
b.Run("Zap.Check", func(b *testing.B) {
logger := newSampledLogger(zap.ErrorLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if m := logger.Check(zap.InfoLevel, getMessage(0)); m != nil {
m.Write()
}
}
})
})
b.Run("Zap.Sugar", func(b *testing.B) {
logger := newSampledLogger(zap.ErrorLevel).Sugar()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info(getMessage(0))
}
})
})
//b.Run("Zap.SugarFormatting", func(b *testing.B) {
// logger := newZapLogger(zap.ErrorLevel).Sugar()
// b.ResetTimer()
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
// }
// })
//})
b.Run("apex/log", func(b *testing.B) {
logger := newDisabledApexLog()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info(getMessage(0))
}
})
})
b.Run("sirupsen/logrus", func(b *testing.B) {
logger := newDisabledLogrus()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info(getMessage(0))
}
})
})
//b.Run("rs/zerolog", func(b *testing.B) {
// logger := newDisabledZerolog()
// b.ResetTimer()
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// logger.Info().Msg(getMessage(0))
// }
// })
//})
}
func BenchmarkAddingFields(b *testing.B) {
b.Logf("Logging with additional context at each log site.")
b.Run("Zap", func(b *testing.B) {
logger := newZapLogger(zap.DebugLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Infow(getMessage(0), fakeFields()...)
}
})
})
b.Run("Zap.Check", func(b *testing.B) {
logger := newSampledLogger(zap.DebugLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if ce := logger.Check(zap.InfoLevel, getMessage(0)); ce != nil {
//ce.Write(fakeFields()...)
}
}
})
})
b.Run("Zap.CheckSampled", func(b *testing.B) {
logger := newSampledLogger(zap.DebugLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
i++
if ce := logger.Check(zap.InfoLevel, getMessage(i)); ce != nil {
//ce.Write(fakeFields()...)
}
}
})
})
b.Run("Zap.Sugar", func(b *testing.B) {
logger := newSampledLogger(zap.DebugLevel).Sugar()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Infow(getMessage(0), fakeSugarFields()...)
}
})
})
b.Run("apex/log", func(b *testing.B) {
logger := newApexLog()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.WithFields(fakeApexFields()).Info(getMessage(0))
}
})
})
//b.Run("go-kit/kit/log", func(b *testing.B) {
// logger := newKitLog()
// b.ResetTimer()
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// logger.Log(fakeSugarFields()...)
// }
// })
//})
//b.Run("inconshreveable/log15", func(b *testing.B) {
// logger := newLog15()
// b.ResetTimer()
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// logger.Info(getMessage(0), fakeSugarFields()...)
// }
// })
//})
b.Run("sirupsen/logrus", func(b *testing.B) {
logger := newLogrus()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.WithFields(fakeLogrusFields()).Info(getMessage(0))
}
})
})
//b.Run("rs/zerolog", func(b *testing.B) {
// logger := newZerolog()
// b.ResetTimer()
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// fakeZerologFields(logger.Info()).Msg(getMessage(0))
// }
// })
//})
//b.Run("rs/zerolog.Check", func(b *testing.B) {
// logger := newZerolog()
// b.ResetTimer()
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// if e := logger.Info(); e.Enabled() {
// fakeZerologFields(e).Msg(getMessage(0))
// }
// }
// })
//})
}

60
config/config.go Normal file
View File

@ -0,0 +1,60 @@
package config
import "github.com/khorevaa/logos/internal/common"
const DefaultConfig = `
appenders:
console:
- name: CONSOLE
target: stdout
encoder:
console:
time_encoder: ISO8601
loggerConfigs:
root:
level: info
appender_refs:
- CONSOLE
logger:
- name: stdlog
level: info
add_caller: true
scan: false
scan_period: 1m
`
type Config struct {
Appenders map[string][]*common.Config `logos-config:"appenders"`
Loggers Loggers `logos-config:"loggerConfigs"`
}
type ScanConfig struct {
Scan bool `logos-config:"scan"`
ScanPeriod string `logos-config:"scan_period"`
}
type Loggers struct {
Root RootLogger `logos-config:"root"`
Logger []LoggerConfig `logos-config:"logger"`
}
type RootLogger struct {
Level string `logos-config:"level"`
AppenderRefs []string `logos-config:"appender_refs"`
AppenderConfig []AppenderConfig `logos-config:"appenders"`
}
type LoggerConfig struct {
Name string `logos-config:"name" logos-validate:"required"`
Level string `logos-config:"level"`
AddCaller bool `logos-config:"add_caller"`
TraceLevel string `logos-config:"trace_level"`
AppenderRefs []string `logos-config:"appender_refs"`
AppenderConfig []AppenderConfig `logos-config:"appenders"`
}
type AppenderConfig struct {
Name string `logos-config:"name" logos-validate:"required"`
Level string `logos-config:"level"`
}

71
config/config_test.go Normal file
View File

@ -0,0 +1,71 @@
package config
import (
"github.com/khorevaa/logos/appender/console"
common2 "github.com/khorevaa/logos/encoder/common"
"github.com/khorevaa/logos/internal/common"
"testing"
)
type ConfigNamespace struct {
name string
config console.Config
}
func TestConfigFrom(t *testing.T) {
tests := []struct {
name string
config interface{}
text []string
}{
{
name: "simple",
config: struct {
Appenders map[string][]interface{} `logos-config:"appenders"`
Loggers Loggers `logos-config:"loggerConfigs"`
}{
Appenders: map[string][]interface{}{
"console": {struct {
Name string `logos-config:"name"`
Target string `logos-config:"target"`
Encoder interface{} `logos-config:"encoder"`
}{
Name: "CONSOLE",
Target: "stderr",
Encoder: struct {
Json common2.JsonEncoderConfig `logos-config:"json"`
}{
Json: common2.JsonEncoderConfig{
TimeEncoder: "ISO8601",
},
},
},
},
},
Loggers: Loggers{
Root: RootLogger{
Level: "error",
AppenderRefs: []string{"CONSOLE"},
//AppenderConfig: nil,
},
}},
text: []string{"hello world", "hello"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg, _ := common.NewConfigFrom(tt.config)
defConfig, err := common.NewConfigFrom(DefaultConfig)
cfg, err = common.MergeConfigs(defConfig, cfg)
t.Error(err)
})
}
}

33
encoder/common/common.go Normal file
View File

@ -0,0 +1,33 @@
package common
import (
"fmt"
"go.uber.org/zap/zapcore"
)
// Config is used to pass encoding parameters to New.
type JsonEncoderConfig struct {
TimeKey string `logos-config:"time_key"`
LevelKey string `logos-config:"level_key"`
NameKey string `logos-config:"name_key"`
CallerKey string `logos-config:"caller_key"`
MessageKey string `logos-config:"message_key"`
StacktraceKey string `logos-config:"stacktrace_key"`
LineEnding string `logos-config:"line_ending"`
TimeEncoder string `logos-config:"time_encoder" logos-validate:"logos.oneof=epoch epoch_millis epoch_nanos ISO8601"`
}
func GetTimeEncoder(name string) (zapcore.TimeEncoder, error) {
switch name {
case "epoch":
return zapcore.EpochTimeEncoder, nil
case "epoch_millis":
return zapcore.EpochMillisTimeEncoder, nil
case "epoch_nanos":
return zapcore.EpochNanosTimeEncoder, nil
case "ISO8601":
return zapcore.ISO8601TimeEncoder, nil
default:
return nil, fmt.Errorf("no such TimeEncoder %q", name)
}
}

182
encoder/console/color.go Normal file
View File

@ -0,0 +1,182 @@
package console
import (
"fmt"
"reflect"
"strings"
)
const (
// No color
NoColor uint16 = 1 << 15
)
const (
// Foreground colors for ColorScheme.
_ uint16 = iota | NoColor
Black
Red
Green
Yellow
Blue
Magenta
Cyan
White
bitsForeground = 0
maskForeground = 0xf
ansiForegroundOffset = 30 - 1
)
const (
// Background colors for ColorScheme.
_ uint16 = iota<<bitsBackground | NoColor
BackgroundBlack
BackgroundRed
BackgroundGreen
BackgroundYellow
BackgroundBlue
BackgroundMagenta
BackgroundCyan
BackgroundWhite
bitsBackground = 4
maskBackground = 0xf << bitsBackground
ansiBackgroundOffset = 40 - 1
)
const (
// Bold flag for ColorScheme.
Bold uint16 = 1<<bitsBold | NoColor
bitsBold = 8
maskBold = 1 << bitsBold
ansiBold = 1
)
// To use with SetColorScheme.
type ColorScheme struct {
Bool uint16
Integer uint16
Float uint16
String uint16
StringQuotation uint16
EscapedChar uint16
FieldName uint16
PointerAddress uint16
Nil uint16
Time uint16
StructName uint16
ObjectLength uint16
LogNaming uint16
Timestamp uint16
InfoLevel uint16
WarnLevel uint16
ErrorLevel uint16
FatalLevel uint16
PanicLevel uint16
DPanicLevel uint16
DebugLevel uint16
}
var (
defaultScheme = ColorScheme{
Bool: Cyan | Bold,
Integer: Blue | Bold,
Float: Magenta | Bold,
String: Red,
StringQuotation: Red | Bold,
EscapedChar: Magenta | Bold,
FieldName: Yellow,
PointerAddress: Blue | Bold,
Nil: Cyan | Bold,
Time: Blue | Bold,
StructName: Green,
ObjectLength: Blue,
LogNaming: Black | Bold | BackgroundWhite,
Timestamp: Blue | Bold,
InfoLevel: Green,
WarnLevel: Yellow | Bold,
ErrorLevel: Red,
FatalLevel: Red | Bold,
PanicLevel: Red | Bold,
DPanicLevel: Black | Bold | BackgroundRed,
DebugLevel: Blue,
}
colorMap = map[string]uint16{
"black": Black,
"red": Red,
"green": Green,
"yellow": Yellow,
"blue": Blue,
"magenta": Magenta,
"cyan": Cyan,
"white": White,
}
colorBgMap = map[string]uint16{
"black": BackgroundBlack,
"red": BackgroundRed,
"green": BackgroundGreen,
"yellow": BackgroundYellow,
"blue": BackgroundBlue,
"magenta": BackgroundMagenta,
"cyan": BackgroundCyan,
"white": BackgroundWhite,
}
)
func getColor(color string) uint16 {
val, ok := colorMap[strings.ToLower(color)]
if ok {
return val
}
return 0
}
func getBgColor(color string) uint16 {
val, ok := colorBgMap[strings.ToLower(color)]
if ok {
return val
}
return 0
}
func (cs *ColorScheme) fixColors() {
typ := reflect.Indirect(reflect.ValueOf(cs))
defaultType := reflect.ValueOf(defaultScheme)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.Uint() == 0 {
field.SetUint(defaultType.Field(i).Uint())
}
}
}
func colorizeText(text string, color uint16) string {
foreground := color & maskForeground >> bitsForeground
background := color & maskBackground >> bitsBackground
bold := color & maskBold
if foreground == 0 && background == 0 && bold == 0 {
return text
}
modBold := ""
modForeground := ""
modBackground := ""
if bold > 0 {
modBold = "\033[1m"
}
if foreground > 0 {
modForeground = fmt.Sprintf("\033[%dm", foreground+ansiForegroundOffset)
}
if background > 0 {
modBackground = fmt.Sprintf("\033[%dm", background+ansiBackgroundOffset)
}
return fmt.Sprintf("%s%s%s%s\033[0m", modForeground, modBackground, modBold, text)
}

View File

@ -0,0 +1,359 @@
package console
import (
"encoding/base64"
"fmt"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"strconv"
"sync"
"time"
)
var poolColoredEncoder = sync.Pool{
New: func() interface{} {
return &coloredEncoder{
buf: bufferpool.Get(),
}
},
}
func getColoredEncoder(lvlColor uint16, scheme ColorScheme) *coloredEncoder {
enc := poolColoredEncoder.Get().(*coloredEncoder)
enc.buf = bufferpool.Get()
enc.scheme = scheme
enc.entLevelColor = lvlColor
return enc
}
func getLevelColor(level zapcore.Level, scheme ColorScheme) uint16 {
switch level {
case zapcore.DebugLevel:
return scheme.DebugLevel
case zapcore.InfoLevel:
return scheme.InfoLevel
case zapcore.ErrorLevel:
return scheme.ErrorLevel
case zapcore.PanicLevel:
return scheme.PanicLevel
case zapcore.WarnLevel:
return scheme.WarnLevel
case zapcore.DPanicLevel:
return scheme.DPanicLevel
case zapcore.FatalLevel:
return scheme.FatalLevel
default:
return scheme.String
}
}
func putColoredEncoder(enc *coloredEncoder) {
enc.scheme = defaultScheme
enc.buf.Free()
enc.entLevelColor = NoColor
enc.EncodeDuration = nil
enc.EncodeTime = nil
poolColoredEncoder.Put(enc)
}
type coloredEncoder struct {
buf *buffer.Buffer
disableColor bool
scheme ColorScheme
entLevelColor uint16
EncodeDuration zapcore.DurationEncoder
EncodeTime zapcore.TimeEncoder
}
func (e *coloredEncoder) addKey(key string) {
e.buf.AppendByte(' ')
e.appendColoredString(key, e.scheme.FieldName)
e.buf.AppendByte('=')
}
func (e *coloredEncoder) appendColoredString(val string, color uint16) {
if e.disableColor {
e.buf.AppendString(val)
return
}
e.buf.AppendString(colorizeText(val, color))
}
func (e *coloredEncoder) AppendBool(val bool) {
e.addElementSeparator()
e.appendColoredString(strconv.FormatBool(val), e.scheme.Bool)
}
func (e *coloredEncoder) AppendByteString(bstr []byte) {
e.addElementSeparator()
e.buf.AppendString(string(bstr))
}
func (e *coloredEncoder) AppendComplex128(val complex128) {
r, i := float64(real(val)), float64(imag(val))
str := fmt.Sprintf("%s%s",
strconv.FormatFloat(r, 'f', -1, 64),
strconv.FormatFloat(i, 'f', -1, 64))
e.addElementSeparator()
e.appendColoredString(str, e.scheme.Float)
}
func (e *coloredEncoder) AppendComplex64(val complex64) {
e.AppendComplex128(complex128(val))
}
func (e *coloredEncoder) AppendFloat64(val float64) {
e.addElementSeparator()
e.appendColoredString(strconv.FormatFloat(val, 'f', -1, 64), e.scheme.Float)
}
func (e *coloredEncoder) AppendFloat32(val float32) {
e.AppendFloat64(float64(val))
}
func (e *coloredEncoder) AppendInt(val int) {
e.AppendInt64(int64(val))
}
func (e *coloredEncoder) AppendInt64(val int64) {
e.addElementSeparator()
e.appendColoredString(strconv.FormatInt(val, 10), e.scheme.Integer)
}
func (e *coloredEncoder) AppendInt32(val int32) {
e.AppendInt64(int64(val))
}
func (e *coloredEncoder) AppendInt16(val int16) {
e.AppendInt64(int64(val))
}
func (e *coloredEncoder) AppendInt8(val int8) {
e.AppendInt64(int64(val))
}
func (e *coloredEncoder) AppendString(str string) {
e.addElementSeparator()
e.appendColoredString(str, e.scheme.String)
}
func (e *coloredEncoder) AppendUint(val uint) {
e.AppendUint64(uint64(val))
}
func (e *coloredEncoder) AppendUint64(val uint64) {
e.addElementSeparator()
e.appendColoredString(strconv.FormatUint(val, 64), e.scheme.PointerAddress)
}
func (e *coloredEncoder) AppendUint32(val uint32) {
e.AppendUint64(uint64(val))
}
func (e *coloredEncoder) AppendUint16(val uint16) {
e.AppendUint64(uint64(val))
}
func (e *coloredEncoder) AppendUint8(val uint8) {
e.AppendUint64(uint64(val))
}
func (e *coloredEncoder) AppendUintptr(val uintptr) {
e.AppendUint64(uint64(val))
}
func (e *coloredEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
e.addKey(key)
return e.AppendArray(marshaler)
}
func (e *coloredEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error {
e.addKey(key)
return e.AppendObject(obj)
}
//func (enc *coloredEncoder) AppendArray(arr ArrayMarshaler) error {
// enc.addElementSeparator()
// enc.buf.AppendByte('[')
// err := arr.MarshalLogArray(enc)
// enc.buf.AppendByte(']')
// return err
//}
//
//func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error {
// enc.addElementSeparator()
// enc.buf.AppendByte('{')
// err := obj.MarshalLogObject(enc)
// enc.buf.AppendByte('}')
// return err
//}
func (e *coloredEncoder) AddBinary(key string, val []byte) {
e.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (e *coloredEncoder) AddByteString(key string, val []byte) {
e.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (e *coloredEncoder) AddBool(key string, val bool) {
e.addKey(key)
e.AppendBool(val)
}
func (e *coloredEncoder) AddComplex128(key string, val complex128) {
e.addKey(key)
e.AppendComplex128(val)
}
func (e *coloredEncoder) AddComplex64(key string, val complex64) {
e.addKey(key)
e.AppendComplex128(complex128(val))
}
func (e *coloredEncoder) AddDuration(key string, val time.Duration) {
e.addKey(key)
e.AppendDuration(val)
}
func (e *coloredEncoder) AddFloat64(key string, val float64) {
e.addKey(key)
e.AppendFloat64(val)
}
func (e *coloredEncoder) AddFloat32(key string, val float32) {
e.AddFloat64(key, float64(val))
}
func (e *coloredEncoder) AddInt(key string, val int) {
e.AddInt64(key, int64(val))
}
func (e *coloredEncoder) AddInt64(key string, val int64) {
e.addKey(key)
e.AppendInt64(val)
}
func (e *coloredEncoder) AddInt32(key string, val int32) {
e.AddInt64(key, int64(val))
}
func (e *coloredEncoder) AddInt16(key string, val int16) {
e.AddInt64(key, int64(val))
}
func (e *coloredEncoder) AddInt8(key string, val int8) {
e.AddInt64(key, int64(val))
}
func (e *coloredEncoder) AddString(key string, val string) {
e.addKey(key)
e.AppendString(val)
}
func (e *coloredEncoder) AddTime(key string, val time.Time) {
e.addKey(key)
e.AppendTime(val)
}
func (e *coloredEncoder) AddUint(key string, val uint) {
e.AddUint64(key, uint64(val))
}
func (e *coloredEncoder) AddUint64(key string, val uint64) {
e.addKey(key)
e.AppendUint64(val)
}
func (e *coloredEncoder) AddUint32(key string, val uint32) {
e.AddUint64(key, uint64(val))
}
func (e *coloredEncoder) AddUint16(key string, val uint16) {
e.AddUint64(key, uint64(val))
}
func (e *coloredEncoder) AddUint8(key string, val uint8) {
e.AddUint64(key, uint64(val))
}
func (e *coloredEncoder) AddUintptr(key string, val uintptr) {
e.AddUint64(key, uint64(val))
}
func (e *coloredEncoder) AddReflected(key string, val interface{}) error {
e.addKey(key)
v, ok := val.(string)
if !ok {
v = fmt.Sprintf("%v", val)
}
e.AppendString(v)
return nil
}
func (e *coloredEncoder) OpenNamespace(_ string) {
// no-op -
// namespaces do not really visually apply to console logs
}
func (e *coloredEncoder) AppendDuration(val time.Duration) {
cur := e.buf.Len()
e.EncodeDuration(val, e)
if cur == e.buf.Len() {
e.AppendInt64(int64(val))
}
}
func (e *coloredEncoder) AppendTime(val time.Time) {
cur := e.buf.Len()
e.EncodeTime(val, e)
if cur == e.buf.Len() {
e.AppendInt64(val.UnixNano())
}
}
func (e *coloredEncoder) AppendArray(arr zapcore.ArrayMarshaler) error {
e.addElementSeparator()
e.buf.AppendByte('[')
err := arr.MarshalLogArray(e)
e.buf.AppendByte(']')
return err
}
func (e *coloredEncoder) AppendObject(obj zapcore.ObjectMarshaler) error {
e.addElementSeparator()
e.buf.AppendByte('{')
err := obj.MarshalLogObject(e)
e.buf.AppendByte('}')
return err
}
func (e *coloredEncoder) AppendReflected(val interface{}) error {
v, ok := val.(string)
if !ok {
v = fmt.Sprintf("%v", val)
}
e.addElementSeparator()
e.AppendString(v)
return nil
}
func (e *coloredEncoder) addElementSeparator() {
last := e.buf.Len() - 1
if last < 0 {
return
}
switch e.buf.Bytes()[last] {
case '{', '[', ':', ',', ' ', '=':
return
default:
e.buf.AppendByte(',')
}
}

112
encoder/console/config.go Normal file
View File

@ -0,0 +1,112 @@
package console
import "strings"
// Schema is the color schema for the default log parts/levels
type ColorSchemaConfig struct {
Timestamp string `logos-config:"timestamp"`
Naming string `logos-config:"naming"`
InfoLevel string `logos-config:"info_level"`
WarnLevel string `logos-config:"warn_level"`
ErrorLevel string `logos-config:"error_level"`
FatalLevel string `logos-config:"fatal_level"`
PanicLevel string `logos-config:"panic_level"`
DPanicLevel string `logos-config:"dpanic_level"`
DebugLevel string `logos-config:"debug_level"`
}
func (c ColorSchemaConfig) Parse() ColorScheme {
scheme := ColorScheme{}
scheme.Timestamp = parseFieldColor(c.Timestamp)
scheme.LogNaming = parseFieldColor(c.Naming)
scheme.InfoLevel = parseFieldColor(c.InfoLevel)
scheme.WarnLevel = parseFieldColor(c.WarnLevel)
scheme.ErrorLevel = parseFieldColor(c.ErrorLevel)
scheme.FatalLevel = parseFieldColor(c.FatalLevel)
scheme.PanicLevel = parseFieldColor(c.PanicLevel)
scheme.DPanicLevel = parseFieldColor(c.DPanicLevel)
scheme.DebugLevel = parseFieldColor(c.DebugLevel)
scheme.fixColors()
return scheme
}
func parseFieldColor(colorString string) uint16 {
colors := strings.Split(colorString, ",")
if len(colors) == 0 {
return 0
}
color := colors[0]
bold := uint16(0)
bgColor := ""
if arr := strings.Split(color, "+"); len(arr) > 1 {
color = strings.TrimSpace(arr[0])
if strings.TrimSpace(arr[1]) == "b" {
bold = Bold
}
}
if len(colors) > 1 {
bgColor = strings.TrimSpace(colors[1])
}
return getColor(color) | bold | getBgColor(bgColor)
}
// Config is used to pass encoding parameters to New.
type Config struct {
// color schema for messages
ColorSchema *ColorSchemaConfig `logos-config:"color_scheme"`
// no colors
DisableColors bool `logos-config:"disable_colors"`
// no check for TTY terminal
ForceColors bool `logos-config:"force_colors"`
// false -> name passed, true -> github.com/khorevaa/logos
DisableNaming bool `logos-config:"disable_naming"`
// no timestamp
DisableTimestamp bool `logos-config:"disable_timestamp"`
// console separator default space
ConsoleSeparator string `logos-config:"console_separator"`
// false -> time passed, true -> timestamp
UseTimePassedAsTimestamp bool `logos-config:"pass_timestamp"`
// false -> info, true -> INFO
UseUppercaseLevel bool `logos-config:"uppercase_level"`
TimestampFormat string `logos-config:"timestamp_format"`
LineEnding string `logos-config:"line_ending"`
}
// EncoderConfig is used to pass encoding parameters to New.
type EncoderConfig struct {
// no colors
DisableColors bool
// no check for TTY terminal
ForceColors bool
// false -> time passed, true -> timestamp
UseTimePassedAsTimestamp bool
// false -> info, true -> INFO
UseUppercaseLevel bool
// false -> name passed, true -> github.com/khorevaa/logos
DisableNaming bool
// no timestamp
DisableTimestamp bool
// console separator default space
ConsoleSeparator string
// line end for log
LineEnding string
// time format string
TimestampFormat string
// color schema for messages
Schema ColorScheme
}

209
encoder/console/console.go Normal file
View File

@ -0,0 +1,209 @@
package console
import (
"fmt"
"github.com/khorevaa/logos/appender"
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"time"
)
var defaultTimestampFormat = "2006-01-02T15:04:05.000Z0700"
var baseTimestamp = time.Now()
var defaultConfig = Config{
ConsoleSeparator: "\t",
TimestampFormat: defaultTimestampFormat,
LineEnding: "\n",
}
func init() {
appender.RegisterEncoderType("console", func(cfg *common.Config) (zapcore.Encoder, error) {
config := defaultConfig
if cfg != nil {
if err := cfg.Unpack(&config); err != nil {
return nil, err
}
}
encoderConfig := EncoderConfig{
DisableColors: config.DisableColors,
ForceColors: config.ForceColors,
DisableNaming: config.DisableNaming,
DisableTimestamp: config.DisableTimestamp,
ConsoleSeparator: config.ConsoleSeparator,
LineEnding: config.LineEnding,
TimestampFormat: config.TimestampFormat,
UseTimePassedAsTimestamp: config.UseTimePassedAsTimestamp,
UseUppercaseLevel: config.UseUppercaseLevel,
}
encoderConfig.Schema = defaultScheme
if config.ColorSchema != nil {
encoderConfig.Schema = config.ColorSchema.Parse()
}
en := NewEncoder(encoderConfig)
return en, nil
})
}
// NewEncoder initializes a a bol.com tailored Encoder
func NewEncoder(cfg EncoderConfig) *Encoder {
return &Encoder{
buf: bufferpool.Get(),
EncoderConfig: cfg,
}
}
// Encoder is a bol.com tailored zap encoder for
// writing human readable logs to the console
type Encoder struct {
buf *buffer.Buffer
EncoderConfig
}
// Clone implements the Clone method of the zapcore Encoder interface
func (e *Encoder) Clone() zapcore.Encoder {
clone := e.clone()
_, _ = clone.buf.Write(e.buf.Bytes())
return clone
}
func (e *Encoder) clone() *Encoder {
clone := get()
clone.EncoderConfig = e.EncoderConfig
clone.buf = bufferpool.Get()
return clone
}
// EncodeEntry implements the EncodeEntry method of the zapcore Encoder interface
func (e Encoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := bufferpool.Get()
lvlColor := getLevelColor(ent.Level, e.Schema)
e.appendTimeInfo(line, ent)
line.AppendString(e.colorizeText(ent.Level.CapitalString(), lvlColor))
e.addSeparatorIfNecessary(line)
//pp.Println(e.DisableNaming, ent.LoggerName)
if !e.DisableNaming && len(ent.LoggerName) > 0 {
line.AppendString(e.colorizeText(ent.LoggerName, e.Schema.LogNaming))
e.addSeparatorIfNecessary(line)
}
if ent.Caller.Defined {
line.AppendString(e.colorizeText(ent.Caller.TrimmedPath(), e.Schema.Nil))
e.addSeparatorIfNecessary(line)
}
line.AppendString(e.colorizeText(ent.Message, lvlColor))
//e.addSeparatorIfNecessary(line)
// Add any structured context.
e.writeContext(lvlColor, line, fields)
// If there's no stacktrace key, honor that; this allows users to force
// single-line output.
if ent.Stack != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
line.AppendString(zapcore.DefaultLineEnding)
return line, nil
}
// appendTimeInfo appends the time related info
// appends nothing on DisableTimestamp
// appends [seconds] on UseTimePassedAsTimestamp
// appends formatted TimestampFormat else
func (e *Encoder) appendTimeInfo(buf *buffer.Buffer, entry zapcore.Entry) {
if !e.DisableTimestamp {
var timeInfo string
if e.UseTimePassedAsTimestamp {
timeInfo = fmt.Sprintf("[%04d]", int(entry.Time.Sub(baseTimestamp)/time.Second))
} else {
timestampFormat := e.TimestampFormat
if timestampFormat == "" {
timestampFormat = defaultTimestampFormat
}
timeInfo = entry.Time.Format(timestampFormat)
}
buf.AppendString(e.colorizeText(timeInfo, e.Schema.Timestamp))
e.addSeparatorIfNecessary(buf)
}
}
func (e Encoder) writeContext(defColor uint16, out *buffer.Buffer, extra []zapcore.Field) {
if len(extra) == 0 {
return
}
//e.buf.AppendByte('{')
enc := getColoredEncoder(defColor, e.Schema)
defer putColoredEncoder(enc)
addFields(enc, extra)
if enc.buf.Len() > 0 {
out.Write(enc.buf.Bytes())
}
}
func (e Encoder) addSeparatorIfNecessary(line *buffer.Buffer) {
if line.Len() > 0 {
line.AppendString(e.ConsoleSeparator)
}
}
func (e Encoder) colorizeText(text string, color uint16) string {
if e.DisableColors {
return text
}
return colorizeText(text, color)
}
func (e *Encoder) addKey(key string) {
if !e.DisableColors {
key = colorizeText(key, e.Schema.FieldName)
}
e.buf.Write([]byte(e.ConsoleSeparator))
e.buf.AppendString(key)
e.buf.AppendByte('=')
}
func (e *Encoder) encodeTime(val time.Time) string {
timestampFormat := e.TimestampFormat
if timestampFormat == "" {
timestampFormat = defaultTimestampFormat
}
timeInfo := val.Format(timestampFormat)
return timeInfo
}
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields {
fields[i].AddTo(enc)
}
}

250
encoder/console/methods.go Normal file
View File

@ -0,0 +1,250 @@
package console
import (
"encoding/base64"
"fmt"
"time"
"go.uber.org/zap/zapcore"
)
// This file contains the methods that implement
// the zapcore ConsoleEncoder interfaces
func (e *Encoder) AppendBool(val bool) {
e.buf.AppendBool(val)
}
func (e *Encoder) AppendByteString(bstr []byte) {
e.buf.AppendString(string(bstr))
}
func (e *Encoder) AppendComplex128(val complex128) {
r, i := float64(real(val)), float64(imag(val))
e.buf.AppendFloat(r, 64)
e.buf.AppendFloat(i, 64)
}
func (e *Encoder) AppendComplex64(val complex64) {
e.AppendComplex128(complex128(val))
}
func (e *Encoder) AppendFloat64(val float64) {
e.buf.AppendFloat(val, 64)
}
func (e *Encoder) AppendFloat32(val float32) {
e.buf.AppendFloat(float64(val), 32)
}
func (e *Encoder) AppendInt(val int) {
e.buf.AppendInt(int64(val))
}
func (e *Encoder) AppendInt64(val int64) {
e.buf.AppendInt(val)
}
func (e *Encoder) AppendInt32(val int32) {
e.buf.AppendInt(int64(val))
}
func (e *Encoder) AppendInt16(val int16) {
e.buf.AppendInt(int64(val))
}
func (e *Encoder) AppendInt8(val int8) {
e.buf.AppendInt(int64(val))
}
func (e *Encoder) AppendString(str string) {
e.buf.AppendString(str)
}
func (e *Encoder) AppendUint(val uint) {
e.buf.AppendUint(uint64(val))
}
func (e *Encoder) AppendUint64(val uint64) {
e.buf.AppendUint(val)
}
func (e *Encoder) AppendUint32(val uint32) {
e.buf.AppendUint(uint64(val))
}
func (e *Encoder) AppendUint16(val uint16) {
e.buf.AppendUint(uint64(val))
}
func (e *Encoder) AppendUint8(val uint8) {
e.buf.AppendUint(uint64(val))
}
func (e *Encoder) AppendUintptr(val uintptr) {
e.AppendUint64(uint64(val))
}
func (e *Encoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
e.addKey(key)
return marshaler.MarshalLogArray(e)
}
func (e *Encoder) AddObject(key string, obj zapcore.ObjectMarshaler) error {
e.addKey(key)
return obj.MarshalLogObject(e)
}
func (e *Encoder) AddBinary(key string, val []byte) {
e.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (e *Encoder) AddByteString(key string, val []byte) {
e.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (e *Encoder) AddBool(key string, val bool) {
e.addKey(key)
e.AppendBool(val)
}
func (e *Encoder) AddComplex128(key string, val complex128) {
e.addKey(key)
e.AppendComplex128(val)
}
func (e *Encoder) AddComplex64(key string, val complex64) {
e.addKey(key)
e.AppendComplex128(complex128(val))
}
func (e *Encoder) AddDuration(key string, val time.Duration) {
e.addKey(key)
e.AppendDuration(val)
}
func (e *Encoder) AddFloat64(key string, val float64) {
e.addKey(key)
e.AppendFloat64(val)
}
func (e *Encoder) AddFloat32(key string, val float32) {
e.AddFloat64(key, float64(val))
}
func (e *Encoder) AddInt(key string, val int) {
e.AddInt64(key, int64(val))
}
func (e *Encoder) AddInt64(key string, val int64) {
e.addKey(key)
e.AppendInt64(val)
}
func (e *Encoder) AddInt32(key string, val int32) {
e.AddInt64(key, int64(val))
}
func (e *Encoder) AddInt16(key string, val int16) {
e.AddInt64(key, int64(val))
}
func (e *Encoder) AddInt8(key string, val int8) {
e.AddInt64(key, int64(val))
}
func (e *Encoder) AddString(key string, val string) {
e.addKey(key)
e.AppendString(val)
}
func (e *Encoder) AddTime(key string, val time.Time) {
e.addKey(key)
e.AppendTime(val)
}
func (e *Encoder) AddUint(key string, val uint) {
e.AddUint64(key, uint64(val))
}
func (e *Encoder) AddUint64(key string, val uint64) {
e.addKey(key)
e.AppendUint64(val)
}
func (e *Encoder) AddUint32(key string, val uint32) {
e.AddUint64(key, uint64(val))
}
func (e *Encoder) AddUint16(key string, val uint16) {
e.AddUint64(key, uint64(val))
}
func (e *Encoder) AddUint8(key string, val uint8) {
e.AddUint64(key, uint64(val))
}
func (e *Encoder) AddUintptr(key string, val uintptr) {
e.AddUint64(key, uint64(val))
}
func (e *Encoder) AddReflected(key string, val interface{}) error {
e.addKey(key)
v, ok := val.(string)
if !ok {
v = fmt.Sprintf("%v", val)
}
e.AppendString(v)
return nil
}
func (e *Encoder) OpenNamespace(_ string) {
// no-op -
// namespaces do not really visually apply to console logs
}
func (e *Encoder) AppendDuration(val time.Duration) {
//cur := e.buf.Len()
//e.EncoderConfig.TimestampFormat.EncodeDuration(val, e)
//if cur == e.buf.Len() {
e.AppendInt64(int64(val))
//}
}
func (e *Encoder) AppendTime(val time.Time) {
cur := e.buf.Len()
encodeTime := e.encodeTime(val)
if len(encodeTime) > 0 {
e.buf.AppendString(encodeTime)
}
if cur == e.buf.Len() {
e.AppendInt64(val.UnixNano())
}
}
func (e *Encoder) AppendArray(arr zapcore.ArrayMarshaler) error {
e.buf.AppendByte('[')
err := arr.MarshalLogArray(e)
e.buf.AppendByte(']')
return err
}
func (e *Encoder) AppendObject(obj zapcore.ObjectMarshaler) error {
e.buf.AppendByte('{')
err := obj.MarshalLogObject(e)
e.buf.AppendByte('}')
return err
}
func (e *Encoder) AppendReflected(val interface{}) error {
v, ok := val.(string)
if !ok {
v = fmt.Sprintf("%v", val)
}
e.AppendString(v)
return nil
}

23
encoder/console/pool.go Normal file
View File

@ -0,0 +1,23 @@
package console
import (
"go.uber.org/zap/buffer"
"sync"
)
var pool = sync.Pool{
New: func() interface{} {
return &Encoder{}
},
}
func get() *Encoder {
return pool.Get().(*Encoder)
}
func put(enc *Encoder) {
enc.buf = nil
pool.Put(enc)
}
var bufferpool = buffer.NewPool()

100
encoder/gelf/gelf.go Normal file
View File

@ -0,0 +1,100 @@
package gelf
import (
"github.com/khorevaa/logos/appender"
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"os"
)
type Encoder struct {
Fields []zapcore.Field
zapcore.Encoder
}
type KeyValuePair struct {
Key string `logos-config:"key"`
Value string `logos-config:"value"`
}
type Config struct {
KeyValuePairs []KeyValuePair `logos-config:"key_value_pairs"`
}
func (e *Encoder) EncodeEntry(enc zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
newFields := make([]zap.Field, len(e.Fields)+len(fields))
i := 0
for ; i < len(e.Fields); i++ {
newFields[i] = e.Fields[i]
}
for ; i < len(e.Fields)+len(fields); i++ {
j := i - len(e.Fields)
f := fields[j]
f.Key = "_" + f.Key
newFields[i] = f
}
return e.Encoder.EncodeEntry(enc, newFields)
}
func LevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
level := uint8(7)
switch l {
case zapcore.DebugLevel:
level = 7
case zapcore.InfoLevel:
level = 6
case zapcore.WarnLevel:
level = 4
case zapcore.ErrorLevel:
level = 3
case zapcore.DPanicLevel:
level = 2
case zapcore.PanicLevel:
level = 1
case zapcore.FatalLevel:
level = 0
}
enc.AppendUint8(level)
}
func init() {
appender.RegisterEncoderType("gelf", func(config *common.Config) (zapcore.Encoder, error) {
cfg := Config{}
if err := config.Unpack(&cfg); err != nil {
return nil, err
}
encoderConfig := zapcore.EncoderConfig{
TimeKey: "timestamp",
LevelKey: "level",
NameKey: "_logger",
CallerKey: "_caller",
MessageKey: "short_message",
StacktraceKey: "full_message",
LineEnding: "\n",
EncodeLevel: LevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
hostname, err := os.Hostname()
if err != nil {
return nil, err
}
fields := []zapcore.Field{
zap.String("version", "1.1"),
zap.String("host", hostname),
}
for _, kv := range cfg.KeyValuePairs {
fields = append(fields, zap.String("_"+kv.Key, kv.Value))
}
return &Encoder{
Fields: fields,
Encoder: zapcore.NewJSONEncoder(encoderConfig),
}, nil
})
}

133
encoder/gelf/gelf_test.go Normal file
View File

@ -0,0 +1,133 @@
package gelf
import (
"fmt"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"testing"
"time"
)
type dummyEncoder struct {
}
func (*dummyEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
panic("implement me")
}
func (*dummyEncoder) AddBinary(key string, value []byte) {
panic("implement me")
}
func (*dummyEncoder) AddBool(key string, value bool) {
panic("implement me")
}
func (*dummyEncoder) AddByteString(key string, value []byte) {
panic("implement me")
}
func (*dummyEncoder) AddComplex128(key string, value complex128) {
panic("implement me")
}
func (*dummyEncoder) AddComplex64(key string, value complex64) {
panic("implement me")
}
func (*dummyEncoder) AddDuration(key string, value time.Duration) {
panic("implement me")
}
func (*dummyEncoder) AddFloat32(key string, value float32) {
panic("implement me")
}
func (*dummyEncoder) AddFloat64(key string, value float64) {
panic("implement me")
}
func (*dummyEncoder) AddInt(key string, value int) {
panic("implement me")
}
func (*dummyEncoder) AddInt16(key string, value int16) {
panic("implement me")
}
func (*dummyEncoder) AddInt32(key string, value int32) {
panic("implement me")
}
func (*dummyEncoder) AddInt64(key string, value int64) {
panic("implement me")
}
func (*dummyEncoder) AddInt8(key string, value int8) {
panic("implement me")
}
func (*dummyEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
panic("implement me")
}
func (*dummyEncoder) AddReflected(key string, value interface{}) error {
panic("implement me")
}
func (*dummyEncoder) AddString(key, value string) {
panic("implement me")
}
func (*dummyEncoder) AddTime(key string, value time.Time) {
panic("implement me")
}
func (*dummyEncoder) AddUint(key string, value uint) {
panic("implement me")
}
func (*dummyEncoder) AddUint16(key string, value uint16) {
panic("implement me")
}
func (*dummyEncoder) AddUint32(key string, value uint32) {
panic("implement me")
}
func (*dummyEncoder) AddUint64(key string, value uint64) {
panic("implement me")
}
func (*dummyEncoder) AddUint8(key string, value uint8) {
panic("implement me")
}
func (*dummyEncoder) AddUintptr(key string, value uintptr) {
panic("implement me")
}
func (*dummyEncoder) Clone() zapcore.Encoder {
panic("implement me")
}
func (*dummyEncoder) EncodeEntry(_ zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
b := &buffer.Buffer{}
b.Write([]byte(fmt.Sprintf("%+v", fields)))
return b, nil
}
func (*dummyEncoder) OpenNamespace(key string) {
panic("implement me")
}
func TestEncoder_EncodeEntry(t *testing.T) {
e := Encoder{
Fields: []zapcore.Field{zap.String("version", "1.1")},
Encoder: &dummyEncoder{},
}
b, _ := e.EncodeEntry(zapcore.Entry{}, []zapcore.Field{zap.String("seq_id", "123")})
assert.Equal(t, `[{Key:version Type:15 Integer:0 String:1.1 Interface:<nil>} {Key:_seq_id Type:15 Integer:0 String:123 Interface:<nil>}]`, b.String())
}

52
encoder/json/json.go Normal file
View File

@ -0,0 +1,52 @@
package json
import (
"github.com/khorevaa/logos/appender"
ec "github.com/khorevaa/logos/encoder/common"
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap/zapcore"
)
var defaultConfig = ec.JsonEncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: "\n",
TimeEncoder: "ISO8601",
}
func init() {
appender.RegisterEncoderType("json", func(cfg *common.Config) (zapcore.Encoder, error) {
config := defaultConfig
if cfg != nil {
if err := cfg.Unpack(&config); err != nil {
return nil, err
}
}
encoderConfig := zapcore.EncoderConfig{
TimeKey: config.TimeKey,
LevelKey: config.LevelKey,
NameKey: config.NameKey,
CallerKey: config.CallerKey,
MessageKey: config.MessageKey,
StacktraceKey: config.StacktraceKey,
LineEnding: config.LineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
te, err := ec.GetTimeEncoder(config.TimeEncoder)
if err != nil {
return nil, err
}
encoderConfig.EncodeTime = te
return zapcore.NewJSONEncoder(encoderConfig), nil
})
}

15
example_config.yaml Normal file
View File

@ -0,0 +1,15 @@
appenders:
console:
- name: CONSOLE
target: stdout
encoder:
console:
loggers:
root:
level: info
appender_refs:
- CONSOLE
logger:
- name: stdlog
level: info
add_caller: true

16
example_test.go Normal file
View File

@ -0,0 +1,16 @@
package logos_test
import (
"github.com/khorevaa/logos"
)
func ExampleNew() {
log := logos.New("github.com/v8platform/test")
log.Info("Error")
log.Sync()
// Output:
// 2021-01-28T22:10:37.059+0300 INFO github.com/v8platform/test Error {"url": "url", "attempt": 3, "backoff": 1}
}

539
field.go Normal file
View File

@ -0,0 +1,539 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package logos
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"math"
"time"
)
// Field is an alias for Field. Aliasing this type dramatically
// improves the navigability of this package's API documentation.
type Field = zapcore.Field
var (
_minTimeInt64 = time.Unix(0, math.MinInt64)
_maxTimeInt64 = time.Unix(0, math.MaxInt64)
)
// Skip constructs a no-op field, which is often useful when handling invalid
// inputs in other Field constructors.
func Skip() Field {
return Field{Type: zapcore.SkipType}
}
// nilField returns a field which will marshal explicitly as nil. See motivation
// in https://github.com/uber-go/zap/issues/753 . If we ever make breaking
// changes and add zapcore.NilType and zapcore.ObjectEncoder.AddNil, the
// implementation here should be changed to reflect that.
func nilField(key string) Field { return Reflect(key, nil) }
// Binary constructs a field that carries an opaque binary blob.
//
// Binary data is serialized in an encoding-appropriate format. For example,
// zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text,
// use ByteString.
func Binary(key string, val []byte) Field {
return Field{Key: key, Type: zapcore.BinaryType, Interface: val}
}
// Bool constructs a field that carries a bool.
func Bool(key string, val bool) Field {
var ival int64
if val {
ival = 1
}
return Field{Key: key, Type: zapcore.BoolType, Integer: ival}
}
// Boolp constructs a field that carries a *bool. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Boolp(key string, val *bool) Field {
if val == nil {
return nilField(key)
}
return Bool(key, *val)
}
// ByteString constructs a field that carries UTF-8 encoded text as a []byte.
// To log opaque binary blobs (which aren't necessarily valid UTF-8), use
// Binary.
func ByteString(key string, val []byte) Field {
return Field{Key: key, Type: zapcore.ByteStringType, Interface: val}
}
// Complex128 constructs a field that carries a complex number. Unlike most
// numeric fields, this costs an allocation (to convert the complex128 to
// interface{}).
func Complex128(key string, val complex128) Field {
return Field{Key: key, Type: zapcore.Complex128Type, Interface: val}
}
// Complex128p constructs a field that carries a *complex128. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Complex128p(key string, val *complex128) Field {
if val == nil {
return nilField(key)
}
return Complex128(key, *val)
}
// Complex64 constructs a field that carries a complex number. Unlike most
// numeric fields, this costs an allocation (to convert the complex64 to
// interface{}).
func Complex64(key string, val complex64) Field {
return Field{Key: key, Type: zapcore.Complex64Type, Interface: val}
}
// Complex64p constructs a field that carries a *complex64. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Complex64p(key string, val *complex64) Field {
if val == nil {
return nilField(key)
}
return Complex64(key, *val)
}
// Float64 constructs a field that carries a float64. The way the
// floating-point value is represented is encoder-dependent, so marshaling is
// necessarily lazy.
func Float64(key string, val float64) Field {
return Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))}
}
// Float64p constructs a field that carries a *float64. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Float64p(key string, val *float64) Field {
if val == nil {
return nilField(key)
}
return Float64(key, *val)
}
// Float32 constructs a field that carries a float32. The way the
// floating-point value is represented is encoder-dependent, so marshaling is
// necessarily lazy.
func Float32(key string, val float32) Field {
return Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))}
}
// Float32p constructs a field that carries a *float32. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Float32p(key string, val *float32) Field {
if val == nil {
return nilField(key)
}
return Float32(key, *val)
}
// Int constructs a field with the given key and value.
func Int(key string, val int) Field {
return Int64(key, int64(val))
}
// Intp constructs a field that carries a *int. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Intp(key string, val *int) Field {
if val == nil {
return nilField(key)
}
return Int(key, *val)
}
// Int64 constructs a field with the given key and value.
func Int64(key string, val int64) Field {
return Field{Key: key, Type: zapcore.Int64Type, Integer: val}
}
// Int64p constructs a field that carries a *int64. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Int64p(key string, val *int64) Field {
if val == nil {
return nilField(key)
}
return Int64(key, *val)
}
// Int32 constructs a field with the given key and value.
func Int32(key string, val int32) Field {
return Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)}
}
// Int32p constructs a field that carries a *int32. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Int32p(key string, val *int32) Field {
if val == nil {
return nilField(key)
}
return Int32(key, *val)
}
// Int16 constructs a field with the given key and value.
func Int16(key string, val int16) Field {
return Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)}
}
// Int16p constructs a field that carries a *int16. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Int16p(key string, val *int16) Field {
if val == nil {
return nilField(key)
}
return Int16(key, *val)
}
// Int8 constructs a field with the given key and value.
func Int8(key string, val int8) Field {
return Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)}
}
// Int8p constructs a field that carries a *int8. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Int8p(key string, val *int8) Field {
if val == nil {
return nilField(key)
}
return Int8(key, *val)
}
// String constructs a field with the given key and value.
func String(key string, val string) Field {
return Field{Key: key, Type: zapcore.StringType, String: val}
}
// Stringp constructs a field that carries a *string. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Stringp(key string, val *string) Field {
if val == nil {
return nilField(key)
}
return String(key, *val)
}
// Uint constructs a field with the given key and value.
func Uint(key string, val uint) Field {
return Uint64(key, uint64(val))
}
// Uintp constructs a field that carries a *uint. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Uintp(key string, val *uint) Field {
if val == nil {
return nilField(key)
}
return Uint(key, *val)
}
// Uint64 constructs a field with the given key and value.
func Uint64(key string, val uint64) Field {
return Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)}
}
// Uint64p constructs a field that carries a *uint64. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Uint64p(key string, val *uint64) Field {
if val == nil {
return nilField(key)
}
return Uint64(key, *val)
}
// Uint32 constructs a field with the given key and value.
func Uint32(key string, val uint32) Field {
return Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)}
}
// Uint32p constructs a field that carries a *uint32. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Uint32p(key string, val *uint32) Field {
if val == nil {
return nilField(key)
}
return Uint32(key, *val)
}
// Uint16 constructs a field with the given key and value.
func Uint16(key string, val uint16) Field {
return Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)}
}
// Uint16p constructs a field that carries a *uint16. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Uint16p(key string, val *uint16) Field {
if val == nil {
return nilField(key)
}
return Uint16(key, *val)
}
// Uint8 constructs a field with the given key and value.
func Uint8(key string, val uint8) Field {
return Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)}
}
// Uint8p constructs a field that carries a *uint8. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Uint8p(key string, val *uint8) Field {
if val == nil {
return nilField(key)
}
return Uint8(key, *val)
}
// Uintptr constructs a field with the given key and value.
func Uintptr(key string, val uintptr) Field {
return Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)}
}
// Uintptrp constructs a field that carries a *uintptr. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Uintptrp(key string, val *uintptr) Field {
if val == nil {
return nilField(key)
}
return Uintptr(key, *val)
}
// Reflect constructs a field with the given key and an arbitrary object. It uses
// an encoding-appropriate, reflection-based function to lazily serialize nearly
// any object into the logging context, but it's relatively slow and
// allocation-heavy. Outside tests, Any is always a better choice.
//
// If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect
// includes the error message in the final log output.
func Reflect(key string, val interface{}) Field {
return Field{Key: key, Type: zapcore.ReflectType, Interface: val}
}
// Namespace creates a named, isolated scope within the logger's context. All
// subsequent fields will be added to the new namespace.
//
// This helps prevent key collisions when injecting loggers into sub-components
// or third-party libraries.
func Namespace(key string) Field {
return Field{Key: key, Type: zapcore.NamespaceType}
}
// Stringer constructs a field with the given key and the output of the value's
// String method. The Stringer's String method is called lazily.
func Stringer(key string, val fmt.Stringer) Field {
return Field{Key: key, Type: zapcore.StringerType, Interface: val}
}
// Time constructs a Field with the given key and value. The encoder
// controls how the time is serialized.
func Time(key string, val time.Time) Field {
if val.Before(_minTimeInt64) || val.After(_maxTimeInt64) {
return Field{Key: key, Type: zapcore.TimeFullType, Interface: val}
}
return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()}
}
// Timep constructs a field that carries a *time.Time. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Timep(key string, val *time.Time) Field {
if val == nil {
return nilField(key)
}
return Time(key, *val)
}
// Stack constructs a field that stores a stacktrace of the current goroutine
// under provided key. Keep in mind that taking a stacktrace is eager and
// expensive (relatively speaking); this function both makes an allocation and
// takes about two microseconds.
func Stack(key string) Field {
return zap.StackSkip(key, 1) // skip Stack
}
// StackSkip constructs a field similarly to Stack, but also skips the given
// number of frames from the top of the stacktrace.
func StackSkip(key string, skip int) Field {
// Returning the stacktrace as a string costs an allocation, but saves us
// from expanding the zapcore.Field union struct to include a byte slice. Since
// taking a stacktrace is already so expensive (~10us), the extra allocation
// is okay.
return zap.StackSkip(key, skip) // skip StackSkip
}
// Duration constructs a field with the given key and value. The encoder
// controls how the duration is serialized.
func Duration(key string, val time.Duration) Field {
return Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)}
}
// Durationp constructs a field that carries a *time.Duration. The returned Field will safely
// and explicitly represent `nil` when appropriate.
func Durationp(key string, val *time.Duration) Field {
if val == nil {
return nilField(key)
}
return Duration(key, *val)
}
// Object constructs a field with the given key and ObjectMarshaler. It
// provides a flexible, but still type-safe and efficient, way to add map- or
// struct-like user-defined types to the logging context. The struct's
// MarshalLogObject method is called lazily.
func Object(key string, val zapcore.ObjectMarshaler) Field {
return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val}
}
// Any takes a key and an arbitrary value and chooses the best way to represent
// them as a field, falling back to a reflection-based approach only if
// necessary.
//
// Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between
// them. To minimize surprises, []byte values are treated as binary blobs, byte
// values are treated as uint8, and runes are always treated as integers.
func Any(key string, value interface{}) Field {
switch val := value.(type) {
case zapcore.ObjectMarshaler:
return Object(key, val)
case zapcore.ArrayMarshaler:
return zap.Array(key, val)
case bool:
return Bool(key, val)
case *bool:
return Boolp(key, val)
case []bool:
return zap.Bools(key, val)
case complex128:
return Complex128(key, val)
case *complex128:
return Complex128p(key, val)
case []complex128:
return zap.Complex128s(key, val)
case complex64:
return Complex64(key, val)
case *complex64:
return Complex64p(key, val)
case []complex64:
return zap.Complex64s(key, val)
case float64:
return Float64(key, val)
case *float64:
return Float64p(key, val)
case []float64:
return zap.Float64s(key, val)
case float32:
return Float32(key, val)
case *float32:
return Float32p(key, val)
case []float32:
return zap.Float32s(key, val)
case int:
return Int(key, val)
case *int:
return Intp(key, val)
case []int:
return zap.Ints(key, val)
case int64:
return Int64(key, val)
case *int64:
return Int64p(key, val)
case []int64:
return zap.Int64s(key, val)
case int32:
return Int32(key, val)
case *int32:
return Int32p(key, val)
case []int32:
return zap.Int32s(key, val)
case int16:
return Int16(key, val)
case *int16:
return Int16p(key, val)
case []int16:
return zap.Int16s(key, val)
case int8:
return Int8(key, val)
case *int8:
return Int8p(key, val)
case []int8:
return zap.Int8s(key, val)
case string:
return String(key, val)
case *string:
return Stringp(key, val)
case []string:
return zap.Strings(key, val)
case uint:
return Uint(key, val)
case *uint:
return Uintp(key, val)
case []uint:
return zap.Uints(key, val)
case uint64:
return Uint64(key, val)
case *uint64:
return Uint64p(key, val)
case []uint64:
return zap.Uint64s(key, val)
case uint32:
return Uint32(key, val)
case *uint32:
return Uint32p(key, val)
case []uint32:
return zap.Uint32s(key, val)
case uint16:
return Uint16(key, val)
case *uint16:
return Uint16p(key, val)
case []uint16:
return zap.Uint16s(key, val)
case uint8:
return Uint8(key, val)
case *uint8:
return Uint8p(key, val)
case []byte:
return Binary(key, val)
case uintptr:
return Uintptr(key, val)
case *uintptr:
return Uintptrp(key, val)
case []uintptr:
return zap.Uintptrs(key, val)
case time.Time:
return Time(key, val)
case *time.Time:
return Timep(key, val)
case []time.Time:
return zap.Times(key, val)
case time.Duration:
return Duration(key, val)
case *time.Duration:
return Durationp(key, val)
case []time.Duration:
return zap.Durations(key, val)
case error:
return zap.NamedError(key, val)
case []error:
return zap.Errors(key, val)
case fmt.Stringer:
return Stringer(key, val)
default:
return Reflect(key, val)
}
}

12
go.mod Normal file
View File

@ -0,0 +1,12 @@
module github.com/khorevaa/logos
go 1.15
require (
github.com/elastic/go-ucfg v0.8.3
github.com/mattn/go-colorable v0.1.8
github.com/stretchr/testify v1.6.1
go.uber.org/zap v1.16.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)

69
go.sum Normal file
View File

@ -0,0 +1,69 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/elastic/go-ucfg v0.8.3 h1:leywnFjzr2QneZZWhE6uWd+QN/UpP0sdJRHYyuFvkeo=
github.com/elastic/go-ucfg v0.8.3/go.mod h1:iaiY0NBIYeasNgycLyTvhJftQlQEUO2hpF+FX0JKxzo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
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/sync v0.0.0-20190423024810-112230192c58/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

260
internal/common/config.go Normal file
View File

@ -0,0 +1,260 @@
package common
import (
"crypto/md5"
"errors"
"io/ioutil"
"github.com/elastic/go-ucfg"
"github.com/elastic/go-ucfg/yaml"
)
// Config object to store hierarchical configurations into.
// See https://godoc.org/github.com/elastic/go-ucfg#Config
type Config ucfg.Config
// ConfigNamespace storing at most one configuration section by name and sub-section.
type ConfigNamespace struct {
name string
config *Config
}
var configOpts = []ucfg.Option{
ucfg.PathSep("."),
ucfg.ResolveEnv,
ucfg.VarExp,
ucfg.StructTag("logos-config"),
ucfg.ValidatorTag("logos-validate"),
ucfg.ReplaceValues,
}
func NewConfig() *Config {
return fromConfig(ucfg.New())
}
// NewConfigFrom creates a new Config object from the given input.
// From can be any kind of structured data (struct, map, array, slice).
//
// If from is a string, the contents is treated like raw YAML input. The string
// will be parsed and a structure config object is build from the parsed
// result.
func NewConfigFrom(from interface{}) (*Config, error) {
if str, ok := from.(string); ok {
c, err := yaml.NewConfig([]byte(str), configOpts...)
return fromConfig(c), err
}
c, err := ucfg.NewFrom(from, configOpts...)
return fromConfig(c), err
}
// MustNewConfigFrom creates a new Config object from the given input.
// From can be any kind of structured data (struct, map, array, slice).
//
// If from is a string, the contents is treated like raw YAML input. The string
// will be parsed and a structure config object is build from the parsed
// result.
//
// MustNewConfigFrom panics if an error occurs.
func MustNewConfigFrom(from interface{}) *Config {
cfg, err := NewConfigFrom(from)
if err != nil {
panic(err)
}
return cfg
}
func MergeConfigs(cfgs ...*Config) (*Config, error) {
config := NewConfig()
for _, c := range cfgs {
if err := config.Merge(c); err != nil {
return nil, err
}
}
return config, nil
}
func NewConfigWithYAML(in []byte, source string) (*Config, error) {
opts := append(
[]ucfg.Option{
ucfg.MetaData(ucfg.Meta{Source: source}),
},
configOpts...,
)
c, err := yaml.NewConfig(in, opts...)
return fromConfig(c), err
}
// OverwriteConfigOpts allow to change the globally set config option
func OverwriteConfigOpts(options []ucfg.Option) {
configOpts = options
}
func LoadFile(path string) (*Config, [md5.Size]byte, error) {
bs, err := ioutil.ReadFile(path)
if err != nil {
return nil, [md5.Size]byte{}, err
}
hash := md5.Sum(bs)
c, err := yaml.NewConfig(bs, configOpts...)
if err != nil {
return nil, hash, err
}
cfg := fromConfig(c)
return cfg, hash, err
}
func (c *Config) Merge(from interface{}) error {
return c.access().Merge(from, configOpts...)
}
func (c *Config) Unpack(to interface{}) error {
return c.access().Unpack(to, configOpts...)
}
func (c *Config) Path() string {
return c.access().Path(".")
}
func (c *Config) PathOf(field string) string {
return c.access().PathOf(field, ".")
}
func (c *Config) HasField(name string) bool {
return c.access().HasField(name)
}
func (c *Config) CountField(name string) (int, error) {
return c.access().CountField(name)
}
func (c *Config) Name() (string, error) {
return c.access().String("name", -1)
}
func (c *Config) MustName(fallback ...string) string {
name, err := c.Name()
if err != nil {
if len(fallback) > 0 {
return fallback[0]
}
return ""
}
return name
}
func (c *Config) Bool(name string, idx int) (bool, error) {
return c.access().Bool(name, idx, configOpts...)
}
func (c *Config) String(name string, idx int) (string, error) {
return c.access().String(name, idx, configOpts...)
}
func (c *Config) Int(name string, idx int) (int64, error) {
return c.access().Int(name, idx, configOpts...)
}
func (c *Config) Float(name string, idx int) (float64, error) {
return c.access().Float(name, idx, configOpts...)
}
func (c *Config) Child(name string, idx int) (*Config, error) {
sub, err := c.access().Child(name, idx, configOpts...)
return fromConfig(sub), err
}
func (c *Config) SetBool(name string, idx int, value bool) error {
return c.access().SetBool(name, idx, value, configOpts...)
}
func (c *Config) SetInt(name string, idx int, value int64) error {
return c.access().SetInt(name, idx, value, configOpts...)
}
func (c *Config) SetFloat(name string, idx int, value float64) error {
return c.access().SetFloat(name, idx, value, configOpts...)
}
func (c *Config) SetString(name string, idx int, value string) error {
return c.access().SetString(name, idx, value, configOpts...)
}
func (c *Config) SetChild(name string, idx int, value *Config) error {
return c.access().SetChild(name, idx, value.access(), configOpts...)
}
func (c *Config) IsDict() bool {
return c.access().IsDict()
}
func (c *Config) IsArray() bool {
return c.access().IsArray()
}
func fromConfig(in *ucfg.Config) *Config {
return (*Config)(in)
}
func (c *Config) access() *ucfg.Config {
return (*ucfg.Config)(c)
}
func (c *Config) GetFields() []string {
return c.access().GetFields()
}
// Unpack unpacks a configuration with at most one sub object. An sub object is
// ignored if it is disabled by setting `enabled: false`. If the configuration
// passed contains multiple active sub objects, Unpack will return an error.
func (ns *ConfigNamespace) Unpack(cfg *Config) error {
fields := cfg.GetFields()
if len(fields) == 0 {
return nil
}
var (
err error
found bool
)
for _, name := range fields {
var sub *Config
sub, err = cfg.Child(name, -1)
if err != nil {
// element is no configuration object -> continue so a namespace
// Config unpacked as a namespace can have other configuration
// values as well
continue
}
if ns.name != "" {
return errors.New("more than one namespace configured")
}
ns.name = name
ns.config = sub
found = true
}
if !found {
return err
}
return nil
}
// Name returns the configuration sections it's name if a section has been set.
func (ns *ConfigNamespace) Name() string {
return ns.name
}
// Config return the sub-configuration section if a section has been set.
func (ns *ConfigNamespace) Config() *Config {
return ns.config
}
// IsSet returns true if a sub-configuration section has been set.
func (ns *ConfigNamespace) IsSet() bool {
return ns.config != nil
}

View File

@ -0,0 +1,52 @@
package common
import "strconv"
type FloatSet map[float64]struct{}
func MakeFloatSetFromStrings(strings ...string) (FloatSet, error) {
if len(strings) == 0 {
return nil, nil
}
set := FloatSet{}
for _, str := range strings {
f, err := strconv.ParseFloat(str, 64)
if err != nil {
return nil, err
}
set[f] = struct{}{}
}
return set, nil
}
func MakeFloatSet(floats ...float64) FloatSet {
if len(floats) == 0 {
return nil
}
set := FloatSet{}
for _, f := range floats {
set[f] = struct{}{}
}
return set
}
func (set FloatSet) Add(f float64) {
set[f] = struct{}{}
}
func (set FloatSet) Del(f float64) {
delete(set, f)
}
func (set FloatSet) Count() int {
return len(set)
}
func (set FloatSet) Has(f float64) (exists bool) {
if set != nil {
_, exists = set[f]
}
return
}

52
internal/common/intset.go Normal file
View File

@ -0,0 +1,52 @@
package common
import "strconv"
type IntSet map[int]struct{}
func MakeIntSetFromStrings(strings ...string) (IntSet, error) {
if len(strings) == 0 {
return nil, nil
}
set := IntSet{}
for _, str := range strings {
i, err := strconv.ParseInt(str, 0, 64)
if err != nil {
return nil, err
}
set[int(i)] = struct{}{}
}
return set, nil
}
func MakeIntSet(ints ...int) IntSet {
if len(ints) == 0 {
return nil
}
set := IntSet{}
for _, i := range ints {
set[i] = struct{}{}
}
return set
}
func (set IntSet) Add(i int) {
set[i] = struct{}{}
}
func (set IntSet) Del(i int) {
delete(set, i)
}
func (set IntSet) Count() int {
return len(set)
}
func (set IntSet) Has(s int) (exists bool) {
if set != nil {
_, exists = set[s]
}
return
}

View File

@ -0,0 +1,45 @@
package common
type StringSet map[string]struct{}
func MakeStringSet(strings ...string) StringSet {
if len(strings) == 0 {
return nil
}
set := StringSet{}
for _, str := range strings {
set[str] = struct{}{}
}
return set
}
func (set StringSet) Add(s string) {
set[s] = struct{}{}
}
func (set StringSet) Del(s string) {
delete(set, s)
}
func (set StringSet) Count() int {
return len(set)
}
func (set StringSet) Has(s string) (exists bool) {
if set != nil {
_, exists = set[s]
}
return
}
func (set StringSet) ToSlice() []string {
if set == nil {
return nil
}
ss := make([]string, 0)
for s := range set {
ss = append(ss, s)
}
return ss
}

View File

@ -0,0 +1,52 @@
package common
import "strconv"
type UintSet map[uint]struct{}
func MakeUintSetFromStrings(strings ...string) (UintSet, error) {
if len(strings) == 0 {
return nil, nil
}
set := UintSet{}
for _, str := range strings {
i, err := strconv.ParseUint(str, 0, 64)
if err != nil {
return nil, err
}
set[uint(i)] = struct{}{}
}
return set, nil
}
func MakeUintSet(uints ...uint) UintSet {
if len(uints) == 0 {
return nil
}
set := UintSet{}
for _, i := range uints {
set[i] = struct{}{}
}
return set
}
func (set UintSet) Add(i uint) {
set[i] = struct{}{}
}
func (set UintSet) Del(i uint) {
delete(set, i)
}
func (set UintSet) Count() int {
return len(set)
}
func (set UintSet) Has(s uint) (exists bool) {
if set != nil {
_, exists = set[s]
}
return
}

View File

@ -0,0 +1,50 @@
package common
import (
"fmt"
"github.com/elastic/go-ucfg"
"reflect"
"strings"
)
func init() {
if err := ucfg.RegisterValidator("logos.oneof", func(v interface{}, params string) error {
if v == nil {
return nil
}
val := reflect.ValueOf(v)
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
intSet, err := MakeIntSetFromStrings(strings.Split(params, " ")...)
if err != nil {
return err
}
if intSet.Has(int(val.Int())) {
return nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
uintSet, err := MakeUintSetFromStrings(strings.Split(params, " ")...)
if err != nil {
return err
}
if uintSet.Has(uint(val.Uint())) {
return nil
}
case reflect.Float32, reflect.Float64:
floatSet, err := MakeFloatSetFromStrings(strings.Split(params, " ")...)
if err != nil {
return err
}
if floatSet.Has(val.Float()) {
return nil
}
case reflect.String:
if MakeStringSet(strings.Split(params, " ")...).Has(val.String()) {
return nil
}
}
return fmt.Errorf("requires value one of %q", params)
}); err != nil {
fmt.Println(err)
}
}

29
level.go Normal file
View File

@ -0,0 +1,29 @@
package logos
import (
"go.uber.org/zap/zapcore"
)
const (
// OffLevel
OffLevel = zapcore.Level(-2)
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DebugLevel = zapcore.DebugLevel
// InfoLevel is the default logging priority.
InfoLevel = zapcore.InfoLevel
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WarnLevel = zapcore.WarnLevel
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel = zapcore.ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
DPanicLevel = zapcore.DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel = zapcore.PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel = zapcore.FatalLevel
)

214
logger.go Normal file
View File

@ -0,0 +1,214 @@
package logos
import (
"go.uber.org/zap"
"sync"
"sync/atomic"
"time"
)
var _ Logger = (*warpLogger)(nil)
var _ SugaredLogger = (*warpLogger)(nil)
func newLogger(name string, logger *zap.Logger) *warpLogger {
l := &warpLogger{
Name: name,
defLogger: logger,
mu: sync.RWMutex{},
}
l.SetUsedAt(time.Now())
return l
}
type warpLogger struct {
Name string
defLogger *zap.Logger
sugaredLogger *zap.SugaredLogger
mu sync.RWMutex
_usedAt uint32 // atomic
_locked uint32
_lockWait sync.WaitGroup
}
func (l *warpLogger) Sugar() SugaredLogger {
l.initSugaredLogger()
return l
}
func (l *warpLogger) Debug(msg string, fields ...Field) {
l.checkLock()
l.defLogger.Debug(msg, fields...)
}
func (l *warpLogger) Info(msg string, fields ...Field) {
l.checkLock()
l.defLogger.Info(msg, fields...)
}
func (l *warpLogger) Warn(msg string, fields ...Field) {
l.checkLock()
l.defLogger.Warn(msg, fields...)
}
func (l *warpLogger) Error(msg string, fields ...Field) {
l.checkLock()
l.defLogger.Error(msg, fields...)
}
func (l *warpLogger) Fatal(msg string, fields ...Field) {
l.checkLock()
l.defLogger.Fatal(msg, fields...)
}
func (l *warpLogger) Panic(msg string, fields ...Field) {
l.checkLock()
l.defLogger.Panic(msg, fields...)
}
func (l *warpLogger) DPanic(msg string, fields ...Field) {
l.checkLock()
l.defLogger.DPanic(msg, fields...)
}
func (l *warpLogger) Debugf(format string, args ...interface{}) {
l.checkLock()
l.sugaredLogger.Debugf(format, args...)
}
func (l *warpLogger) Infof(format string, args ...interface{}) {
l.checkLock()
l.sugaredLogger.Infof(format, args...)
}
func (l *warpLogger) Warnf(format string, args ...interface{}) {
l.checkLock()
l.sugaredLogger.Warnf(format, args...)
}
func (l *warpLogger) Errorf(format string, args ...interface{}) {
l.checkLock()
l.sugaredLogger.Errorf(format, args...)
}
func (l *warpLogger) Fatalf(format string, args ...interface{}) {
l.checkLock()
l.sugaredLogger.Fatalf(format, args...)
}
func (l *warpLogger) Panicf(format string, args ...interface{}) {
l.checkLock()
l.sugaredLogger.Panicf(format, args...)
}
func (l *warpLogger) DPanicf(format string, args ...interface{}) {
l.checkLock()
l.sugaredLogger.DPanicf(format, args...)
}
func (l *warpLogger) Debugw(msg string, keysAndValues ...interface{}) {
l.checkLock()
l.sugaredLogger.Debugw(msg, keysAndValues...)
}
func (l *warpLogger) Infow(msg string, keysAndValues ...interface{}) {
l.checkLock()
l.sugaredLogger.Infow(msg, keysAndValues...)
}
func (l *warpLogger) Warnw(msg string, keysAndValues ...interface{}) {
l.checkLock()
l.sugaredLogger.Warnw(msg, keysAndValues...)
}
func (l *warpLogger) Errorw(msg string, keysAndValues ...interface{}) {
l.checkLock()
l.sugaredLogger.Errorw(msg, keysAndValues...)
}
func (l *warpLogger) Fatalw(msg string, keysAndValues ...interface{}) {
l.checkLock()
l.sugaredLogger.Fatalw(msg, keysAndValues...)
}
func (l *warpLogger) Panicw(msg string, keysAndValues ...interface{}) {
l.checkLock()
l.sugaredLogger.Panicw(msg, keysAndValues...)
}
func (l *warpLogger) DPanicw(msg string, keysAndValues ...interface{}) {
l.checkLock()
l.sugaredLogger.DPanicw(msg, keysAndValues...)
}
func (l *warpLogger) Sync() error {
return l.defLogger.Sync()
}
func (l *warpLogger) Desugar() Logger {
return l
}
func (l *warpLogger) UsedAt() time.Time {
unix := atomic.LoadUint32(&l._usedAt)
return time.Unix(int64(unix), 0)
}
func (l *warpLogger) SetUsedAt(tm time.Time) {
atomic.StoreUint32(&l._usedAt, uint32(tm.Unix()))
}
func (l *warpLogger) initSugaredLogger() {
if l.sugaredLogger == nil {
l.sugaredLogger = l.defLogger.Sugar()
}
}
func (l *warpLogger) updateLogger(logger *zap.Logger) {
l.lock()
defer l.unlock()
_ = l.Sync()
l.defLogger = logger
if l.sugaredLogger != nil {
l.sugaredLogger = l.defLogger.Sugar()
}
}
func (l *warpLogger) lock() {
l.mu.Lock()
atomic.StoreUint32(&l._locked, 1)
l._lockWait.Add(1)
}
func (l *warpLogger) unlock() {
atomic.StoreUint32(&l._locked, 0)
l._lockWait.Done()
l.mu.Unlock()
}
func (l *warpLogger) checkLock() {
if l.locked() {
l._lockWait.Wait()
}
}
func (l *warpLogger) locked() bool {
return atomic.LoadUint32(&l._locked) == 1
}

79
loggerConfig.go Normal file
View File

@ -0,0 +1,79 @@
package logos
import (
"github.com/khorevaa/logos/appender"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type loggerConfig struct {
Name string
// Global core config
Level zapcore.Level
AddCaller bool
AddStacktrace zapcore.LevelEnabler
Parent *loggerConfig
coreConfigs map[string]zapcore.Level
}
func (l *loggerConfig) CreateLogger(appenders map[string]*appender.Appender) *warpLogger {
if l.Level == OffLevel {
return newLogger(l.Name, newZapLogger(l.Name, zapcore.NewNopCore()))
}
zc := newZapCore(l.coreConfigs, appenders)
zl := newZapLogger(l.Name, zc, zap.WithCaller(l.AddCaller), zap.AddStacktrace(l.AddStacktrace), zap.AddCallerSkip(1))
return newLogger(l.Name, zl)
}
func (l *loggerConfig) UpdateLogger(logger *warpLogger, appenders map[string]*appender.Appender) {
if l.Level == OffLevel {
logger.updateLogger(zap.NewNop())
}
zc := newZapCore(l.coreConfigs, appenders)
newLogger := zap.New(zc, zap.WithCaller(l.AddCaller), zap.AddStacktrace(l.AddStacktrace), zap.AddCallerSkip(1))
if len(l.Name) > 0 {
newLogger = newLogger.Named(l.Name)
}
logger.updateLogger(newLogger)
}
func (l *loggerConfig) copy(name string) *loggerConfig {
log := &loggerConfig{
Name: name,
Level: l.Level,
Parent: l.Parent,
coreConfigs: make(map[string]zapcore.Level),
}
copyMapConfig(log.coreConfigs, l.coreConfigs)
return log
}
func copyMapConfig(dst map[string]zapcore.Level, src map[string]zapcore.Level) {
if len(src) == 0 {
return
}
if dst == nil {
dst = make(map[string]zapcore.Level, len(src))
}
for name, level := range src {
dst[name] = level
}
}

164
logos.go Normal file
View File

@ -0,0 +1,164 @@
package logos
import (
"errors"
"fmt"
"github.com/khorevaa/logos/config"
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"strconv"
"sync"
"syscall"
_ "github.com/khorevaa/logos/appender"
_ "github.com/khorevaa/logos/encoder/common"
_ "github.com/khorevaa/logos/encoder/console"
_ "github.com/khorevaa/logos/encoder/gelf"
_ "github.com/khorevaa/logos/encoder/json"
)
var (
manager *logManager
configFile string
initLocker sync.Mutex
explicitInited = false
debug bool
)
func resolveConfigFileFromEnv() (string, error) {
f := os.Getenv("LOGOS_CONFIG_FILE")
if f == "" {
return "", errors.New("environment variable 'LOGOS_CONFIG_FILE' is not set")
}
return f, nil
}
func resolveConfigFileFromWorkDir() (string, error) {
matches1, _ := filepath.Glob("logos.yaml")
matches2, _ := filepath.Glob("logos.yml")
matches := append(matches1, matches2...)
switch len(matches) {
case 0:
return "", errors.New("no config file found in work dir")
case 1:
return matches[0], nil
default:
panic(fmt.Errorf("multiple config files found %v", matches))
}
}
func init() {
initLocker.Lock()
defer initLocker.Unlock()
debug, _ = strconv.ParseBool(os.Getenv("LOGOS_DEBUG"))
if configFile == "" {
cf, err := resolveConfigFileFromEnv()
if err == nil {
configFile = cf
}
}
if configFile == "" {
cf, err := resolveConfigFileFromWorkDir()
if err == nil {
configFile = cf
}
}
var err error
var rawConfig *common.Config
if configFile != "" {
// load ConfigFile
configFile, err = filepath.Abs(configFile)
if err != nil {
panic(err)
}
if debug {
fmt.Println("logos using config file: ", configFile)
bs, err := ioutil.ReadFile(configFile)
if err != nil {
panic(err)
}
fmt.Println(string(bs))
}
rawConfig, _, err = common.LoadFile(configFile)
} else {
if debug {
fmt.Print("logos using default config:\n" + config.DefaultConfig)
}
rawConfig, err = common.NewConfigFrom(config.DefaultConfig)
}
if err != nil {
panic(err)
}
manager, err = newLogManager(rawConfig)
if err != nil {
panic(err)
}
manager.RedirectStdLog()
go func() {
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
<-quit
Sync()
}()
}
func InitWithConfigContent(content string) error {
initLocker.Lock()
defer initLocker.Unlock()
if explicitInited {
return errors.New("logos is explicit inited")
}
if debug {
fmt.Println("logos InitWithConfigContent:\n" + content)
}
rawConfig, err := common.NewConfigFrom(content)
if err != nil {
return err
}
err = manager.Update(rawConfig)
if err != nil {
return err
}
explicitInited = true
return nil
}
func New(name string) Logger {
return manager.NewLogger(name)
}
func SetLevel(name string, level zapcore.Level) {
manager.SetLevel(name, level)
}
func UpdateLogger(name string, logger *zap.Logger) {
manager.UpdateLogger(name, logger)
}
func Sync() {
_ = manager.Sync()
}

100
logos_test.go Normal file
View File

@ -0,0 +1,100 @@
package logos
import (
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
stdlog "log"
"testing"
)
func TestNew(t *testing.T) {
tests := []struct {
name string
logName string
text []string
}{
{
"simple",
"github.com/khorevaa/logos",
[]string{"hello world", "hello"},
},
{
"simple",
"github.com/v8platform",
[]string{"hello world", "hello"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
log := New(tt.logName)
SetLevel(tt.logName, zap.DebugLevel)
for _, text := range tt.text {
stdlog.Println(text)
log.Info(text)
log.Debug(text)
}
})
}
}
func TestInitWithConfigContent(t *testing.T) {
const newConfig = `
appenders:
console:
- name: CONSOLE
target: stdout
#no_color: true
encoder:
console:
color_scheme:
#debug_level: "Black,yellow"
rolling_file:
- name: ROLL_FILE
file_name: ./logs/log.log
max_size: 100
encoder:
json:
loggerConfigs:
root:
level: info
appender_refs:
- CONSOLE
logger:
- name: github.com
level: debug
add_caller: true
trace_level: error
appender_refs:
- ROLL_FILE
appenders:
- name: CONSOLE
level: debug
`
l := New("github.com/logger")
l.Info("hello")
l.Debug("world")
l2 := New("github.com/logger/v1")
l2.Info("hello world test/logger/v1", zap.String("key", "val"))
l2.Debug("hello world test/logger/v1")
err := InitWithConfigContent(newConfig)
//l.SetLLevel(OffLevel)
assert.Nil(t, err)
//err = InitWithConfigContent(newConfig)
//assert.NotNil(t, err)
l.Debug("hello world")
l2.Debug("hello world test/logger/v1")
l2.Error("hello world test/logger/v1", zap.Any("interface", []interface{}{1, "2", true}))
l2.DPanic("hello world test/logger/v1", zap.Any("ints", []int{1, 2, 3321}))
l2.Warn("hello world test/logger/v1", zap.Int("key", 123), zap.Bool("bool", false), zap.Any("bools", []bool{false, true, true}))
}

382
manager.go Normal file
View File

@ -0,0 +1,382 @@
package logos
import (
"fmt"
"github.com/khorevaa/logos/appender"
config2 "github.com/khorevaa/logos/config"
"github.com/khorevaa/logos/internal/common"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"sync"
)
const (
rootLoggerName = "root"
packageSepBySlash = '/'
)
type logManager struct {
getLoggerLocker sync.RWMutex
loggerConfigs sync.Map
coreLoggers sync.Map //
appenders map[string]*appender.Appender
rootLevel zapcore.Level
rootLogger *warpLogger
rootLoggerConfig *loggerConfig
}
func newLogManager(rawConfig *common.Config) (*logManager, error) {
config := config2.Config{}
err := rawConfig.Unpack(&config)
if err != nil {
return nil, err
}
m := logManager{
loggerConfigs: sync.Map{},
coreLoggers: sync.Map{},
appenders: map[string]*appender.Appender{},
}
for appenderType, appenderConfigs := range config.Appenders {
for _, appenderConfig := range appenderConfigs {
createAppender, err := appender.CreateAppender(appenderType, appenderConfig)
if err != nil {
return nil, err
}
name, err := appenderConfig.Name()
if err != nil {
return nil, err
}
m.appenders[name] = createAppender
}
}
err = m.newRootLoggerFromCfg(config.Loggers.Root)
if err != nil {
return nil, err
}
// loggerConfigs
for _, lc := range config.Loggers.Logger {
l, err := m.newLoggerFromCfg(lc)
if err != nil {
return nil, err
}
m.loggerConfigs.Store(lc.Name, l)
//if _, loaded := m.loggerConfigs.LoadOrStore(lc.Name, l); loaded {
// return nil, fmt.Errorf("duplicated logger %q", lc.Name)
//}
}
return &m, nil
}
func (m *logManager) NewLogger(name string) Logger {
return m.getLogger(name)
}
func (m *logManager) SetLevel(name string, level zapcore.Level) {
// TODO Чтото сделать
//return m.getLogger(name)
}
func (m *logManager) getLogger(name string, lock ...bool) *warpLogger {
if len(name) == 0 {
return m.rootLogger
}
if len(lock) > 0 && lock[0] || len(lock) == 0 {
m.getLoggerLocker.Lock()
defer m.getLoggerLocker.Unlock()
}
if core, ok := m.coreLoggers.Load(name); ok {
return core.(*warpLogger)
}
if cfg, ok := m.loggerConfigs.Load(name); ok {
logConfig := cfg.(*loggerConfig)
core := logConfig.CreateLogger(m.appenders)
m.coreLoggers.Store(logConfig.Name, core)
return core
}
logConfig := m.newCoreLoggerConfig(name)
m.loggerConfigs.Store(name, logConfig)
core := logConfig.CreateLogger(m.appenders)
m.coreLoggers.Store(logConfig.Name, core)
return core
}
func (m *logManager) getParent(name string) *loggerConfig {
parent := m.getRootLoggerConfig()
for i, c := range name {
// Search for package separator character
if c == packageSepBySlash {
parentName := name[0:i]
if parentName != "" {
parent = m.loadCoreLoggerConfig(parentName, parent)
}
}
}
return parent
}
func (m *logManager) getRootLoggerConfig() *loggerConfig {
if m.rootLoggerConfig != nil {
return m.rootLoggerConfig
}
name := rootLoggerName
if log, ok := m.loggerConfigs.Load(name); ok {
return log.(*loggerConfig)
}
log := &loggerConfig{
Name: name,
Level: m.rootLevel,
coreConfigs: make(map[string]zapcore.Level),
AddStacktrace: StackTraceLevelEnabler,
}
log.coreConfigs["CONSOLE"] = InfoLevel
m.rootLoggerConfig = log
m.loggerConfigs.Store(name, log)
m.rootLogger = log.CreateLogger(m.appenders)
return m.rootLoggerConfig
}
func (m *logManager) newLoggerFromCfg(loggerCfg config2.LoggerConfig) (*loggerConfig, error) {
name := loggerCfg.Name
levelName := loggerCfg.Level
appenderConfigs := loggerCfg.AppenderConfig
appenders := loggerCfg.AppenderRefs
level, err := createLevel(levelName)
if err != nil {
return nil, err
}
log := m.newCoreLoggerConfig(name)
log.Level = level
if len(appenders) > 0 {
log.coreConfigs = make(map[string]zapcore.Level, len(appenders))
for _, appenderName := range appenders {
log.coreConfigs[appenderName] = level
}
}
for _, appenderConfig := range appenderConfigs {
if len(appenderConfig.Level) > 0 {
appenderLevel, err := createLevel(appenderConfig.Level)
if err != nil {
debugf("creating appender level <%s> error: %s", appenderConfig.Level, err)
continue
}
log.coreConfigs[appenderConfig.Name] = appenderLevel
}
}
log.AddCaller = loggerCfg.AddCaller
log.AddStacktrace = StackTraceLevelEnabler
if tLevel, err := createLevel(loggerCfg.TraceLevel); len(loggerCfg.TraceLevel) > 0 && err == nil {
log.AddStacktrace = zap.NewAtomicLevelAt(tLevel)
}
return log, nil
}
func debugf(format string, args ...interface{}) {
if debug {
fmt.Printf(format, args...)
}
}
func (m *logManager) loadCoreLoggerConfig(name string, parent *loggerConfig) *loggerConfig {
if log, ok := m.loggerConfigs.Load(name); ok {
return log.(*loggerConfig)
}
if parent == nil {
parent = m.rootLoggerConfig
}
log := &loggerConfig{
Name: name,
Parent: parent,
AddStacktrace: parent.AddStacktrace,
AddCaller: parent.AddCaller,
coreConfigs: make(map[string]zapcore.Level),
}
copyMapConfig(log.coreConfigs, parent.coreConfigs)
m.loggerConfigs.Store(name, log)
return log
}
func (m *logManager) newCoreLoggerConfig(name string) *loggerConfig {
parent := m.getParent(name)
loggerConfig := m.loadCoreLoggerConfig(name, parent)
return loggerConfig
}
func (m *logManager) RedirectStdLog() {
stdlog := m.getLogger("stdlog", false)
zap.RedirectStdLog(stdlog.defLogger)
}
func (m *logManager) Update(rawConfig *common.Config) error {
nc, err := newLogManager(rawConfig)
if err != nil {
return err
}
m.getLoggerLocker.Lock()
defer m.getLoggerLocker.Unlock()
err = m.Sync()
if err != nil {
return err
}
m.appenders = nc.appenders
m.rootLevel = nc.rootLevel
m.rootLoggerConfig = nc.rootLoggerConfig
m.rootLoggerConfig.UpdateLogger(m.rootLogger, m.appenders)
m.loggerConfigs.Range(func(key, value interface{}) bool {
name := key.(string)
newLog := nc.newCoreLoggerConfig(name)
ref := value.(*loggerConfig)
*ref = *newLog
return true
})
nc.loggerConfigs.Range(func(key, value interface{}) bool {
if _, found := m.loggerConfigs.Load(key); found {
return true
}
m.loggerConfigs.Store(key, value)
return true
})
m.coreLoggers.Range(func(key, value interface{}) bool {
newCore := m.newCoreLoggerConfig(key.(string))
newCore.UpdateLogger(value.(*warpLogger), m.appenders)
return true
})
m.RedirectStdLog()
return nil
}
func (m *logManager) Sync() error {
m.coreLoggers.Range(func(_, value interface{}) bool {
_ = value.(*warpLogger).Sync()
return true
})
for _, a := range m.appenders {
_ = a.Writer.Sync()
}
return nil
}
func (m *logManager) newRootLoggerFromCfg(root config2.RootLogger) error {
levelName := root.Level
appenderConfigs := root.AppenderConfig
appenders := root.AppenderRefs
level, err := createLevel(levelName)
if err != nil {
return err
}
m.rootLevel = level
rootLoggerConfig := &loggerConfig{
Name: rootLoggerName,
Level: m.rootLevel,
coreConfigs: make(map[string]zapcore.Level),
AddStacktrace: StackTraceLevelEnabler,
}
for _, appenderName := range appenders {
rootLoggerConfig.coreConfigs[appenderName] = level
}
for _, appenderConfig := range appenderConfigs {
if len(appenderConfig.Level) > 0 {
appenderLevel, err := createLevel(appenderConfig.Level)
if err != nil {
debugf("creating appender level <%s> error: %s", appenderConfig.Level, err)
continue
}
rootLoggerConfig.coreConfigs[appenderConfig.Name] = appenderLevel
}
}
m.rootLoggerConfig = rootLoggerConfig
m.loggerConfigs.Store(rootLoggerName, m.rootLoggerConfig)
m.rootLogger = m.rootLoggerConfig.CreateLogger(m.appenders)
m.coreLoggers.Store(rootLoggerName, m.rootLogger)
return nil
}
func (m *logManager) UpdateLogger(name string, logger *zap.Logger) {
core := m.getLogger(name, false)
core.updateLogger(logger)
}
func createLevel(level string) (zapcore.Level, error) {
switch level {
case "off", "OFF", "false":
return OffLevel, nil
default:
var l zapcore.Level
err := l.UnmarshalText([]byte(level))
return l, err
}
}

123
types.go Normal file
View File

@ -0,0 +1,123 @@
package logos
type Logger interface {
// Debug uses fmt.Sprint to construct and log a message.
Debug(msg string, fields ...Field)
// Info uses fmt.Sprint to construct and log a message.
Info(msg string, fields ...Field)
// Warn uses fmt.Sprint to construct and log a message.
Warn(msg string, fields ...Field)
// Error uses fmt.Sprint to construct and log a message.
Error(msg string, fields ...Field)
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit(1).
Fatal(msg string, fields ...Field)
// Panic uses fmt.Sprint to construct and log a message, then panics.
Panic(msg string, fields ...Field)
// DPanic uses fmt.Sprint to construct and log a message. In development, the
// logger then panics.
DPanic(msg string, fields ...Field)
Sync() error
Sugar() SugaredLogger
}
type SugaredLogger interface {
// Debug uses fmt.Sprint to construct and log a message.
Debug(msg string, fields ...Field)
// Info uses fmt.Sprint to construct and log a message.
Info(msg string, fields ...Field)
// Warn uses fmt.Sprint to construct and log a message.
Warn(msg string, fields ...Field)
// Error uses fmt.Sprint to construct and log a message.
Error(msg string, fields ...Field)
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit(1).
Fatal(msg string, fields ...Field)
// Panic uses fmt.Sprint to construct and log a message, then panics.
Panic(msg string, fields ...Field)
// DPanic uses fmt.Sprint to construct and log a message. In development, the
// logger then panics.
DPanic(msg string, fields ...Field)
// Debugf uses fmt.Sprintf to construct and log a message.
Debugf(format string, args ...interface{})
// Infof uses fmt.Sprintf to log a templated message.
Infof(format string, args ...interface{})
// Warnf uses fmt.Sprintf to log a templated message.
Warnf(format string, args ...interface{})
// Errorf uses fmt.Sprintf to log a templated message.
Errorf(format string, args ...interface{})
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit(1).
Fatalf(format string, args ...interface{})
// Panicf uses fmt.Sprintf to log a templated message, then panics.
Panicf(format string, args ...interface{})
// DPanicf uses fmt.Sprintf to log a templated message. In development, the
// logger then panics.
DPanicf(format string, args ...interface{})
// Debugw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
Debugw(msg string, keysAndValues ...interface{})
// Infow logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
Infow(msg string, keysAndValues ...interface{})
// Warnw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
Warnw(msg string, keysAndValues ...interface{})
// Errorw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
Errorw(msg string, keysAndValues ...interface{})
// Fatalw logs a message with some additional context, then calls os.Exit(1).
// The additional context is added in the form of key-value pairs. The optimal
// way to write the value to the log message will be inferred by the value's
// type. To explicitly specify a type you can pass a Field such as
// logp.Stringer.
Fatalw(msg string, keysAndValues ...interface{})
// Panicw logs a message with some additional context, then panics. The
// additional context is added in the form of key-value pairs. The optimal way
// to write the value to the log message will be inferred by the value's type.
// To explicitly specify a type you can pass a Field such as logp.Stringer.
Panicw(msg string, keysAndValues ...interface{})
// DPanicw logs a message with some additional context. The logger panics only
// in Development mode. The additional context is added in the form of
// key-value pairs. The optimal way to write the value to the log message will
// be inferred by the value's type. To explicitly specify a type you can pass a
// Field such as logp.Stringer.
DPanicw(msg string, keysAndValues ...interface{})
Sync() error
Desugar() Logger
}

36
warp_zap.go Normal file
View File

@ -0,0 +1,36 @@
package logos
import (
"github.com/khorevaa/logos/appender"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var StackTraceLevelEnabler = zap.NewAtomicLevelAt(zapcore.ErrorLevel)
func newZapCore(config map[string]zapcore.Level, appenders map[string]*appender.Appender) zapcore.Core {
zcs := make([]zapcore.Core, 0)
for name, level := range config {
if level == OffLevel {
continue
}
if a, ok := appenders[name]; ok {
zcs = append(zcs, zapcore.NewCore(a.Encoder, a.Writer, level))
}
}
if len(zcs) == 0 {
return zapcore.NewNopCore()
}
return zapcore.NewTee(zcs...)
}
func newZapLogger(name string, core zapcore.Core, option ...zap.Option) *zap.Logger {
return zap.New(core, option...).Named(name)
}