├── .gitignore ├── LICENSE ├── README.md ├── example └── grafana │ ├── docker-compose.yaml │ ├── loki │ └── etc │ │ └── local-config.yaml │ └── tempo │ └── tempo-local.yaml ├── field.go ├── go.mod ├── go.sum ├── logger.go ├── propagation.go ├── propagation ├── extract │ ├── gin.go │ └── options.go ├── gintrace_test.go └── inject │ └── http.go ├── test └── logger_test.go ├── trace.go └── zap_log.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 xiaolei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # logger 2 | 3 | #### logger 4 | 5 | logger wraps uber/zap and trace with opentelemetry 6 | 7 | #### 8 | 9 | ```mermaid 10 | flowchart LR 11 | logger -- openTelemetrySDK --> trace 12 | trace -- http --> tempo 13 | 14 | logger -- zap --> log 15 | log-->loki 16 | 17 | tempo-->grafana 18 | loki-->grafana 19 | 20 | ``` 21 | 22 | #### Feature 23 | 24 | - [x] 支持日志及切分 25 | - [x] 支持追踪(基于 `opentelemetry`) 26 | - [x] 支持 Debug,Info,Warn,Error,Fatal 日志等级 27 | - [x] 支持异常自动恢复 `defer logger.End(ctx)` 28 | 29 | #### Install 30 | 31 | ```text 32 | go get -u -v github.com/itmisx/logger 33 | ``` 34 | 35 | #### Usage 36 | 37 | - 配置项 38 | 39 | ```go 40 | type Config struct { 41 | Debug bool `yaml:"debug" mapstructure:"debug"` // 调试模式,默认仅记录错误 42 | Output bool `yaml:"output" mapstructure:"output"` // 日志打印方式。none不打印日志,console打印到控制台,file输出到文件。默认为console 43 | File string `yaml:"file" mapstructure:"file"` // 日志文件路径 44 | MaxSize int `yaml:"max_size" mapstructure:"max_size"` // 单个日志文件的大小限制,单位MB 45 | MaxBackups int `yaml:"max_backups" mapstructure:"max_backups"` // 日志文件数据的限制 46 | MaxAge int `yaml:"max_age" mapstructure:"max_age"` // 日志文件的保存天数 47 | Compress bool `yaml:"compress" mapstructure:"compress"` // 日志文件压缩开关 48 | Rotate string `yaml:"rotate" mapstructure:"rotate"` // 日志切分的时间,参考linux定时任务0 0 0 * * *,精确到秒 49 | EnableTrace bool `yaml:"enable_trace" mapstructure:"enable_trace"` // 日志追踪开关 50 | TracerProviderType string `yaml:"tracer_provider_type" mapstructure:"tracer_provider_type"`// 追踪内容导出类型,默认为jaeger 51 | TraceSampleRatio float64 `yaml:"trace_sample_ratio" mapstructure:"trace_sample_ratio"` // 追踪采样的频率, 0.0-1 52 | JaegerServer string `yaml:"jaeger_server" mapstructure:"jaeger_server"`// jaeger的URI地址 53 | JaegerUsername string `yaml:"jaeger_username" mapstructure:"jaeger_username"`// jaeger用户名 54 | JaegerPassword string `yaml:"jaeger_password" mapstructure:"jaeger_password"`// jaeger密码 55 | } 56 | ``` 57 | 58 | - 初始化 59 | 60 | ```go 61 | // 初始化,配置参考logger.Config 62 | // service.name为服务的名字 63 | // service.version为版本 64 | // logger.Init(config,attrs...logger.Field),更多的logger.Type参考logger下filed.go 65 | logger.Init(conf,logger.String("service.name","service1"),logger.String("service.version","version")) 66 | ``` 67 | 68 | * 基础使用 69 | 70 | ```go 71 | // 在一个函数中启动一个span 72 | // 并注册一个延迟结束(!!切记,缺少可能会导致内存泄露) 73 | func foo(){ 74 | // logger.Start(ctx,spanName,attrs...logger.Field) 75 | // 可以为一个span指定spanName以及额外的属性信息 76 | // attr支持logger.String("key","value") 形式,更多的logger.Type参考logger下filed.go 77 | ctx:=logger.Start(context,spanName,logger.String("key","value")) 78 | defer logger.End(ctx) 79 | // 正常记录日志 80 | // logger.Info(ctx,msg,attrs...logger.Field) 81 | // 记录msg信息及额外的信息 82 | // attr支持logger.String("key","value") 形式 83 | // 支持Debug,Info,Warn,Error,Fatal 84 | logger.Info(ctx,msg,logger.String("key","value")) 85 | } 86 | ``` 87 | 88 | * 追踪传递,就是传递 traceID 和 spanID 89 | 90 | > gin 框架实例 91 | 92 | ```go 93 | // 请求方 94 | // 95 | // 请求方调用request时,注入context trace信息 96 | // post为封装的一个请求函数 97 | func post(ctx context.Context){ 98 | …… // request的请求 99 | // 注入context追踪信息 100 | // request类型为*http.Request 101 | logger.HttpInject(ctx,request) 102 | …… // 发送请求 103 | } 104 | 105 | 106 | // 接收方 107 | // gin举例,初始化gin时,注册中间件 108 | // sevice为当前后台服务的名称 109 | router.Use(GinMiddleware("service")) 110 | // 使用 111 | func foo(c *gin.Context){ 112 | ctx:=logger.Start(c.Request.Context(),spanName,logger.String("key","value")) 113 | defer logger.End(ctx) 114 | // 正常记录日志 115 | logger.Info(ctx,msg,logger.String("key","value")) 116 | } 117 | ``` 118 | 119 | > 手动传递 120 | 121 | ```go 122 | // 对于不能通过函数或请求传递的,则需要手动传递 123 | // 通过 指定traceID和spanID生成一个context 124 | // 125 | // 然后,logger.Start(ctx,"spanName"),其生成的span就为childspan 126 | // 127 | // 其中traceID和spanID可以利用logger.GenTraceID()和logger.GenSpanID()生成 128 | // 也可以通过上个start返回的spanCtx中获得,logger.TraceID(spanCtx),logger.SpanID(spanCtx) 129 | ctx, err := NewRootContext(traceID, spanID) 130 | ``` 131 | 132 | #### functions 133 | 134 | - Init(conf Config,applicationAttributes ...logger.Field) //初始化,配置及应用信息 135 | - Start(ctx context.Context,spanName string,spanStartOption ...logger.Field) context.Context //启动日志追踪,spanName 为追踪跨度的名称,spanStartOption 为跨度额外信息 136 | - Info(ctx context.Context,msg string,attributes ...logger.Field) // 普通日志 137 | - Warn(ctx context.Context,msg string,attributes ...logger.Field) // 警告日志 138 | - Error(ctx context.Context,msg string,attributes ...logger.Field) // 错误日志 139 | - End(ctx context.Context) //结束日志追踪 140 | - TraceID(ctx context.Context)string //获取 traceID 141 | - SpanID(ctx context.Context)string //获取 spanID 142 | - GenTraceID()string // 生成 traceID 143 | - GenSpanID()string // 生成 spanID 144 | 145 | > logger.Field 类型支持 146 | 147 | - bool 148 | - boolSlice 149 | - int 150 | - intSlice 151 | - int64 152 | - int64Slice 153 | - float64 154 | - float64Slice 155 | - string 156 | - stringSlice 157 | - stringer, interface{String()string{}} 158 | 159 | #### 基于 loki+tempo+grafana 的效果(日志查询+追踪) 160 | 161 | ![image](https://user-images.githubusercontent.com/5791324/147378026-42819fbb-5abf-46b9-8bba-a3ec7bfbcc81.png) 162 | 163 | #### License 164 | 165 | Use of logger is governed by the Mit License 166 | -------------------------------------------------------------------------------- /example/grafana/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | # logging模板 3 | x-logging: &tpl-logging 4 | driver: loki 5 | options: 6 | loki-url: "http://localhost:31000/loki/api/v1/push" 7 | services: 8 | # loki 日志搜集 9 | loki: 10 | image: grafana/loki:2.4.1 11 | container_name: grafana_loki 12 | depends_on: 13 | - tempo 14 | ports: 15 | - "31000:3100" 16 | volumes: 17 | - ./loki/etc:/etc/loki 18 | #environment: 19 | #- JAEGER_AGENT_HOST=tempo 20 | #- JAEGER_ENDPOINT=http://tempo:14268/api/traces # send traces to Tempo 21 | #- JAEGER_SAMPLER_TYPE=const 22 | #- JAEGER_SAMPLER_PARAM=1 23 | networks: 24 | - grafana 25 | restart: "always" 26 | logging: 27 | driver: json-file 28 | options: 29 | max-size: "200k" # 单个文件大小为200k 30 | max-file: "10" # 最多10个文件 31 | 32 | # promtail 日志搜集 33 | promtail: 34 | image: grafana/promtail:2.4.1 35 | container_name: grafana_promtail 36 | networks: 37 | - grafana 38 | restart: "always" 39 | logging: *tpl-logging 40 | 41 | # tempo 日志追踪 42 | tempo: 43 | image: grafana/tempo:1.4.1 44 | command: ["-config.file=/etc/tempo.yaml"] 45 | container_name: grafana_tempo 46 | volumes: 47 | - ./tempo/tempo-local.yaml:/etc/tempo.yaml 48 | #- ./tempo/data:/tmp/tempo 49 | ports: 50 | - "14268:14268" # jaeger ingest 51 | - "3200:3200" # tempo 52 | - "55680" # otlp grpc 53 | - "55681" # otlp http 54 | - "9411" # zipkin 55 | restart: "always" 56 | networks: 57 | - grafana 58 | logging: *tpl-logging 59 | 60 | # grafana 界面查看 61 | grafana: 62 | image: grafana/grafana:8.2.6 63 | container_name: grafana 64 | ports: 65 | - "30000:3000" 66 | volumes: 67 | - ./grafana/data:/var/lib/grafana 68 | - ./grafana/conf:/usr/share/grafana/conf 69 | networks: 70 | - grafana 71 | restart: "always" 72 | logging: *tpl-logging 73 | networks: 74 | grafana: 75 | external: true 76 | -------------------------------------------------------------------------------- /example/grafana/loki/etc/local-config.yaml: -------------------------------------------------------------------------------- 1 | auth_enabled: false 2 | 3 | server: 4 | http_listen_port: 3100 5 | 6 | common: 7 | path_prefix: /loki 8 | storage: 9 | filesystem: 10 | chunks_directory: /loki/chunks 11 | rules_directory: /loki/rules 12 | replication_factor: 1 13 | ring: 14 | instance_addr: 127.0.0.1 15 | kvstore: 16 | store: inmemory 17 | 18 | schema_config: 19 | configs: 20 | - from: 2020-10-24 21 | store: boltdb-shipper 22 | object_store: filesystem 23 | schema: v11 24 | index: 25 | prefix: index_ 26 | period: 24h 27 | 28 | ruler: 29 | alertmanager_url: http://localhost:9093 30 | storage_config: 31 | boltdb: 32 | directory: /tmp/loki/index 33 | filesystem: 34 | directory: /tmp/loki/chunks 35 | limits_config: 36 | enforce_metric_name: false 37 | reject_old_samples: true 38 | reject_old_samples_max_age: 168h 39 | # 表的保留期7天 40 | table_manager: 41 | retention_deletes_enabled: true 42 | retention_period: 168h 43 | -------------------------------------------------------------------------------- /example/grafana/tempo/tempo-local.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | http_listen_port: 3200 3 | distributor: 4 | receivers: # this configuration will listen on all ports and protocols that tempo is capable of. 5 | jaeger: # the receives all come from the OpenTelemetry collector. more configuration information can 6 | protocols: # be found there: https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver 7 | thrift_http: # 8 | grpc: # for a production deployment you should only enable the receivers you need! 9 | thrift_binary: 10 | thrift_compact: 11 | zipkin: 12 | otlp: 13 | protocols: 14 | http: 15 | grpc: 16 | opencensus: 17 | 18 | ingester: 19 | trace_idle_period: 5s # the length of time after a trace has not received spans to consider it complete and flush it 20 | max_block_bytes: 1073741824 # cut the head block when it hits this size or ... 21 | max_block_duration: 1h # this much time passes 22 | 23 | compactor: 24 | compaction: 25 | compaction_window: 1h # blocks in this time window will be compacted together 26 | max_block_bytes: 100000000 # maximum size of compacted blocks 27 | block_retention: 168h 28 | compacted_block_retention: 1h 29 | 30 | storage: 31 | trace: 32 | backend: local # backend configuration to use 33 | block: 34 | bloom_filter_false_positive: .05 # bloom filter false positive rate. lower values create larger filters but fewer false positives 35 | index_downsample_bytes: 1000 # number of bytes per index record 36 | encoding: zstd # block encoding/compression. options: none, gzip, lz4-64k, lz4-256k, lz4-1M, lz4, snappy, zstd, s2 37 | wal: 38 | path: /tmp/tempo/wal # where to store the the wal locally 39 | encoding: snappy # wal encoding/compression. options: none, gzip, lz4-64k, lz4-256k, lz4-1M, lz4, snappy, zstd, s2 40 | local: 41 | path: /tmp/tempo/blocks 42 | pool: 43 | max_workers: 100 # worker pool determines the number of parallel requests to the object store backend 44 | queue_depth: 10000 45 | -------------------------------------------------------------------------------- /field.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type FieldType int 9 | 10 | const ( 11 | nnknownType FieldType = iota 12 | boolType 13 | boolSliceType 14 | intType 15 | intSliceType 16 | int64Type 17 | int64SliceType 18 | float64Type 19 | float64SliceType 20 | stringType 21 | stringSliceType 22 | stringerType 23 | anyType 24 | errType 25 | ) 26 | 27 | type Field struct { 28 | Key string 29 | Type FieldType 30 | Bool bool 31 | Bools []bool 32 | Integer int 33 | Integers []int 34 | String string 35 | Float64 float64 36 | Integer64 int64 37 | Integer64s []int64 38 | Strings []string 39 | Float64s []float64 40 | Any interface{} 41 | } 42 | 43 | // Bool 44 | func Bool(key string, val bool) Field { 45 | return Field{Key: key, Type: boolType, Bool: val} 46 | } 47 | 48 | // BoolSlice 49 | func BoolSlice(key string, val []bool) Field { 50 | return Field{Key: key, Type: boolSliceType, Bools: val} 51 | } 52 | 53 | // Int 54 | func Int(key string, val int) Field { 55 | return Field{Key: key, Type: intType, Integer: val} 56 | } 57 | 58 | // IntSlice 59 | func IntSlice(key string, val []int) Field { 60 | return Field{Key: key, Type: intSliceType, Integers: val} 61 | } 62 | 63 | // Int64 64 | func Int64(key string, val int64) Field { 65 | return Field{Key: key, Type: int64Type, Integer64: val} 66 | } 67 | 68 | // Int64Slice 69 | func Int64Slice(key string, val []int64) Field { 70 | return Field{Key: key, Type: int64SliceType, Integer64s: val} 71 | } 72 | 73 | // Float64 74 | func Float64(key string, val float64) Field { 75 | return Field{Key: key, Type: float64Type, Float64: val} 76 | } 77 | 78 | // Float64Slice 79 | func Float64Slice(key string, val []float64) Field { 80 | return Field{Key: key, Type: float64SliceType, Float64s: val} 81 | } 82 | 83 | // String 84 | func String(key string, val string) Field { 85 | return Field{Key: key, Type: stringType, String: val} 86 | } 87 | 88 | // StringSlice 89 | func StringSlice(key string, val []string) Field { 90 | return Field{Key: key, Type: stringSliceType, Strings: val} 91 | } 92 | 93 | // Stringer 94 | func Stringer(key string, val fmt.Stringer) Field { 95 | return Field{Key: key, Type: stringerType, String: val.String()} 96 | } 97 | 98 | // Any 99 | func Any(key string, val interface{}) Field { 100 | return Field{Key: key, Type: anyType, Any: val} 101 | } 102 | 103 | // Err 104 | func Err(err error) Field { 105 | if err == nil { 106 | err = errors.New("nil") 107 | } 108 | return Field{Key: "error", Type: stringType, String: err.Error()} 109 | } 110 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/itmisx/logx 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.0 7 | github.com/robfig/cron/v3 v3.0.1 8 | github.com/stretchr/testify v1.9.0 9 | go.opentelemetry.io/contrib/propagators/b3 v1.30.0 10 | go.opentelemetry.io/otel v1.30.0 11 | go.opentelemetry.io/otel/exporters/jaeger v1.17.0 12 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 13 | go.opentelemetry.io/otel/sdk v1.30.0 14 | go.opentelemetry.io/otel/trace v1.30.0 15 | go.uber.org/zap v1.27.0 16 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 17 | ) 18 | 19 | require ( 20 | github.com/bytedance/sonic v1.12.3 // indirect 21 | github.com/bytedance/sonic/loader v0.2.0 // indirect 22 | github.com/cloudwego/base64x v0.1.4 // indirect 23 | github.com/cloudwego/iasm v0.2.0 // indirect 24 | github.com/davecgh/go-spew v1.1.1 // indirect 25 | github.com/gabriel-vasile/mimetype v1.4.5 // indirect 26 | github.com/gin-contrib/sse v0.1.0 // indirect 27 | github.com/go-logr/logr v1.4.2 // indirect 28 | github.com/go-logr/stdr v1.2.2 // indirect 29 | github.com/go-playground/locales v0.14.1 // indirect 30 | github.com/go-playground/universal-translator v0.18.1 // indirect 31 | github.com/go-playground/validator/v10 v10.22.1 // indirect 32 | github.com/goccy/go-json v0.10.3 // indirect 33 | github.com/google/uuid v1.6.0 // indirect 34 | github.com/json-iterator/go v1.1.12 // indirect 35 | github.com/klauspost/cpuid/v2 v2.2.8 // indirect 36 | github.com/leodido/go-urn v1.4.0 // indirect 37 | github.com/mattn/go-isatty v0.0.20 // indirect 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 39 | github.com/modern-go/reflect2 v1.0.2 // indirect 40 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 41 | github.com/pmezard/go-difflib v1.0.0 // indirect 42 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 43 | github.com/ugorji/go/codec v1.2.12 // indirect 44 | go.opentelemetry.io/otel/metric v1.30.0 // indirect 45 | go.uber.org/multierr v1.11.0 // indirect 46 | golang.org/x/arch v0.11.0 // indirect 47 | golang.org/x/crypto v0.28.0 // indirect 48 | golang.org/x/net v0.30.0 // indirect 49 | golang.org/x/sys v0.26.0 // indirect 50 | golang.org/x/text v0.19.0 // indirect 51 | google.golang.org/protobuf v1.35.1 // indirect 52 | gopkg.in/yaml.v3 v3.0.1 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= 2 | github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= 5 | github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 6 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 7 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 9 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= 14 | github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= 15 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 16 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 17 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 18 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 19 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 20 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 21 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 22 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 23 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 24 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 25 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 26 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 27 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 28 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 29 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 30 | github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= 31 | github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 32 | github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= 33 | github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 34 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 35 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 36 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 37 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 38 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 39 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 40 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 41 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 42 | github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= 43 | github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 44 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 45 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 46 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 47 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 48 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 49 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 52 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 53 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 54 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 55 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 58 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 59 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 60 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 61 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 62 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 63 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 64 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 65 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 66 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 67 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 68 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 69 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 70 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 71 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 72 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 73 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 74 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 75 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 76 | go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= 77 | go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= 78 | go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= 79 | go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= 80 | go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= 81 | go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= 82 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= 83 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= 84 | go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= 85 | go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= 86 | go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= 87 | go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= 88 | go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= 89 | go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= 90 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 91 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 92 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 93 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 94 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 95 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 96 | golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= 97 | golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 98 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 99 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 100 | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 101 | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 102 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 105 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 106 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 107 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 108 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 109 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 110 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 111 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 112 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 113 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 114 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 115 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 116 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 117 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 118 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "strconv" 10 | "time" 11 | 12 | "go.opentelemetry.io/contrib/propagators/b3" 13 | "go.opentelemetry.io/otel" 14 | "go.opentelemetry.io/otel/sdk/trace" 15 | oteltrace "go.opentelemetry.io/otel/trace" 16 | "go.uber.org/zap" 17 | "go.uber.org/zap/zapcore" 18 | ) 19 | 20 | // Config 配置项 21 | type Config struct { 22 | // 是否开启debug模式,未开启debug模式,仅记录错误 23 | Debug bool `yaml:"debug" mapstructure:"debug"` 24 | // 追踪使能 25 | EnableTrace bool `yaml:"enable_trace" mapstructure:"enable_trace"` 26 | // 日志输出的方式 27 | // none为不输出日志,file 为文件方式输出,console为控制台。默认为none 28 | Output string `yaml:"output" mapstructure:"output"` 29 | // 日志文件路径 30 | File string `yaml:"file" mapstructure:"file"` // 日志文件路径 31 | // 日志文件大小限制,默认最大100MB,超过将触发文件切割 32 | MaxSize int `yaml:"max_size" mapstructure:"max_size"` 33 | // 日志文件的分割文件的数量,超过的将会被删除 34 | MaxBackups int `yaml:"max_backups" mapstructure:"max_backups"` 35 | // 日志文件的保留时间,超过的将会被删除 36 | MaxAge int `yaml:"max_age" mapstructure:"max_age"` 37 | // 是否启用日志文件的压缩功能 38 | Compress bool `yaml:"compress" mapstructure:"compress"` 39 | // 是否启用日志的切割功能 40 | Rotate string `yaml:"rotate" mapstructure:"rotate"` 41 | // 日志追踪的类型,file or jaeger 42 | TracerProviderType string `yaml:"tracer_provider_type" mapstructure:"tracer_provider_type"` 43 | // 日志追踪采样的比率, 0.0-1 44 | // 0,never trace 45 | // 1,always trace 46 | TraceSampleRatio float64 `yaml:"trace_sample_ratio" mapstructure:"trace_sample_ratio"` 47 | // 最大span数量限制,当达到最大限制时,停止trace 48 | // default 200 49 | MaxSpan int `yaml:"max_span" mapstructure:"max_span"` 50 | // Jaeger server 51 | JaegerServer string `yaml:"jaeger_server" mapstructure:"jaeger_server"` 52 | JaegerUsername string `yaml:"jaeger_username" mapstructure:"jaeger_username"` 53 | JaegerPassword string `yaml:"jaeger_password" mapstructure:"jaeger_password"` 54 | } 55 | 56 | var ( 57 | enable_log bool 58 | logger *zap.Logger 59 | config Config 60 | provider *trace.TracerProvider 61 | ) 62 | 63 | // save context span 64 | type LoggerSpanContext struct { 65 | span oteltrace.Span 66 | } 67 | 68 | type LoggerContextKey int 69 | 70 | const ( 71 | loggerSpanContextKey LoggerContextKey = iota 72 | ) 73 | 74 | // LoggerInit logger初始化 75 | // 76 | // applicationAttributes 应用属性,如应用的名称,版本等 77 | // can use service.name,service.namesapce,service.instance.id,service.version, 78 | // telemetry.sdk.name,telemetry.sdk.language,telemetry.sdk.version,telemetry.auto.version 79 | // or other key that you need 80 | // 81 | // example: 82 | // Init(conf,String("sevice.name",service1)) 83 | func Init(conf Config, applicationAttributes ...Field) { 84 | otel.SetTextMapPropagator(b3.New()) 85 | config = conf 86 | if config.EnableTrace { 87 | var pd *trace.TracerProvider 88 | if conf.TracerProviderType == "" { 89 | conf.TracerProviderType = "jaeger" 90 | } 91 | if conf.TracerProviderType == "jaeger" { 92 | pd, _ = Trace{}.NewJaegerProvider(conf, applicationAttributes...) 93 | } else if conf.TracerProviderType == "file" { 94 | pd, _ = Trace{}.NewFileProvider(conf, applicationAttributes...) 95 | } else { 96 | log.Fatal("Unsupported tracerProvider type") 97 | } 98 | 99 | if pd != nil { 100 | provider = pd 101 | } 102 | } 103 | // 默认不输出日志 104 | if config.Output == "" { 105 | config.Output = "none" 106 | } 107 | if config.Output != "none" { 108 | enable_log = true 109 | if config.Output != "file" && config.Output != "console" { 110 | config.Output = "console" 111 | } 112 | zapLogger := newZapLogger(conf) 113 | logger = zapLogger.Logger 114 | zapLogger.rotateCrond(conf) 115 | } 116 | if config.MaxSpan == 0 { 117 | config.MaxSpan = 200 118 | } 119 | } 120 | 121 | // Start 启动一个span追踪 122 | // ctx 上级span 123 | // spanName span名字 124 | // spanStartOption span附带属性 125 | func Start(ctx context.Context, spanName string, spanStartOption ...Field) context.Context { 126 | var loggerSpanContext LoggerSpanContext 127 | var spanContext context.Context 128 | var enableTrace bool 129 | var span oteltrace.Span 130 | // 根据配置开启日志追踪 131 | if config.EnableTrace { 132 | enableTrace = true 133 | } 134 | spanName = spanName + " | " + time.Now().Format("15:04:05") 135 | // 根据条件 136 | // 如果未开启追踪,则返回一个nooptreace,意味着将不再追踪 137 | if enableTrace { 138 | spanContext, span = provider.Tracer("").Start(ctx, spanName, oteltrace.WithAttributes(FieldsToKeyValues(spanStartOption...)...)) 139 | loggerSpanContext.span = span 140 | } else { 141 | // 如果trace失能,将会创建一个noop traceProvider 142 | spanContext, span = oteltrace.NewNoopTracerProvider().Tracer("").Start(ctx, spanName) 143 | loggerSpanContext.span = span 144 | } 145 | startCtx := context.WithValue(spanContext, loggerSpanContextKey, loggerSpanContext) 146 | return startCtx 147 | } 148 | 149 | // SetSpanAttr 为当前的span动态设置属性 150 | func SetSpanAttr(ctx context.Context, attributes ...Field) { 151 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 152 | if !ok { 153 | return 154 | } 155 | if config.EnableTrace { 156 | loggerSpanContext.span.SetAttributes(FieldsToKeyValues(attributes...)...) 157 | } 158 | } 159 | 160 | // Debug record debug 161 | func Debug(ctx context.Context, msg string, attributes ...Field) { 162 | if enable_log { 163 | logger.Debug(msg, FieldsToZapFields(ctx, attributes...)...) 164 | } 165 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 166 | if !ok { 167 | return 168 | } 169 | if config.EnableTrace { 170 | loggerSpanContext.span.AddEvent(msg, oteltrace.WithAttributes(FieldsToKeyValues(attributes...)...)) 171 | } 172 | } 173 | 174 | // Info record info 175 | func Info(ctx context.Context, msg string, attributes ...Field) { 176 | if enable_log { 177 | logger.Info(msg, FieldsToZapFields(ctx, attributes...)...) 178 | } 179 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 180 | if !ok { 181 | return 182 | } 183 | if config.EnableTrace { 184 | loggerSpanContext.span.AddEvent(msg, oteltrace.WithAttributes(FieldsToKeyValues(attributes...)...)) 185 | } 186 | } 187 | 188 | // Warn record warn 189 | func Warn(ctx context.Context, msg string, attributes ...Field) { 190 | if enable_log { 191 | logger.Warn(msg, FieldsToZapFields(ctx, attributes...)...) 192 | } 193 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 194 | if !ok { 195 | return 196 | } 197 | if config.EnableTrace { 198 | loggerSpanContext.span.AddEvent(msg, oteltrace.WithAttributes(FieldsToKeyValues(attributes...)...)) 199 | } 200 | } 201 | 202 | // Error record error 203 | func Error(ctx context.Context, msg string, attributes ...Field) { 204 | if enable_log { 205 | logger.Error(msg, FieldsToZapFields(ctx, attributes...)...) 206 | } 207 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 208 | if !ok { 209 | return 210 | } 211 | if config.EnableTrace { 212 | loggerSpanContext.span.RecordError(errors.New(msg), oteltrace.WithAttributes(FieldsToKeyValues(attributes...)...)) 213 | } 214 | } 215 | 216 | // Fatal record fatal 217 | func Fatal(ctx context.Context, msg string, attributes ...Field) { 218 | defer func() { 219 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 220 | if !ok { 221 | return 222 | } 223 | if config.EnableTrace { 224 | // add error logs 225 | loggerSpanContext.span.RecordError(errors.New(msg), oteltrace.WithAttributes(FieldsToKeyValues(attributes...)...)) 226 | } 227 | }() 228 | if enable_log { 229 | logger.Fatal(msg, FieldsToZapFields(ctx, attributes...)...) 230 | } 231 | } 232 | 233 | // TraceID return traceID 234 | func TraceID(ctx context.Context) string { 235 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 236 | if !ok { 237 | return "" 238 | } 239 | return loggerSpanContext.span.SpanContext().TraceID().String() 240 | } 241 | 242 | // TraceID return traceID 243 | func SpanID(ctx context.Context) string { 244 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 245 | if !ok { 246 | return "" 247 | } 248 | return loggerSpanContext.span.SpanContext().SpanID().String() 249 | } 250 | 251 | // NewRootContext new root context with given traceID and spanID 252 | func NewRootContext(traceID string, spanID string) (context.Context, error) { 253 | var ( 254 | err error 255 | tID oteltrace.TraceID 256 | sID oteltrace.SpanID 257 | ctx = context.Background() 258 | ) 259 | // set traceID and spanID 260 | { 261 | tID, err = oteltrace.TraceIDFromHex(traceID) 262 | if err != nil { 263 | return ctx, errors.New("invalid traceID") 264 | } 265 | sID, err = oteltrace.SpanIDFromHex(spanID) 266 | if err != nil { 267 | return ctx, errors.New("invalid spanID") 268 | } 269 | } 270 | // generate root ctx by spanContextConfig 271 | sc := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ 272 | TraceID: tID, 273 | SpanID: sID, 274 | }) 275 | ctx = oteltrace.ContextWithRemoteSpanContext(ctx, sc) 276 | return ctx, nil 277 | } 278 | 279 | // GenTraceID generate traceID 280 | // current timestamp with 8 rand bytes 281 | func GenTraceID() string { 282 | stime := strconv.FormatInt(time.Now().UnixNano(), 16) 283 | paddingLen := 16 - len(stime) 284 | for i := 0; i < paddingLen; i++ { 285 | stime = stime + "0" 286 | } 287 | return stime + randString(16) 288 | } 289 | 290 | // GenSpanID gererate spanID 291 | func GenSpanID() string { 292 | return randString(16) 293 | } 294 | 295 | // End end trace 296 | func End(ctx context.Context) { 297 | if err := recover(); err != nil { 298 | logger.Error("panic", zap.String("recover", fmt.Sprint(err)), zap.Stack("stack")) 299 | } 300 | loggerSpanContext, ok := ctx.Value(loggerSpanContextKey).(LoggerSpanContext) 301 | if !ok { 302 | return 303 | } 304 | if config.EnableTrace { 305 | loggerSpanContext.span.End() 306 | } 307 | } 308 | 309 | // FieldsToZapFields 310 | func FieldsToZapFields(ctx context.Context, fields ...Field) []zapcore.Field { 311 | kvs := []zapcore.Field{} 312 | if traceID := TraceID(ctx); traceID != "" { 313 | kvs = append(kvs, zap.String("trace_id", traceID)) 314 | } 315 | if spanID := SpanID(ctx); spanID != "" { 316 | kvs = append(kvs, zap.String("span_id", spanID)) 317 | } 318 | for _, f := range fields { 319 | switch f.Type { 320 | case boolType: 321 | kvs = append(kvs, zap.Bool(f.Key, f.Bool)) 322 | case boolSliceType: 323 | kvs = append(kvs, zap.Bools(f.Key, f.Bools)) 324 | case intType: 325 | kvs = append(kvs, zap.Int(f.Key, f.Integer)) 326 | case intSliceType: 327 | kvs = append(kvs, zap.Ints(f.Key, f.Integers)) 328 | case int64Type: 329 | kvs = append(kvs, zap.Int64(f.Key, f.Integer64)) 330 | case int64SliceType: 331 | kvs = append(kvs, zap.Int64s(f.Key, f.Integer64s)) 332 | case float64Type: 333 | kvs = append(kvs, zap.Float64(f.Key, f.Float64)) 334 | case float64SliceType: 335 | kvs = append(kvs, zap.Float64s(f.Key, f.Float64s)) 336 | case stringType: 337 | kvs = append(kvs, zap.String(f.Key, f.String)) 338 | case stringSliceType: 339 | kvs = append(kvs, zap.Strings(f.Key, f.Strings)) 340 | case stringerType: 341 | kvs = append(kvs, zap.String(f.Key, f.String)) 342 | case anyType: 343 | kvs = append(kvs, zap.Any(f.Key, f.Any)) 344 | } 345 | } 346 | return kvs 347 | } 348 | 349 | // RandString 生成随机字符串 350 | func randString(len int) string { 351 | bytes := []byte{} 352 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 353 | seed := "0123456789abcdef" 354 | for i := 0; i < len; i++ { 355 | bytes = append(bytes, seed[r.Intn(15)]) 356 | } 357 | return string(bytes) 358 | } 359 | -------------------------------------------------------------------------------- /propagation.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/itmisx/logx/propagation/extract" 9 | "github.com/itmisx/logx/propagation/inject" 10 | ) 11 | 12 | // HTTPInject inject spanContext 13 | func HttpInject(ctx context.Context, request *http.Request) error { 14 | return inject.HttpInject(ctx, request) 15 | } 16 | 17 | // GinMiddleware extract spanContext 18 | func GinMiddleware(service string) gin.HandlerFunc { 19 | return extract.GinMiddleware(service) 20 | } 21 | -------------------------------------------------------------------------------- /propagation/extract/gin.go: -------------------------------------------------------------------------------- 1 | package extract 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gin-gonic/gin" 7 | "go.opentelemetry.io/otel" 8 | "go.opentelemetry.io/otel/attribute" 9 | "go.opentelemetry.io/otel/propagation" 10 | semconv "go.opentelemetry.io/otel/semconv/v1.4.0" 11 | oteltrace "go.opentelemetry.io/otel/trace" 12 | ) 13 | 14 | const ( 15 | tracerKey = "otel-go-contrib-tracer" 16 | tracerName = "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" 17 | ) 18 | 19 | // Middleware returns middleware that will trace incoming requests. 20 | // The service parameter should describe the name of the (virtual) 21 | // server handling the request. 22 | func GinMiddleware(service string, opts ...Option) gin.HandlerFunc { 23 | cfg := config{} 24 | for _, opt := range opts { 25 | opt.apply(&cfg) 26 | } 27 | if cfg.TracerProvider == nil { 28 | cfg.TracerProvider = otel.GetTracerProvider() 29 | } 30 | tracer := cfg.TracerProvider.Tracer( 31 | tracerName, 32 | // oteltrace.WithInstrumentationVersion(SemVersion()), 33 | ) 34 | if cfg.Propagators == nil { 35 | cfg.Propagators = otel.GetTextMapPropagator() 36 | } 37 | return func(c *gin.Context) { 38 | c.Set(tracerKey, tracer) 39 | savedCtx := c.Request.Context() 40 | defer func() { 41 | c.Request = c.Request.WithContext(savedCtx) 42 | }() 43 | ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header)) 44 | opts := []oteltrace.SpanStartOption{ 45 | oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", c.Request)...), 46 | oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(c.Request)...), 47 | oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(service, c.FullPath(), c.Request)...), 48 | oteltrace.WithSpanKind(oteltrace.SpanKindServer), 49 | } 50 | spanName := c.FullPath() 51 | if spanName == "" { 52 | spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method) 53 | } 54 | ctx, span := tracer.Start(ctx, spanName, opts...) 55 | defer span.End() 56 | 57 | // pass the span through the request context 58 | c.Request = c.Request.WithContext(ctx) 59 | 60 | // serve the request to the next middleware 61 | c.Next() 62 | 63 | status := c.Writer.Status() 64 | attrs := semconv.HTTPAttributesFromHTTPStatusCode(status) 65 | spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCode(status) 66 | span.SetAttributes(attrs...) 67 | span.SetStatus(spanStatus, spanMessage) 68 | if len(c.Errors) > 0 { 69 | span.SetAttributes(attribute.String("gin.errors", c.Errors.String())) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /propagation/extract/options.go: -------------------------------------------------------------------------------- 1 | package extract 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/propagation" 5 | oteltrace "go.opentelemetry.io/otel/trace" 6 | ) 7 | 8 | type config struct { 9 | TracerProvider oteltrace.TracerProvider 10 | Propagators propagation.TextMapPropagator 11 | } 12 | 13 | // Option specifies instrumentation configuration options. 14 | type Option interface { 15 | apply(*config) 16 | } 17 | 18 | type optionFunc func(*config) 19 | 20 | func (o optionFunc) apply(c *config) { 21 | o(c) 22 | } 23 | 24 | // WithPropagators specifies propagators to use for extracting 25 | // information from the HTTP requests. If none are specified, global 26 | // ones will be used. 27 | func WithPropagators(propagators propagation.TextMapPropagator) Option { 28 | return optionFunc(func(cfg *config) { 29 | if propagators != nil { 30 | cfg.Propagators = propagators 31 | } 32 | }) 33 | } 34 | 35 | // WithTracerProvider specifies a tracer provider to use for creating a tracer. 36 | // If none is specified, the global provider is used. 37 | func WithTracerProvider(provider oteltrace.TracerProvider) Option { 38 | return optionFunc(func(cfg *config) { 39 | if provider != nil { 40 | cfg.TracerProvider = provider 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /propagation/gintrace_test.go: -------------------------------------------------------------------------------- 1 | package propagation 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/itmisx/logx/propagation/extract" 10 | "github.com/stretchr/testify/assert" 11 | 12 | "github.com/gin-gonic/gin" 13 | "go.opentelemetry.io/contrib/propagators/b3" 14 | "go.opentelemetry.io/otel" 15 | "go.opentelemetry.io/otel/propagation" 16 | "go.opentelemetry.io/otel/trace" 17 | oteltrace "go.opentelemetry.io/otel/trace" 18 | ) 19 | 20 | func TestGetSpanNotInstrumented(t *testing.T) { 21 | router := gin.New() 22 | router.GET("/ping", func(c *gin.Context) { 23 | ctx := c.Request.Context() 24 | // Assert we don't have a span on the context. 25 | span := oteltrace.SpanFromContext(ctx) 26 | ok := !span.SpanContext().IsValid() 27 | assert.True(t, ok) 28 | _, _ = c.Writer.Write([]byte("ok")) 29 | }) 30 | r := httptest.NewRequest("GET", "/ping", nil) 31 | w := httptest.NewRecorder() 32 | router.ServeHTTP(w, r) 33 | response := w.Result() 34 | assert.Equal(t, http.StatusOK, response.StatusCode) 35 | } 36 | 37 | func TestPropagationWithGlobalPropagators(t *testing.T) { 38 | provider := trace.NewNoopTracerProvider() 39 | // 设置propagator的类型为b3 40 | otel.SetTextMapPropagator(b3.New()) 41 | 42 | r := httptest.NewRequest("GET", "/user/123", nil) 43 | w := httptest.NewRecorder() 44 | 45 | ctx := context.Background() 46 | sc := trace.NewSpanContext(trace.SpanContextConfig{ 47 | TraceID: trace.TraceID{0x01}, 48 | SpanID: trace.SpanID{0x01}, 49 | }) 50 | ctx = trace.ContextWithRemoteSpanContext(ctx, sc) 51 | ctx, _ = provider.Tracer("tracerName").Start(ctx, "test") 52 | 53 | // 注入span-context 54 | otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) 55 | 56 | router := gin.New() 57 | 58 | // 中间件解析span-context 59 | router.Use(extract.GinMiddleware("foobar")) 60 | router.GET("/user/:id", func(c *gin.Context) { 61 | span := oteltrace.SpanFromContext(c.Request.Context()) 62 | assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) 63 | assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) 64 | }) 65 | 66 | router.ServeHTTP(w, r) 67 | } 68 | -------------------------------------------------------------------------------- /propagation/inject/http.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | b3prop "go.opentelemetry.io/contrib/propagators/b3" 8 | "go.opentelemetry.io/otel" 9 | "go.opentelemetry.io/otel/propagation" 10 | ) 11 | 12 | func HttpInject(ctx context.Context, request *http.Request) error { 13 | otel.SetTextMapPropagator(b3prop.New()) 14 | otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(request.Header)) 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /test/logger_test.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/itmisx/logx" 12 | "github.com/itmisx/logx/propagation/extract" 13 | ) 14 | 15 | func TestTrace(*testing.T) { 16 | conf := logx.Config{ 17 | Debug: true, 18 | EnableTrace: true, 19 | TracerProviderType: "file", 20 | } 21 | 22 | logx.Init(conf, logx.Int64("ID", 1)) 23 | ctx1 := logx.Start(context.Background(), "test1") 24 | defer logx.End(ctx1) 25 | logx.Warn(ctx1, "test info", logx.String("name1", "test")) 26 | logx.Error(ctx1, "test info", logx.Bool("sex1", true)) 27 | logx.Error(ctx1, "test info", logx.Int("age1", 30)) 28 | 29 | ctx2 := logx.Start(ctx1, "test2", logx.Int("spanNum", 2)) 30 | fmt.Println(logx.TraceID(ctx2)) 31 | logx.Error(ctx2, "test2 info", logx.String("name2", "test")) 32 | logx.Info(ctx2, "test2 info", logx.Bool("sex2", true)) 33 | logx.Info(ctx2, "test2 info", logx.Int("age2", 30)) 34 | logx.End(ctx2) 35 | } 36 | 37 | func TestCustomizeTraceID(*testing.T) { 38 | conf := logx.Config{ 39 | Debug: true, 40 | EnableTrace: true, 41 | Output: "file", 42 | File: "./run.log", 43 | Rotate: "0 * * * * *", 44 | TracerProviderType: "file", 45 | } 46 | logx.Init(conf, logx.Int64("ID", 1)) 47 | traceID := logx.GenTraceID() 48 | spanID := logx.GenSpanID() 49 | ctx, err := logx.NewRootContext(traceID, spanID) 50 | if err != nil { 51 | fmt.Println(err) 52 | } 53 | ctx = logx.Start(ctx, "test1") 54 | logx.Warn(ctx, "test info", logx.String("name1", "test"), logx.Any("conf", conf)) 55 | logx.Error(ctx, "test info", logx.Bool("sex1", true)) 56 | logx.Error(ctx, "test info", logx.Int("age1", 30)) 57 | logx.End(ctx) 58 | 59 | <-make(chan bool) 60 | } 61 | 62 | func TestLog(*testing.T) { 63 | conf := logx.Config{ 64 | Debug: true, 65 | Output: "file", 66 | Rotate: "0 * * * * *", 67 | } 68 | ctx := context.Background() 69 | logx.Init(conf) 70 | for { 71 | logx.Info(ctx, "log info") 72 | logx.Warn(ctx, "log warn") 73 | logx.Error(ctx, "log error") 74 | time.Sleep(time.Second * 5) 75 | } 76 | } 77 | 78 | func TestPropagationWithGlobalPropagators(t *testing.T) { 79 | // logx init 80 | { 81 | conf := logx.Config{ 82 | Debug: true, 83 | EnableTrace: true, 84 | TracerProviderType: "file", 85 | TraceSampleRatio: 1, 86 | JaegerServer: "http://120.77.213.80:14268/api/traces", 87 | } 88 | logx.Init(conf, logx.Int64("ID", 1)) 89 | } 90 | 91 | // new gin server 92 | { 93 | router := gin.Default() 94 | router.Static("/upload", "./upload") // 上传目录 95 | router.Use(extract.GinMiddleware("")) 96 | router.GET("/user/:id", func(c *gin.Context) { 97 | ctx := logx.Start(c.Request.Context(), "123") 98 | logx.Info(ctx, "456") 99 | logx.End(ctx) 100 | }) 101 | router.POST("/open-api/control/signal-test", func(c *gin.Context) { 102 | ctx := logx.Start(c.Request.Context(), "123") 103 | logx.Info(ctx, "789") 104 | logx.End(ctx) 105 | }) 106 | go func() { 107 | router.Run() 108 | }() 109 | } 110 | 111 | // wait gin startup 112 | { 113 | time.Sleep(time.Second * 1) 114 | } 115 | 116 | // test propagation 117 | { 118 | ctx := logx.Start(context.Background(), "123") 119 | logx.Info(ctx, "123") 120 | logx.End(ctx) 121 | 122 | request, _ := http.NewRequest("GET", "http://localhost:8080/test", nil) 123 | logx.HttpInject(ctx, request) 124 | client := &http.Client{} 125 | client.Do(request) 126 | } 127 | 128 | // wait 129 | <-make(chan bool) 130 | } 131 | 132 | func TestMaxspan(*testing.T) { 133 | conf := logx.Config{ 134 | Debug: true, 135 | EnableTrace: true, 136 | TracerProviderType: "file", 137 | } 138 | 139 | logx.Init(conf, logx.Int64("ID", 1)) 140 | ctx1 := logx.Start(context.Background(), "test1") 141 | defer logx.End(ctx1) 142 | logx.Warn(ctx1, "test info", logx.String("name1", "test")) 143 | logx.Error(ctx1, "test info", logx.Bool("sex1", true)) 144 | logx.Error(ctx1, "test info", logx.Int("age1", 30)) 145 | for i := 0; i < 300; i++ { 146 | ctx2 := logx.Start(ctx1, "test2", logx.Int("spanNum", 2)) 147 | fmt.Println(logx.TraceID(ctx2)) 148 | logx.Error(ctx2, "test2 info", logx.String("name2", "test")) 149 | logx.Info(ctx2, "test2 info", logx.Bool("sex2", true)) 150 | logx.Info(ctx2, "test2 info", logx.Int("age2", 30)) 151 | logx.End(ctx2) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /trace.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "os" 7 | 8 | "go.opentelemetry.io/otel" 9 | "go.opentelemetry.io/otel/attribute" 10 | "go.opentelemetry.io/otel/exporters/jaeger" 11 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 12 | "go.opentelemetry.io/otel/sdk/resource" 13 | "go.opentelemetry.io/otel/sdk/trace" 14 | semconv "go.opentelemetry.io/otel/semconv/v1.4.0" 15 | ) 16 | 17 | type Trace struct{} 18 | 19 | // NewJaegerProvider 20 | func (tx Trace) NewJaegerProvider(conf Config, 21 | attributes ...Field, 22 | ) (*trace.TracerProvider, error) { 23 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(conf.JaegerServer), 24 | jaeger.WithUsername(conf.JaegerUsername), 25 | jaeger.WithPassword(conf.JaegerPassword))) 26 | if err != nil { 27 | return nil, err 28 | } 29 | if conf.TraceSampleRatio > 1 { 30 | conf.TraceSampleRatio = 1 31 | } 32 | if conf.TraceSampleRatio < 0 { 33 | conf.TraceSampleRatio = 0 34 | } 35 | tp := trace.NewTracerProvider( 36 | // Always be sure to batch in production. 37 | trace.WithBatcher(exp), 38 | // Record information about this application in an Resource. 39 | trace.WithResource(resource.NewWithAttributes( 40 | semconv.SchemaURL, 41 | FieldsToKeyValues(attributes...)..., 42 | )), 43 | trace.WithSampler(trace.TraceIDRatioBased(conf.TraceSampleRatio)), 44 | ) 45 | 46 | otel.SetTracerProvider(tp) 47 | return tp, nil 48 | } 49 | 50 | // NewFileProvider 51 | func (tx Trace) NewFileProvider(conf Config, attributes ...Field) (*trace.TracerProvider, error) { 52 | f, _ := os.Create("trace.txt") 53 | exp, _ := newExporter(f) 54 | tp := trace.NewTracerProvider( 55 | // Always be sure to batch in production. 56 | trace.WithBatcher(exp), 57 | // Record information about this application in an Resource. 58 | trace.WithResource(resource.NewWithAttributes( 59 | semconv.SchemaURL, 60 | FieldsToKeyValues(attributes...)..., 61 | )), 62 | trace.WithSampler(trace.AlwaysSample()), 63 | ) 64 | 65 | otel.SetTracerProvider(tp) 66 | return tp, nil 67 | } 68 | 69 | func newExporter(w io.Writer) (trace.SpanExporter, error) { 70 | return stdouttrace.New( 71 | stdouttrace.WithWriter(w), 72 | // Use human-readable output. 73 | stdouttrace.WithPrettyPrint(), 74 | // Do not print timestamps for the demo. 75 | stdouttrace.WithoutTimestamps(), 76 | ) 77 | } 78 | 79 | // FieldsToKeyValue 80 | func FieldsToKeyValues(fields ...Field) []attribute.KeyValue { 81 | kvs := []attribute.KeyValue{} 82 | for _, f := range fields { 83 | switch f.Type { 84 | case boolType: 85 | kvs = append(kvs, attribute.Bool(f.Key, f.Bool)) 86 | case boolSliceType: 87 | kvs = append(kvs, attribute.BoolSlice(f.Key, f.Bools)) 88 | case intType: 89 | kvs = append(kvs, attribute.Int(f.Key, f.Integer)) 90 | case intSliceType: 91 | kvs = append(kvs, attribute.IntSlice(f.Key, f.Integers)) 92 | case int64Type: 93 | kvs = append(kvs, attribute.Int64(f.Key, f.Integer64)) 94 | case int64SliceType: 95 | kvs = append(kvs, attribute.Int64Slice(f.Key, f.Integer64s)) 96 | case float64Type: 97 | kvs = append(kvs, attribute.Float64(f.Key, f.Float64)) 98 | case float64SliceType: 99 | kvs = append(kvs, attribute.Float64Slice(f.Key, f.Float64s)) 100 | case stringType: 101 | kvs = append(kvs, attribute.String(f.Key, f.String)) 102 | case stringSliceType: 103 | kvs = append(kvs, attribute.StringSlice(f.Key, f.Strings)) 104 | case stringerType: 105 | stringer := stringer{str: f.String} 106 | kvs = append(kvs, attribute.Stringer(f.Key, stringer)) 107 | case anyType: 108 | if str, err := json.Marshal(f.Any); err == nil { 109 | kvs = append(kvs, attribute.String(f.Key, string(str))) 110 | } 111 | } 112 | } 113 | return kvs 114 | } 115 | 116 | // stringer fmt.Stringer 117 | type stringer struct { 118 | str string 119 | } 120 | 121 | func (s stringer) String() string { 122 | return s.str 123 | } 124 | -------------------------------------------------------------------------------- /zap_log.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "os" 5 | "sync" 6 | "time" 7 | 8 | "github.com/robfig/cron/v3" 9 | "go.uber.org/zap" 10 | "go.uber.org/zap/zapcore" 11 | "gopkg.in/natefinch/lumberjack.v2" 12 | ) 13 | 14 | // var zlogger *zap.Logger 15 | type zapLogger struct { 16 | Logger *zap.Logger 17 | lumLogger *lumberjack.Logger 18 | } 19 | 20 | var rotateCrondOnce sync.Once 21 | 22 | // newZLogger init a zap logger 23 | func newZapLogger(conf Config) zapLogger { 24 | // maxage default 7 days 25 | if config.MaxAge == 0 { 26 | config.MaxAge = 7 27 | } 28 | if conf.File == "" { 29 | conf.File = "./logs/run.log" 30 | } 31 | if conf.MaxBackups == 0 { 32 | conf.MaxBackups = 15 33 | } 34 | // log rolling config 35 | hook := lumberjack.Logger{ 36 | Filename: conf.File, 37 | MaxSize: conf.MaxSize, 38 | MaxBackups: conf.MaxBackups, 39 | MaxAge: conf.MaxAge, 40 | LocalTime: true, 41 | Compress: conf.Compress, 42 | } 43 | // Multi writer 44 | // lumberWriter and consoleWrite 45 | var multiWriter zapcore.WriteSyncer 46 | var writeSyncers []zapcore.WriteSyncer 47 | if conf.Output == "file" { 48 | writeSyncers = append(writeSyncers, zapcore.AddSync(&hook)) 49 | } else { 50 | writeSyncers = append(writeSyncers, zapcore.AddSync(os.Stdout)) 51 | } 52 | if len(writeSyncers) > 0 { 53 | multiWriter = zapcore.NewMultiWriteSyncer(writeSyncers...) 54 | } 55 | 56 | // encoderConfig 57 | encoderConfig := zapcore.EncoderConfig{ 58 | TimeKey: "time", 59 | LevelKey: "level", 60 | NameKey: "logger", 61 | CallerKey: "caller", 62 | FunctionKey: zapcore.OmitKey, 63 | MessageKey: "msg", 64 | StacktraceKey: "stacktrace", 65 | LineEnding: zapcore.DefaultLineEnding, 66 | EncodeLevel: zapcore.LowercaseLevelEncoder, 67 | EncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) { 68 | encoder.AppendString(t.Format("2006-01-02 15:04:05")) 69 | }, 70 | EncodeDuration: zapcore.SecondsDurationEncoder, 71 | EncodeCaller: zapcore.FullCallerEncoder, 72 | } 73 | // logLevel 74 | // Encoder console or json 75 | enco := zapcore.NewJSONEncoder(encoderConfig) 76 | var atomicLevel zap.AtomicLevel 77 | if conf.Debug { 78 | atomicLevel = zap.NewAtomicLevelAt(zap.DebugLevel) 79 | } else { 80 | atomicLevel = zap.NewAtomicLevelAt(zap.ErrorLevel) 81 | } 82 | 83 | // new core config 84 | core := zapcore.NewCore( 85 | enco, 86 | multiWriter, 87 | atomicLevel, 88 | ) 89 | 90 | // new logger 91 | logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) 92 | return zapLogger{ 93 | Logger: logger, 94 | lumLogger: &hook, 95 | } 96 | } 97 | 98 | // rotateCrond 99 | func (zl zapLogger) rotateCrond(conf Config) { 100 | if conf.Rotate != "" { 101 | rotateCrondOnce.Do(func() { 102 | cron := cron.New(cron.WithSeconds()) 103 | cron.AddFunc(conf.Rotate, func() { 104 | zl.lumLogger.Rotate() 105 | }) 106 | cron.Start() 107 | }) 108 | } 109 | } 110 | --------------------------------------------------------------------------------