From aecbfb39bbfe65fb76f22e7b2790e084dab31f5c Mon Sep 17 00:00:00 2001 From: copy rogers <40619032+rogerogers@users.noreply.github.com> Date: Fri, 4 Nov 2022 15:51:15 +0800 Subject: [PATCH] feat: add zap log (#3) Signed-off-by: rogerogers Signed-off-by: rogerogers --- licenses/LICENSE-testify.txt | 21 ++++ licenses/LICENSE-zap.txt | 19 +++ zap/README.md | 41 +++++++ zap/go.mod | 9 ++ zap/go.sum | 99 ++++++++++++++++ zap/logger.go | 218 ++++++++++++++++++++++++++++++++++ zap/logger_test.go | 219 +++++++++++++++++++++++++++++++++++ zap/option.go | 96 +++++++++++++++ 8 files changed, 722 insertions(+) create mode 100644 licenses/LICENSE-testify.txt create mode 100644 licenses/LICENSE-zap.txt create mode 100644 zap/README.md create mode 100644 zap/go.mod create mode 100644 zap/go.sum create mode 100644 zap/logger.go create mode 100644 zap/logger_test.go create mode 100644 zap/option.go diff --git a/licenses/LICENSE-testify.txt b/licenses/LICENSE-testify.txt new file mode 100644 index 0000000..0be77a0 --- /dev/null +++ b/licenses/LICENSE-testify.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. + +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. \ No newline at end of file diff --git a/licenses/LICENSE-zap.txt b/licenses/LICENSE-zap.txt new file mode 100644 index 0000000..2cf1638 --- /dev/null +++ b/licenses/LICENSE-zap.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 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. \ No newline at end of file diff --git a/zap/README.md b/zap/README.md new file mode 100644 index 0000000..f440ad5 --- /dev/null +++ b/zap/README.md @@ -0,0 +1,41 @@ +# Hertz zap (This is a community driven project) + +## Introduction + +This is a logger library that uses [zap](https://github.com/uber-go/zap) to implement the [Hertz logger interface](https://www.cloudwego.io/docs/hertz/tutorials/framework-exten/log/) + +## Usage + +Download and install it: + +```go +go get github.com/hertz-contrib/logger/zap +``` + +Import it in your code: + +```go +import hertzzap github.com/hertz-contrib/logger/ +``` + +Simple Example: + +```go +package main + +import ( + "github.com/cloudwego/hertz/pkg/common/hlog" + hertzzap "github.com/hertz-contrib/logger/zap" +) + +func main() { + logger := hertzzap.NewLogger() + hlog.SetLogger(logger) + + // ... + + hlog.Infof("hello %s", "hertz") +} +``` + +> For some reason, zap don't support log context info yet. diff --git a/zap/go.mod b/zap/go.mod new file mode 100644 index 0000000..1a52840 --- /dev/null +++ b/zap/go.mod @@ -0,0 +1,9 @@ +module github.com/hertz-contrib/logger/zap + +go 1.16 + +require ( + github.com/cloudwego/hertz v0.3.2 + github.com/stretchr/testify v1.8.0 + go.uber.org/zap v1.23.0 +) diff --git a/zap/go.sum b/zap/go.sum new file mode 100644 index 0000000..a8070d4 --- /dev/null +++ b/zap/go.sum @@ -0,0 +1,99 @@ +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM= +github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= +github.com/bytedance/sonic v1.3.5/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/cloudwego/hertz v0.3.2 h1:YwGmozomDPiJhKfKjKggMUmfNBNHR9iQGVLjY3wJpsY= +github.com/cloudwego/hertz v0.3.2/go.mod h1:hnv3B7eZ6kMv7CKFHT2OC4LU0mA4s5XPyu/SbixLcrU= +github.com/cloudwego/netpoll v0.2.6/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E= +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/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/zap/logger.go b/zap/logger.go new file mode 100644 index 0000000..4611a1a --- /dev/null +++ b/zap/logger.go @@ -0,0 +1,218 @@ +// Copyright 2022 CloudWeGo Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zap + +import ( + "context" + "io" + + "github.com/cloudwego/hertz/pkg/common/hlog" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var _ hlog.FullLogger = (*Logger)(nil) + +type Logger struct { + l *zap.SugaredLogger + config *config +} + +func NewLogger(opts ...Option) *Logger { + config := defaultConfig() + + // apply options + for _, opt := range opts { + opt.apply(config) + } + + logger := zap.New( + zapcore.NewCore(config.coreConfig.enc, config.coreConfig.ws, config.coreConfig.lvl), + config.zapOpts...) + + return &Logger{ + l: logger.Sugar(), + config: config, + } +} + +func (l *Logger) Log(level hlog.Level, kvs ...interface{}) { + switch level { + case hlog.LevelTrace, hlog.LevelDebug: + l.l.Debug(kvs...) + case hlog.LevelInfo: + l.l.Info(kvs...) + case hlog.LevelNotice, hlog.LevelWarn: + l.l.Warn(kvs...) + case hlog.LevelError: + l.l.Error(kvs...) + case hlog.LevelFatal: + l.l.Fatal(kvs...) + default: + l.l.Warn(kvs...) + } +} + +func (l *Logger) Logf(level hlog.Level, format string, kvs ...interface{}) { + logger := l.l.With() + switch level { + case hlog.LevelTrace, hlog.LevelDebug: + logger.Debugf(format, kvs...) + case hlog.LevelInfo: + logger.Infof(format, kvs...) + case hlog.LevelNotice, hlog.LevelWarn: + logger.Warnf(format, kvs...) + case hlog.LevelError: + logger.Errorf(format, kvs...) + case hlog.LevelFatal: + logger.Fatalf(format, kvs...) + default: + logger.Warnf(format, kvs...) + } +} + +func (l *Logger) CtxLogf(level hlog.Level, ctx context.Context, format string, kvs ...interface{}) { + switch level { + case hlog.LevelDebug, hlog.LevelTrace: + l.l.Debugf(format, kvs...) + case hlog.LevelInfo: + l.l.Infof(format, kvs...) + case hlog.LevelNotice, hlog.LevelWarn: + l.l.Warnf(format, kvs...) + case hlog.LevelError: + l.l.Errorf(format, kvs...) + case hlog.LevelFatal: + l.l.Fatalf(format, kvs...) + default: + l.l.Warnf(format, kvs...) + } +} + +func (l *Logger) Trace(v ...interface{}) { + l.Log(hlog.LevelTrace, v...) +} + +func (l *Logger) Debug(v ...interface{}) { + l.Log(hlog.LevelDebug, v...) +} + +func (l *Logger) Info(v ...interface{}) { + l.Log(hlog.LevelInfo, v...) +} + +func (l *Logger) Notice(v ...interface{}) { + l.Log(hlog.LevelNotice, v...) +} + +func (l *Logger) Warn(v ...interface{}) { + l.Log(hlog.LevelWarn, v...) +} + +func (l *Logger) Error(v ...interface{}) { + l.Log(hlog.LevelError, v...) +} + +func (l *Logger) Fatal(v ...interface{}) { + l.Log(hlog.LevelFatal, v...) +} + +func (l *Logger) Tracef(format string, v ...interface{}) { + l.Logf(hlog.LevelTrace, format, v...) +} + +func (l *Logger) Debugf(format string, v ...interface{}) { + l.Logf(hlog.LevelDebug, format, v...) +} + +func (l *Logger) Infof(format string, v ...interface{}) { + l.Logf(hlog.LevelInfo, format, v...) +} + +func (l *Logger) Noticef(format string, v ...interface{}) { + l.Logf(hlog.LevelWarn, format, v...) +} + +func (l *Logger) Warnf(format string, v ...interface{}) { + l.Logf(hlog.LevelWarn, format, v...) +} + +func (l *Logger) Errorf(format string, v ...interface{}) { + l.Logf(hlog.LevelError, format, v...) +} + +func (l *Logger) Fatalf(format string, v ...interface{}) { + l.Logf(hlog.LevelFatal, format, v...) +} + +func (l *Logger) CtxTracef(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelDebug, ctx, format, v...) +} + +func (l *Logger) CtxDebugf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelDebug, ctx, format, v...) +} + +func (l *Logger) CtxInfof(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelInfo, ctx, format, v...) +} + +func (l *Logger) CtxNoticef(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelWarn, ctx, format, v...) +} + +func (l *Logger) CtxWarnf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelWarn, ctx, format, v...) +} + +func (l *Logger) CtxErrorf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelError, ctx, format, v...) +} + +func (l *Logger) CtxFatalf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelFatal, ctx, format, v...) +} + +func (l *Logger) SetLevel(level hlog.Level) { + var lvl zapcore.Level + switch level { + case hlog.LevelTrace, hlog.LevelDebug: + lvl = zap.DebugLevel + case hlog.LevelInfo: + lvl = zap.InfoLevel + case hlog.LevelWarn, hlog.LevelNotice: + lvl = zap.WarnLevel + case hlog.LevelError: + lvl = zap.ErrorLevel + case hlog.LevelFatal: + lvl = zap.FatalLevel + default: + lvl = zap.WarnLevel + } + l.config.coreConfig.lvl.SetLevel(lvl) +} + +func (l *Logger) SetOutput(writer io.Writer) { + ws := zapcore.AddSync(writer) + log := zap.New( + zapcore.NewCore(l.config.coreConfig.enc, ws, l.config.coreConfig.lvl), + l.config.zapOpts..., + ).Sugar() + l.config.coreConfig.ws = ws + l.l = log +} + +func (l *Logger) Sync() { + _ = l.l.Sync() +} diff --git a/zap/logger_test.go b/zap/logger_test.go new file mode 100644 index 0000000..3c586ae --- /dev/null +++ b/zap/logger_test.go @@ -0,0 +1,219 @@ +// Copyright 2022 CloudWeGo Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zap + +import ( + "bytes" + "context" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/cloudwego/hertz/pkg/common/hlog" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// testEncoderConfig encoder config for testing, copy from zap +func testEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "name", + TimeKey: "ts", + CallerKey: "caller", + FunctionKey: "func", + StacktraceKey: "stacktrace", + LineEnding: "\n", + EncodeTime: zapcore.EpochTimeEncoder, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +// humanEncoderConfig copy from zap +func humanEncoderConfig() zapcore.EncoderConfig { + cfg := testEncoderConfig() + cfg.EncodeTime = zapcore.ISO8601TimeEncoder + cfg.EncodeLevel = zapcore.CapitalLevelEncoder + cfg.EncodeDuration = zapcore.StringDurationEncoder + return cfg +} + +// TestLogger test logger work with hertz +func TestLogger(t *testing.T) { + buf := new(bytes.Buffer) + + logger := NewLogger(WithZapOptions(zap.WithFatalHook(zapcore.WriteThenPanic))) + defer logger.Sync() + + hlog.SetLogger(logger) + hlog.SetOutput(buf) + hlog.SetLevel(hlog.LevelDebug) + + type logMap map[string]string + + logTestSlice := []logMap{ + { + "logMessage": "this is a trace log", + "formatLogMessage": "this is a trace log: %s", + "logLevel": "Trace", + "zapLogLevel": "debug", + }, + { + "logMessage": "this is a debug log", + "formatLogMessage": "this is a debug log: %s", + "logLevel": "Debug", + "zapLogLevel": "debug", + }, + { + "logMessage": "this is a info log", + "formatLogMessage": "this is a info log: %s", + "logLevel": "Info", + "zapLogLevel": "info", + }, + { + "logMessage": "this is a notice log", + "formatLogMessage": "this is a notice log: %s", + "logLevel": "Notice", + "zapLogLevel": "warn", + }, + { + "logMessage": "this is a warn log", + "formatLogMessage": "this is a warn log: %s", + "logLevel": "Warn", + "zapLogLevel": "warn", + }, + { + "logMessage": "this is a error log", + "formatLogMessage": "this is a error log: %s", + "logLevel": "Error", + "zapLogLevel": "error", + }, + { + "logMessage": "this is a fatal log", + "formatLogMessage": "this is a fatal log: %s", + "logLevel": "Fatal", + "zapLogLevel": "fatal", + }, + } + + testHertzLogger := reflect.ValueOf(logger) + + for _, v := range logTestSlice { + t.Run(v["logLevel"], func(t *testing.T) { + if v["logLevel"] == "Fatal" { + defer func() { + assert.Equal(t, "this is a fatal log", recover()) + }() + } + logFunc := testHertzLogger.MethodByName(v["logLevel"]) + logFunc.Call([]reflect.Value{ + reflect.ValueOf(v["logMessage"]), + }) + assert.Contains(t, buf.String(), v["logMessage"]) + assert.Contains(t, buf.String(), v["zapLogLevel"]) + + buf.Reset() + + logfFunc := testHertzLogger.MethodByName(fmt.Sprintf("%sf", v["logLevel"])) + logfFunc.Call([]reflect.Value{ + reflect.ValueOf(v["formatLogMessage"]), + reflect.ValueOf(v["logLevel"]), + }) + assert.Contains(t, buf.String(), fmt.Sprintf(v["formatLogMessage"], v["logLevel"])) + assert.Contains(t, buf.String(), v["zapLogLevel"]) + + buf.Reset() + + ctx := context.Background() + ctxLogfFunc := testHertzLogger.MethodByName(fmt.Sprintf("Ctx%sf", v["logLevel"])) + ctxLogfFunc.Call([]reflect.Value{ + reflect.ValueOf(ctx), + reflect.ValueOf(v["formatLogMessage"]), + reflect.ValueOf(v["logLevel"]), + }) + assert.Contains(t, buf.String(), fmt.Sprintf(v["formatLogMessage"], v["logLevel"])) + assert.Contains(t, buf.String(), v["zapLogLevel"]) + + buf.Reset() + }) + } +} + +// TestLogLevel test SetLevel +func TestLogLevel(t *testing.T) { + buf := new(bytes.Buffer) + + logger := NewLogger() + defer logger.Sync() + + // output to buffer + logger.SetOutput(buf) + + logger.Debug("this is a debug log") + assert.False(t, strings.Contains(buf.String(), "this is a debug log")) + + logger.SetLevel(hlog.LevelDebug) + + logger.Debugf("this is a debug log %s", "msg") + assert.True(t, strings.Contains(buf.String(), "this is a debug log")) +} + +// TestCoreOption test zapcore config option +func TestCoreOption(t *testing.T) { + buf := new(bytes.Buffer) + + logger := NewLogger( + WithCoreEnc(zapcore.NewConsoleEncoder(humanEncoderConfig())), + WithCoreLevel(zap.NewAtomicLevelAt(zapcore.WarnLevel)), + WithCoreWs(zapcore.AddSync(buf)), + ) + defer logger.Sync() + + logger.SetOutput(buf) + + logger.Debug("this is a debug log") + // test log level + assert.False(t, strings.Contains(buf.String(), "this is a debug log")) + + logger.Error("this is a warn log") + // test log level + assert.True(t, strings.Contains(buf.String(), "this is a warn log")) + // test console encoder result + assert.True(t, strings.Contains(buf.String(), "\tERROR\t")) +} + +// TestCoreOption test zapcore config option +func TestZapOption(t *testing.T) { + buf := new(bytes.Buffer) + + logger := NewLogger( + WithZapOptions(zap.AddCaller()), + ) + defer logger.Sync() + + logger.SetOutput(buf) + + logger.Debug("this is a debug log") + assert.False(t, strings.Contains(buf.String(), "this is a debug log")) + + logger.Error("this is a warn log") + // test caller in log result + assert.True(t, strings.Contains(buf.String(), "caller")) +} diff --git a/zap/option.go b/zap/option.go new file mode 100644 index 0000000..cd57bb2 --- /dev/null +++ b/zap/option.go @@ -0,0 +1,96 @@ +// Copyright 2022 CloudWeGo Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zap + +import ( + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Option interface { + apply(cfg *config) +} + +type option func(cfg *config) + +func (fn option) apply(cfg *config) { + fn(cfg) +} + +type coreConfig struct { + enc zapcore.Encoder + ws zapcore.WriteSyncer + lvl zap.AtomicLevel +} + +type config struct { + coreConfig coreConfig + zapOpts []zap.Option +} + +// defaultCoreConfig default zapcore config: json encoder, atomic level, stdout write syncer +func defaultCoreConfig() *coreConfig { + // default log encoder + enc := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) + // default log level + lvl := zap.NewAtomicLevelAt(zap.InfoLevel) + // default write syncer stdout + ws := zapcore.AddSync(os.Stdout) + + return &coreConfig{ + enc: enc, + ws: ws, + lvl: lvl, + } +} + +// defaultConfig default config +func defaultConfig() *config { + coreConfig := defaultCoreConfig() + return &config{ + coreConfig: *coreConfig, + zapOpts: []zap.Option{}, + } +} + +// WithCoreEnc zapcore encoder +func WithCoreEnc(enc zapcore.Encoder) Option { + return option(func(cfg *config) { + cfg.coreConfig.enc = enc + }) +} + +// WithCoreWs zapcore write syncer +func WithCoreWs(ws zapcore.WriteSyncer) Option { + return option(func(cfg *config) { + cfg.coreConfig.ws = ws + }) +} + +// WithCoreLevel zapcore log level +func WithCoreLevel(lvl zap.AtomicLevel) Option { + return option(func(cfg *config) { + cfg.coreConfig.lvl = lvl + }) +} + +// WithZapOptions add origin zap option +func WithZapOptions(opts ...zap.Option) Option { + return option(func(cfg *config) { + cfg.zapOpts = append(cfg.zapOpts, opts...) + }) +}