update pkg/log from internal library

pull/147/head
wei cheng 6 years ago
parent 3f2c51b56e
commit c243e54e2b
No known key found for this signature in database
GPG Key ID: 5AF7E34E6A971D7F
  1. 2
      go.mod
  2. 34
      pkg/log/doc.go
  3. 50
      pkg/log/dsn.go
  4. 2
      pkg/log/field.go
  5. 13
      pkg/log/handler.go
  6. 20
      pkg/log/internal/core/buffer_test.go
  7. 22
      pkg/log/internal/core/bufferpool.go
  8. 4
      pkg/log/internal/core/field.go
  9. 20
      pkg/log/internal/core/pool.go
  10. 20
      pkg/log/internal/core/pool_test.go
  11. 125
      pkg/log/log.go
  12. 22
      pkg/log/log_test.go
  13. 61
      pkg/log/logrus.go
  14. 27
      pkg/log/pattern.go
  15. 19
      pkg/log/stdout.go
  16. 32
      pkg/log/util.go
  17. 54
      pkg/log/util_test.go
  18. 29
      pkg/log/verbose.go

@ -25,7 +25,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 // indirect github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 // indirect
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec // indirect github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec // indirect
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
github.com/sirupsen/logrus v1.4.1 // indirect github.com/sirupsen/logrus v1.4.1
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
github.com/tsuna/gohbase v0.0.0-20190201102810-d3184c1526df github.com/tsuna/gohbase v0.0.0-20190201102810-d3184c1526df
github.com/urfave/cli v1.20.0 github.com/urfave/cli v1.20.0

@ -2,21 +2,23 @@
主要功能 主要功能
1. 日志打印到本地 1. 日志打印到elk
2. 日志打印到标准输出 2. 日志打印到本地内部使用log4go
3. verbose日志实现参考glog实现可通过设置不同verbose级别默认不开启 3. 日志打印到标准输出
4. verbose日志实现参考glog实现可通过设置不同verbose级别默认不开启
日志配置 日志配置
1. 默认配置 1. 默认agent配置
目前日志已经实现默认配置可以直接使用以下方式 目前日志已经实现默认配置可以根据env自动切换远程日志可以直接使用以下方式
log.Init(nil) log.Init(nil)
2. 启动参数 or 环境变量 2. 启动参数 or 环境变量
启动参数 环境变量 说明 启动参数 环境变量 说明
log.stdout LOG_STDOUT 是否开启标准输出 log.stdout LOG_STDOUT 是否开启标准输出
log.agent LOG_AGENT 远端日志地址unixpacket:///var/run/lancer/collector_tcp.sock?timeout=100ms&chan=1024
log.dir LOG_DIR 文件日志路径 log.dir LOG_DIR 文件日志路径
log.v LOG_V verbose日志级别 log.v LOG_V verbose日志级别
log.module LOG_MODULE 可单独配置每个文件的verbose级别file=1,file2=2 log.module LOG_MODULE 可单独配置每个文件的verbose级别file=1,file2=2
@ -30,9 +32,14 @@
stdout = true stdout = true
vLevel = 3 vLevel = 3
filter = ["fileld1", "field2"] filter = ["fileld1", "field2"]
[log.module] [log.module]
"dao_user" = 2 "dao_user" = 2
"servic*" = 1 "servic*" = 1
[log.agent]
taskID = "00000x"
proto = "unixpacket"
addr = "/var/run/lancer/collector_tcp.sock"
chanSize = 10240
配置说明 配置说明
@ -47,5 +54,16 @@
2. log.module 2. log.module
可单独配置每个文件的verbose级别 可单独配置每个文件的verbose级别
3. log.agent
远端日志配置项
taskID lancer分配的taskID
proto 网络协议常见tcp, udp, unixgram
addr 网络地址常见ip:prot, sock
chanSize 日志队列长度
最佳实践
1. KVString 使用 KVString 代替 KV 可以减少对象分配, 避免给 golang GC 造成压力.
*/ */
package log package log

@ -0,0 +1,50 @@
package log
import (
"bytes"
"fmt"
"strconv"
"strings"
)
type verboseModule map[string]int32
type logFilter []string
func (f *logFilter) String() string {
return fmt.Sprint(*f)
}
// Set sets the value of the named command-line flag.
// format: -log.filter key1,key2
func (f *logFilter) Set(value string) error {
for _, i := range strings.Split(value, ",") {
*f = append(*f, strings.TrimSpace(i))
}
return nil
}
func (m verboseModule) String() string {
// FIXME strings.Builder
var buf bytes.Buffer
for k, v := range m {
buf.WriteString(k)
buf.WriteString(strconv.FormatInt(int64(v), 10))
buf.WriteString(",")
}
return buf.String()
}
// Set sets the value of the named command-line flag.
// format: -log.module file=1,file2=2
func (m verboseModule) Set(value string) error {
for _, i := range strings.Split(value, ",") {
kv := strings.Split(i, "=")
if len(kv) == 2 {
if v, err := strconv.ParseInt(kv[1], 10, 64); err == nil {
m[strings.TrimSpace(kv[0])] = int32(v)
}
}
}
return nil
}

@ -18,7 +18,7 @@ func KVString(key string, value string) D {
// KVInt construct Field with int value. // KVInt construct Field with int value.
func KVInt(key string, value int) D { func KVInt(key string, value int) D {
return D{Key: key, Type: core.IntType, Int64Val: int64(value)} return D{Key: key, Type: core.IntTpye, Int64Val: int64(value)}
} }
// KVInt64 construct D with int64 value. // KVInt64 construct D with int64 value.

@ -16,6 +16,8 @@ const (
_level = "level" _level = "level"
// log time. // log time.
_time = "time" _time = "time"
// request path.
// _title = "title"
// log file. // log file.
_source = "source" _source = "source"
// common log filed. // common log filed.
@ -27,7 +29,7 @@ const (
// uniq ID from trace. // uniq ID from trace.
_tid = "traceid" _tid = "traceid"
// request time. // request time.
_ts = "ts" // _ts = "ts"
// requester. // requester.
_caller = "caller" _caller = "caller"
// container environment: prod, pre, uat, fat. // container environment: prod, pre, uat, fat.
@ -38,6 +40,8 @@ const (
_mirror = "mirror" _mirror = "mirror"
// color. // color.
_color = "color" _color = "color"
// env_color
_envColor = "env_color"
// cluster. // cluster.
_cluster = "cluster" _cluster = "cluster"
) )
@ -75,8 +79,8 @@ type Handlers struct {
} }
// Log handlers logging. // Log handlers logging.
func (hs Handlers) Log(c context.Context, lv Level, d ...D) { func (hs Handlers) Log(ctx context.Context, lv Level, d ...D) {
var hasSource bool hasSource := false
for i := range d { for i := range d {
if _, ok := hs.filters[d[i].Key]; ok { if _, ok := hs.filters[d[i].Key]; ok {
d[i].Value = "***" d[i].Value = "***"
@ -87,11 +91,12 @@ func (hs Handlers) Log(c context.Context, lv Level, d ...D) {
} }
if !hasSource { if !hasSource {
fn := funcName(3) fn := funcName(3)
errIncr(lv, fn)
d = append(d, KVString(_source, fn)) d = append(d, KVString(_source, fn))
} }
d = append(d, KV(_time, time.Now()), KVInt64(_levelValue, int64(lv)), KVString(_level, lv.String())) d = append(d, KV(_time, time.Now()), KVInt64(_levelValue, int64(lv)), KVString(_level, lv.String()))
for _, h := range hs.handlers { for _, h := range hs.handlers {
h.Log(c, lv, d...) h.Log(ctx, lv, d...)
} }
} }

@ -1,3 +1,23 @@
// 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 core package core
import ( import (

@ -1,3 +1,25 @@
// 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 core houses zap's shared internal buffer pool. Third-party
// packages can recreate the same functionality with buffers.NewPool.
package core package core
var ( var (

@ -15,7 +15,7 @@ type FieldType int32
const ( const (
UnknownType FieldType = iota UnknownType FieldType = iota
StringType StringType
IntType IntTpye
Int64Type Int64Type
UintType UintType
Uint64Type Uint64Type
@ -43,7 +43,7 @@ func (f Field) AddTo(enc ObjectEncoder) {
switch f.Type { switch f.Type {
case StringType: case StringType:
enc.AddString(f.Key, f.StringVal) enc.AddString(f.Key, f.StringVal)
case IntType: case IntTpye:
enc.AddInt(f.Key, int(f.Int64Val)) enc.AddInt(f.Key, int(f.Int64Val))
case Int64Type: case Int64Type:
enc.AddInt64(f.Key, f.Int64Val) enc.AddInt64(f.Key, f.Int64Val)

@ -1,3 +1,23 @@
// 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 core package core
import "sync" import "sync"

@ -1,3 +1,23 @@
// 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 core package core
import ( import (

@ -4,17 +4,18 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"io"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/bilibili/kratos/pkg/conf/env" "github.com/bilibili/kratos/pkg/conf/env"
"github.com/bilibili/kratos/pkg/stat/prom"
) )
// Config log config. // Config log config.
type Config struct { type Config struct {
AppID string Family string
Host string Host string
// stdout // stdout
Stdout bool Stdout bool
@ -43,23 +44,39 @@ type Config struct {
Filter []string Filter []string
} }
// errProm prometheus error counter.
var errProm = prom.BusinessErrCount
// Render render log output
type Render interface {
Render(io.Writer, map[string]interface{}) error
RenderString(map[string]interface{}) string
}
var ( var (
h Handler h Handler
c *Config c *Config
) )
func init() { func init() {
host, _ := os.Hostname()
c = &Config{
Family: env.AppID,
Host: host,
}
h = newHandlers([]string{}, NewStdout())
addFlag(flag.CommandLine) addFlag(flag.CommandLine)
h = newHandlers(nil)
c = new(Config)
} }
var ( var (
_v int _v int
_stdout bool _stdout bool
_dir string _dir string
_filter logFilter _agentDSN string
_module = verboseModule{} _filter logFilter
_module = verboseModule{}
_noagent bool
) )
// addFlag init log from dsn. // addFlag init log from dsn.
@ -75,12 +92,15 @@ func addFlag(fs *flag.FlagSet) {
if tf := os.Getenv("LOG_FILTER"); len(tf) > 0 { if tf := os.Getenv("LOG_FILTER"); len(tf) > 0 {
_filter.Set(tf) _filter.Set(tf)
} }
_noagent, _ = strconv.ParseBool(os.Getenv("LOG_NO_AGENT"))
// get val from flag // get val from flag
fs.IntVar(&_v, "log.v", _v, "log verbose level, or use LOG_V env variable.") fs.IntVar(&_v, "log.v", _v, "log verbose level, or use LOG_V env variable.")
fs.BoolVar(&_stdout, "log.stdout", _stdout, "log enable stdout or not, or use LOG_STDOUT env variable.") fs.BoolVar(&_stdout, "log.stdout", _stdout, "log enable stdout or not, or use LOG_STDOUT env variable.")
fs.StringVar(&_dir, "log.dir", _dir, "log file `path, or use LOG_DIR env variable.") fs.StringVar(&_dir, "log.dir", _dir, "log file `path, or use LOG_DIR env variable.")
fs.StringVar(&_agentDSN, "log.agent", _agentDSN, "log agent dsn, or use LOG_AGENT env variable.")
fs.Var(&_module, "log.module", "log verbose for specified module, or use LOG_MODULE env variable, format: file=1,file2=2.") fs.Var(&_module, "log.module", "log verbose for specified module, or use LOG_MODULE env variable, format: file=1,file2=2.")
fs.Var(&_filter, "log.filter", "log field for sensitive message, or use LOG_FILTER env variable, format: field1,field2.") fs.Var(&_filter, "log.filter", "log field for sensitive message, or use LOG_FILTER env variable, format: field1,field2.")
fs.BoolVar(&_noagent, "log.noagent", _noagent, "force disable log agent print log to stderr, or use LOG_NO_AGENT")
} }
// Init create logger with context. // Init create logger with context.
@ -96,8 +116,8 @@ func Init(conf *Config) {
Filter: _filter, Filter: _filter,
} }
} }
if conf.AppID == "" && len(env.AppID) != 0 { if len(env.AppID) != 0 {
conf.AppID = env.AppID conf.Family = env.AppID // for caster
} }
conf.Host = env.Hostname conf.Host = env.Hostname
if len(conf.Host) == 0 { if len(conf.Host) == 0 {
@ -106,7 +126,7 @@ func Init(conf *Config) {
} }
var hs []Handler var hs []Handler
// when env is dev // when env is dev
if isNil || conf.Stdout { if conf.Stdout || (isNil && (env.DeployEnv == "" || env.DeployEnv == env.DeployEnvDev)) || _noagent {
hs = append(hs, NewStdout()) hs = append(hs, NewStdout())
} }
if conf.Dir != "" { if conf.Dir != "" {
@ -116,21 +136,6 @@ func Init(conf *Config) {
c = conf c = conf
} }
type logFilter []string
func (f *logFilter) String() string {
return fmt.Sprint(*f)
}
// Set sets the value of the named command-line flag.
// format: -log.filter key1,key2
func (f *logFilter) Set(value string) error {
for _, i := range strings.Split(value, ",") {
*f = append(*f, strings.TrimSpace(i))
}
return nil
}
// Info logs a message at the info log level. // Info logs a message at the info log level.
func Info(format string, args ...interface{}) { func Info(format string, args ...interface{}) {
h.Log(context.Background(), _infoLevel, KVString(_log, fmt.Sprintf(format, args...))) h.Log(context.Background(), _infoLevel, KVString(_log, fmt.Sprintf(format, args...)))
@ -176,22 +181,19 @@ func Errorv(ctx context.Context, args ...D) {
h.Log(ctx, _errorLevel, args...) h.Log(ctx, _errorLevel, args...)
} }
// SetFormat only effective on stdout and file handler func logw(args []interface{}) []D {
// %T time format at "15:04:05.999" on stdout handler, "15:04:05 MST" on file handler if len(args)%2 != 0 {
// %t time format at "15:04:05" on stdout handler, "15:04" on file on file handler Warn("log: the variadic must be plural, the last one will ignored")
// %D data format at "2006/01/02" }
// %d data format at "01/02" ds := make([]D, 0, len(args)/2)
// %L log level e.g. INFO WARN ERROR for i := 0; i < len(args)-1; i = i + 2 {
// %M log message and additional fields: key=value this is log message if key, ok := args[i].(string); ok {
// NOTE below pattern not support on file handler ds = append(ds, KV(key, args[i+1]))
// %f function name and line number e.g. model.Get:121 } else {
// %i instance id Warn("log: key must be string, get %T, ignored", args[i])
// %e deploy env e.g. dev uat fat prod }
// %z zone }
// %S full file name and line number: /a/b/c/d.go:23 return ds
// %s final file name element and line number: d.go:23
func SetFormat(format string) {
h.SetFormat(format)
} }
// Infow logs a message with some additional context. The variadic key-value pairs are treated as they are in With. // Infow logs a message with some additional context. The variadic key-value pairs are treated as they are in With.
@ -209,19 +211,22 @@ func Errorw(ctx context.Context, args ...interface{}) {
h.Log(ctx, _errorLevel, logw(args)...) h.Log(ctx, _errorLevel, logw(args)...)
} }
func logw(args []interface{}) []D { // SetFormat only effective on stdout and file handler
if len(args)%2 != 0 { // %T time format at "15:04:05.999" on stdout handler, "15:04:05 MST" on file handler
Warn("log: the variadic must be plural, the last one will ignored") // %t time format at "15:04:05" on stdout handler, "15:04" on file on file handler
} // %D data format at "2006/01/02"
ds := make([]D, 0, len(args)/2) // %d data format at "01/02"
for i := 0; i < len(args)-1; i = i + 2 { // %L log level e.g. INFO WARN ERROR
if key, ok := args[i].(string); ok { // %M log message and additional fields: key=value this is log message
ds = append(ds, KV(key, args[i+1])) // NOTE below pattern not support on file handler
} else { // %f function name and line number e.g. model.Get:121
Warn("log: key must be string, get %T, ignored", args[i]) // %i instance id
} // %e deploy env e.g. dev uat fat prod
} // %z zone
return ds // %S full file name and line number: /a/b/c/d.go:23
// %s final file name element and line number: d.go:23
func SetFormat(format string) {
h.SetFormat(format)
} }
// Close close resource. // Close close resource.
@ -230,3 +235,9 @@ func Close() (err error) {
h = _defaultStdout h = _defaultStdout
return return
} }
func errIncr(lv Level, source string) {
if lv == _errorLevel {
errProm.Incr(source)
}
}

@ -37,19 +37,16 @@ func testLog(t *testing.T) {
Error("hello %s", "world") Error("hello %s", "world")
Errorv(context.Background(), KV("key", 2222222), KV("test2", "test")) Errorv(context.Background(), KV("key", 2222222), KV("test2", "test"))
Errorc(context.Background(), "keys: %s %s...", "key1", "key2") Errorc(context.Background(), "keys: %s %s...", "key1", "key2")
Errorw(context.Background(), "key1", "value1", "key2", "value2")
}) })
t.Run("Warn", func(t *testing.T) { t.Run("Warn", func(t *testing.T) {
Warn("hello %s", "world") Warn("hello %s", "world")
Warnv(context.Background(), KV("key", 2222222), KV("test2", "test")) Warnv(context.Background(), KV("key", 2222222), KV("test2", "test"))
Warnc(context.Background(), "keys: %s %s...", "key1", "key2") Warnc(context.Background(), "keys: %s %s...", "key1", "key2")
Warnw(context.Background(), "key1", "value1", "key2", "value2")
}) })
t.Run("Info", func(t *testing.T) { t.Run("Info", func(t *testing.T) {
Info("hello %s", "world") Info("hello %s", "world")
Infov(context.Background(), KV("key", 2222222), KV("test2", "test")) Infov(context.Background(), KV("key", 2222222), KV("test2", "test"))
Infoc(context.Background(), "keys: %s %s...", "key1", "key2") Infoc(context.Background(), "keys: %s %s...", "key1", "key2")
Infow(context.Background(), "key1", "value1", "key2", "value2")
}) })
} }
@ -87,3 +84,22 @@ func TestLogWithMirror(t *testing.T) {
Infov(context.Background(), KV("key1", "val1"), KV("key2", ""), KV("log", "log content"), KV("msg", "msg content")) Infov(context.Background(), KV("key1", "val1"), KV("key2", ""), KV("log", "log content"), KV("msg", "msg content"))
} }
func TestOverwriteSouce(t *testing.T) {
ctx := context.Background()
t.Run("test source kv string", func(t *testing.T) {
Infov(ctx, KVString("source", "test"))
})
t.Run("test source kv string", func(t *testing.T) {
Infov(ctx, KV("source", "test"))
})
}
func BenchmarkLog(b *testing.B) {
ctx := context.Background()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Infov(ctx, KVString("test", "hello"), KV("int", 34), KV("hhh", "hhhh"))
}
})
}

@ -0,0 +1,61 @@
package log
import (
"context"
"io/ioutil"
"os"
"github.com/sirupsen/logrus"
)
func init() {
redirectLogrus()
}
func redirectLogrus() {
// FIXME: because of different stack depth call runtime.Caller will get error function name.
logrus.AddHook(redirectHook{})
if os.Getenv("LOGRUS_STDOUT") == "" {
logrus.SetOutput(ioutil.Discard)
}
}
type redirectHook struct{}
func (redirectHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (redirectHook) Fire(entry *logrus.Entry) error {
lv := _infoLevel
var logrusLv string
var verbose int32
switch entry.Level {
case logrus.FatalLevel, logrus.PanicLevel:
logrusLv = entry.Level.String()
fallthrough
case logrus.ErrorLevel:
lv = _errorLevel
case logrus.WarnLevel:
lv = _warnLevel
case logrus.InfoLevel:
lv = _infoLevel
case logrus.DebugLevel:
// use verbose log replace of debuglevel
verbose = 10
}
args := make([]D, 0, len(entry.Data)+1)
args = append(args, D{Key: _log, Value: entry.Message})
for k, v := range entry.Data {
args = append(args, D{Key: k, Value: v})
}
if logrusLv != "" {
args = append(args, D{Key: "logrus_lv", Value: logrusLv})
}
if verbose != 0 {
V(verbose).Infov(context.Background(), args...)
} else {
h.Log(context.Background(), lv, args...)
}
return nil
}

@ -4,17 +4,13 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"path"
"runtime"
"strings" "strings"
"sync" "sync"
"time" "time"
) )
// Render render log output
type Render interface {
Render(io.Writer, map[string]interface{}) error
RenderString(map[string]interface{}) string
}
var patternMap = map[string]func(map[string]interface{}) string{ var patternMap = map[string]func(map[string]interface{}) string{
"T": longTime, "T": longTime,
"t": shortTime, "t": shortTime,
@ -25,8 +21,8 @@ var patternMap = map[string]func(map[string]interface{}) string{
"i": keyFactory(_instanceID), "i": keyFactory(_instanceID),
"e": keyFactory(_deplyEnv), "e": keyFactory(_deplyEnv),
"z": keyFactory(_zone), "z": keyFactory(_zone),
"S": keyFactory(_source), "S": longSource,
"s": keyFactory(_source), "s": shortSource,
"M": message, "M": message,
} }
@ -78,6 +74,7 @@ func (p *pattern) Render(w io.Writer, d map[string]interface{}) error {
for _, f := range p.funcs { for _, f := range p.funcs {
buf.WriteString(f(d)) buf.WriteString(f(d))
} }
_, err := buf.WriteTo(w) _, err := buf.WriteTo(w)
return err return err
} }
@ -114,6 +111,20 @@ func keyFactory(key string) func(map[string]interface{}) string {
} }
} }
func longSource(map[string]interface{}) string {
if _, file, lineNo, ok := runtime.Caller(6); ok {
return fmt.Sprintf("%s:%d", file, lineNo)
}
return "unknown:0"
}
func shortSource(map[string]interface{}) string {
if _, file, lineNo, ok := runtime.Caller(6); ok {
return fmt.Sprintf("%s:%d", path.Base(file), lineNo)
}
return "unknown:0"
}
func longTime(map[string]interface{}) string { func longTime(map[string]interface{}) string {
return time.Now().Format("15:04:05.000") return time.Now().Format("15:04:05.000")
} }

@ -2,27 +2,22 @@ package log
import ( import (
"context" "context"
"io"
"os" "os"
"time" "time"
) )
const defaultPattern = "%L %d-%T %f %M"
var _defaultStdout = NewStdout() var _defaultStdout = NewStdout()
// StdoutHandler stdout log handler // StdoutHandler stdout log handler
type StdoutHandler struct { type StdoutHandler struct {
out io.Writer
err io.Writer
render Render render Render
} }
// NewStdout create a stdout log handler // NewStdout create a stdout log handler
func NewStdout() *StdoutHandler { func NewStdout() *StdoutHandler {
return &StdoutHandler{ return &StdoutHandler{render: newPatternRender(defaultPattern)}
out: os.Stdout,
err: os.Stderr,
render: newPatternRender("[%D %T] [%s] %M"),
}
} }
// Log stdout loging, only for developing env. // Log stdout loging, only for developing env.
@ -31,12 +26,8 @@ func (h *StdoutHandler) Log(ctx context.Context, lv Level, args ...D) {
// add extra fields // add extra fields
addExtraField(ctx, d) addExtraField(ctx, d)
d[_time] = time.Now().Format(_timeFormat) d[_time] = time.Now().Format(_timeFormat)
if lv <= _infoLevel { h.render.Render(os.Stderr, d)
h.render.Render(h.out, d) os.Stderr.Write([]byte("\n"))
} else {
h.render.Render(h.err, d)
}
h.out.Write([]byte("\n"))
} }
// Close stdout loging // Close stdout loging

@ -2,7 +2,6 @@ package log
import ( import (
"context" "context"
"fmt"
"math" "math"
"runtime" "runtime"
"strconv" "strconv"
@ -16,11 +15,7 @@ import (
func addExtraField(ctx context.Context, fields map[string]interface{}) { func addExtraField(ctx context.Context, fields map[string]interface{}) {
if t, ok := trace.FromContext(ctx); ok { if t, ok := trace.FromContext(ctx); ok {
if s, ok := t.(fmt.Stringer); ok { fields[_tid] = t.TraceID()
fields[_tid] = s.String()
} else {
fields[_tid] = fmt.Sprintf("%s", t)
}
} }
if caller := metadata.String(ctx, metadata.Caller); caller != "" { if caller := metadata.String(ctx, metadata.Caller); caller != "" {
fields[_caller] = caller fields[_caller] = caller
@ -28,24 +23,35 @@ func addExtraField(ctx context.Context, fields map[string]interface{}) {
if color := metadata.String(ctx, metadata.Color); color != "" { if color := metadata.String(ctx, metadata.Color); color != "" {
fields[_color] = color fields[_color] = color
} }
if env.Color != "" {
fields[_envColor] = env.Color
}
if cluster := metadata.String(ctx, metadata.Cluster); cluster != "" { if cluster := metadata.String(ctx, metadata.Cluster); cluster != "" {
fields[_cluster] = cluster fields[_cluster] = cluster
} }
fields[_deplyEnv] = env.DeployEnv fields[_deplyEnv] = env.DeployEnv
fields[_zone] = env.Zone fields[_zone] = env.Zone
fields[_appID] = c.AppID fields[_appID] = c.Family
fields[_instanceID] = c.Host fields[_instanceID] = c.Host
if metadata.Bool(ctx, metadata.Mirror) { if metadata.String(ctx, metadata.Mirror) != "" {
fields[_mirror] = true fields[_mirror] = true
} }
} }
// funcName get func name.
func funcName(skip int) (name string) {
if _, file, lineNo, ok := runtime.Caller(skip); ok {
return file + ":" + strconv.Itoa(lineNo)
}
return "unknown:0"
}
// toMap convert D slice to map[string]interface{} for legacy file and stdout. // toMap convert D slice to map[string]interface{} for legacy file and stdout.
func toMap(args ...D) map[string]interface{} { func toMap(args ...D) map[string]interface{} {
d := make(map[string]interface{}, 10+len(args)) d := make(map[string]interface{}, 10+len(args))
for _, arg := range args { for _, arg := range args {
switch arg.Type { switch arg.Type {
case core.UintType, core.Uint64Type, core.IntType, core.Int64Type: case core.UintType, core.Uint64Type, core.IntTpye, core.Int64Type:
d[arg.Key] = arg.Int64Val d[arg.Key] = arg.Int64Val
case core.StringType: case core.StringType:
d[arg.Key] = arg.StringVal d[arg.Key] = arg.StringVal
@ -61,11 +67,3 @@ func toMap(args ...D) map[string]interface{} {
} }
return d return d
} }
// funcName get func name.
func funcName(skip int) (name string) {
if _, file, lineNo, ok := runtime.Caller(skip); ok {
return file + ":" + strconv.Itoa(lineNo)
}
return "unknown:0"
}

@ -0,0 +1,54 @@
package log
import (
"reflect"
"strings"
"testing"
"time"
)
func TestFuncName(t *testing.T) {
name := funcName(1)
if !strings.Contains(name, "util_test.go:11") {
t.Errorf("expect contains util_test.go:11 got %s", name)
}
}
func Test_toMap(t *testing.T) {
type args struct {
args []D
}
tests := []struct {
name string
args args
want map[string]interface{}
}{
{
args: args{[]D{KVString("test", "hello")}},
want: map[string]interface{}{"test": "hello"},
},
{
args: args{[]D{KVInt64("test", 123)}},
want: map[string]interface{}{"test": int64(123)},
},
{
args: args{[]D{KVFloat32("test", float32(1.01))}},
want: map[string]interface{}{"test": float32(1.01)},
},
{
args: args{[]D{KVFloat32("test", float32(1.01))}},
want: map[string]interface{}{"test": float32(1.01)},
},
{
args: args{[]D{KVDuration("test", time.Second)}},
want: map[string]interface{}{"test": time.Second},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := toMap(tt.args.args...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("toMap() = %v, want %v", got, tt.want)
}
})
}
}

@ -1,42 +1,13 @@
package log package log
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
) )
type verboseModule map[string]int32
func (m verboseModule) String() string {
// FIXME strings.Builder
var buf bytes.Buffer
for k, v := range m {
buf.WriteString(k)
buf.WriteString(strconv.FormatInt(int64(v), 10))
buf.WriteString(",")
}
return buf.String()
}
// Set sets the value of the named command-line flag.
// format: -log.module file=1,file2=2
func (m verboseModule) Set(value string) error {
for _, i := range strings.Split(value, ",") {
kv := strings.Split(i, "=")
if len(kv) == 2 {
if v, err := strconv.ParseInt(kv[1], 10, 64); err == nil {
m[strings.TrimSpace(kv[0])] = int32(v)
}
}
}
return nil
}
// V reports whether verbosity at the call site is at least the requested level. // V reports whether verbosity at the call site is at least the requested level.
// The returned value is a boolean of type Verbose, which implements Info, Infov etc. // The returned value is a boolean of type Verbose, which implements Info, Infov etc.
// These methods will write to the Info log if called. // These methods will write to the Info log if called.

Loading…
Cancel
Save