You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by to...@apache.org on 2020/12/09 07:23:10 UTC

[apisix-ingress-controller] branch master updated: chore: introduce a log package (#64)

This is an automated email from the ASF dual-hosted git repository.

tokers pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 4ffef79  chore: introduce a log package (#64)
4ffef79 is described below

commit 4ffef796a0560daa7b8861bbfa6cbe15b5ba046a
Author: Alex Zhang <zc...@gmail.com>
AuthorDate: Wed Dec 9 15:23:03 2020 +0800

    chore: introduce a log package (#64)
    
    * chore: introduce a log package
    
    * test: add more test cases.
    
    * fix: style
---
 go.mod                         |   1 +
 go.sum                         |  29 ++---
 pkg/log/default_logger.go      | 110 +++++++++++++++++
 pkg/log/default_logger_test.go |  82 +++++++++++++
 pkg/log/logger.go              | 271 +++++++++++++++++++++++++++++++++++++++++
 pkg/log/logger_test.go         | 102 ++++++++++++++++
 pkg/log/options.go             |  54 ++++++++
 7 files changed, 635 insertions(+), 14 deletions(-)

diff --git a/go.mod b/go.mod
index bd84afe..8fa7ae8 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
 	github.com/spf13/cobra v1.1.1
 	github.com/stretchr/testify v1.4.0
 	github.com/tidwall/gjson v1.6.4
+	go.uber.org/zap v1.13.0
 	gopkg.in/yaml.v2 v2.2.8
 	k8s.io/api v0.0.0-20190819141258-3544db3b9e44
 	k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d
diff --git a/go.sum b/go.sum
index 47c702e..4bf044b 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,7 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -112,12 +113,10 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
 github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
-github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -133,7 +132,6 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE=
 github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -189,6 +187,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+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=
@@ -222,7 +221,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
 github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -253,13 +251,20 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -276,6 +281,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -300,7 +306,6 @@ golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQ
 golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
 golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -319,23 +324,18 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
 golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
@@ -357,6 +357,9 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -367,7 +370,6 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
@@ -400,15 +402,14 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 k8s.io/api v0.0.0-20190819141258-3544db3b9e44 h1:7Gz7/nQ7X2qmPXMyN0bNq7Zm9Uip+UnFuMZTd2l3vms=
 k8s.io/api v0.0.0-20190819141258-3544db3b9e44/go.mod h1:AOxZTnaXR/xiarlQL0JUfwQPxjmKDvVYoRp58cA7lUo=
diff --git a/pkg/log/default_logger.go b/pkg/log/default_logger.go
new file mode 100644
index 0000000..03d20d9
--- /dev/null
+++ b/pkg/log/default_logger.go
@@ -0,0 +1,110 @@
+package log
+
+import "go.uber.org/zap/zapcore"
+
+var (
+	// DefaultLogger is the default logger, which logs message to stderr and with
+	// the minimal level "warn".
+	DefaultLogger *Logger
+)
+
+func init() {
+	l, err := NewLogger(
+		WithOutputFile("stderr"),
+		WithLogLevel("warn"),
+	)
+	if err != nil {
+		panic(err)
+	}
+	DefaultLogger = l
+}
+
+// Debug uses the fmt.Sprint to construct and log a message using the DefaultLogger.
+func Debug(args ...interface{}) {
+	DefaultLogger.Debug(args...)
+}
+
+// Debugf uses the fmt.Sprintf to log a templated message using the DefaultLogger.
+func Debugf(template string, args ...interface{}) {
+	DefaultLogger.Debugf(template, args...)
+}
+
+// Debugw logs a message with some additional context using the DefaultLogger.
+func Debugw(message string, fields ...zapcore.Field) {
+	DefaultLogger.Debugw(message, fields...)
+}
+
+// Info uses the fmt.Sprint to construct and log a message using the DefaultLogger.
+func Info(args ...interface{}) {
+	DefaultLogger.Info(args...)
+}
+
+// Infof uses the fmt.Sprintf to log a templated message using the DefaultLogger.
+func Infof(template string, args ...interface{}) {
+	DefaultLogger.Infof(template, args...)
+}
+
+// Infow logs a message with some additional context using the DefaultLogger.
+func Infow(message string, fields ...zapcore.Field) {
+	DefaultLogger.Infow(message, fields...)
+}
+
+// Warn uses the fmt.Sprint to construct and log a message using the DefaultLogger.
+func Warn(args ...interface{}) {
+	DefaultLogger.Warn(args...)
+}
+
+// Warnf uses the fmt.Sprintf to log a templated message using the DefaultLogger.
+func Warnf(template string, args ...interface{}) {
+	DefaultLogger.Warnf(template, args...)
+}
+
+// Warnw logs a message with some additional context using the DefaultLogger.
+func Warnw(message string, fields ...zapcore.Field) {
+	DefaultLogger.Warnw(message, fields...)
+}
+
+// Error uses the fmt.Sprint to construct and log a message using the DefaultLogger.
+func Error(args ...interface{}) {
+	DefaultLogger.Error(args...)
+}
+
+// Errorf uses the fmt.Sprintf to log a templated message using the DefaultLogger.
+func Errorf(template string, args ...interface{}) {
+	DefaultLogger.Errorf(template, args...)
+}
+
+// Errorw logs a message with some additional context using the DefaultLogger.
+func Errorw(message string, fields ...zapcore.Field) {
+	DefaultLogger.Errorw(message, fields...)
+}
+
+// Panic uses the fmt.Sprint to construct and log a message using the DefaultLogger.
+func Panic(args ...interface{}) {
+	DefaultLogger.Panic(args...)
+}
+
+// Panicf uses the fmt.Sprintf to log a templated message using the DefaultLogger.
+func Panicf(template string, args ...interface{}) {
+	DefaultLogger.Panicf(template, args...)
+}
+
+// Panicw logs a message with some additional context using the DefaultLogger.
+func Panicw(message string, fields ...zapcore.Field) {
+	DefaultLogger.Panicw(message, fields...)
+}
+
+// Fatal uses the fmt.Sprint to construct and log a message using the DefaultLogger.
+func Fatal(args ...interface{}) {
+	DefaultLogger.Fatal(args...)
+}
+
+// Fatalf uses the fmt.Sprintf to log a templated message using the DefaultLogger.
+func Fatalf(template string, args ...interface{}) {
+	DefaultLogger.Fatalf(template, args...)
+}
+
+// Fatalw logs a message with some additional context using the DefaultLogger.
+func Fatalw(message string, fields ...zapcore.Field) {
+	DefaultLogger.Fatalw(message, fields...)
+}
diff --git a/pkg/log/default_logger_test.go b/pkg/log/default_logger_test.go
new file mode 100644
index 0000000..58a1aff
--- /dev/null
+++ b/pkg/log/default_logger_test.go
@@ -0,0 +1,82 @@
+package log
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+var (
+	logHandler = map[string][]reflect.Value{
+		zapcore.DebugLevel.String(): {
+			reflect.ValueOf(Debug),
+			reflect.ValueOf(Debugf),
+			reflect.ValueOf(Debugw),
+		},
+		zapcore.InfoLevel.String(): {
+			reflect.ValueOf(Info),
+			reflect.ValueOf(Infof),
+			reflect.ValueOf(Infow),
+		},
+		zapcore.WarnLevel.String(): {
+			reflect.ValueOf(Warn),
+			reflect.ValueOf(Warnf),
+			reflect.ValueOf(Warnw),
+		},
+		zapcore.ErrorLevel.String(): {
+			reflect.ValueOf(Error),
+			reflect.ValueOf(Errorf),
+			reflect.ValueOf(Errorw),
+		},
+		zapcore.PanicLevel.String(): {
+			reflect.ValueOf(Panic),
+			reflect.ValueOf(Panicf),
+			reflect.ValueOf(Panicw),
+		},
+		zapcore.FatalLevel.String(): {
+			reflect.ValueOf(Fatal),
+			reflect.ValueOf(Fatalf),
+			reflect.ValueOf(Fatalw),
+		},
+	}
+)
+
+func TestDefaultLogger(t *testing.T) {
+	for level, handlers := range logHandler {
+		t.Run("test log with level "+level, func(t *testing.T) {
+			fws := &fakeWriteSyncer{}
+			logger, err := NewLogger(WithLogLevel(level), WithWriteSyncer(fws))
+			assert.Nil(t, err, "failed to new logger: ", err)
+			defer logger.Close()
+			// Reset default logger
+			DefaultLogger = logger
+
+			handlers[0].Call([]reflect.Value{reflect.ValueOf("hello")})
+			assert.Nil(t, logger.Sync(), "failed to sync logger")
+
+			fields := unmarshalLogMessage(t, fws.bytes())
+			assert.Equal(t, fields.Level, level, "bad log level ", fields.Level)
+			assert.Equal(t, fields.Message, "hello", "bad log message ", fields.Message)
+
+			handlers[1].Call([]reflect.Value{reflect.ValueOf("hello I am %s"), reflect.ValueOf("alex")})
+			assert.Nil(t, logger.Sync(), "failed to sync logger")
+
+			fields = unmarshalLogMessage(t, fws.bytes())
+			assert.Equal(t, fields.Level, level, "bad log level ", fields.Level)
+			assert.Equal(t, fields.Message, "hello I am alex", "bad log message ", fields.Message)
+
+			handlers[2].Call([]reflect.Value{reflect.ValueOf("hello"), reflect.ValueOf(zap.String("name", "alex")), reflect.ValueOf(zap.Int("age", 3))})
+
+			assert.Nil(t, logger.Sync(), "failed to sync logger")
+
+			fields = unmarshalLogMessage(t, fws.bytes())
+			assert.Equal(t, fields.Level, level, "bad log level ", fields.Level)
+			assert.Equal(t, fields.Message, "hello", "bad log message ", fields.Message)
+			assert.Equal(t, fields.Name, "alex", "bad name field ", fields.Name)
+			assert.Equal(t, fields.Age, 3, "bad age field ", fields.Age)
+		})
+	}
+}
diff --git a/pkg/log/logger.go b/pkg/log/logger.go
new file mode 100644
index 0000000..6a46127
--- /dev/null
+++ b/pkg/log/logger.go
@@ -0,0 +1,271 @@
+package log
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"runtime"
+	"time"
+
+	"go.uber.org/zap/zapcore"
+)
+
+var (
+	levelMap = map[string]zapcore.Level{
+		zapcore.DebugLevel.String(): zapcore.DebugLevel,
+		zapcore.InfoLevel.String():  zapcore.InfoLevel,
+		zapcore.WarnLevel.String():  zapcore.WarnLevel,
+		zapcore.ErrorLevel.String(): zapcore.ErrorLevel,
+		zapcore.PanicLevel.String(): zapcore.PanicLevel,
+		zapcore.FatalLevel.String(): zapcore.FatalLevel,
+	}
+)
+
+// Logger is a log object, which exposes standard APIs like
+// errorf, error, warn, warnf and etcd.
+type Logger struct {
+	writer io.Writer
+	core   zapcore.Core
+	level  zapcore.Level
+}
+
+func (logger *Logger) write(level zapcore.Level, message string, fields []zapcore.Field) {
+	e := zapcore.Entry{
+		Level:   level,
+		Time:    time.Now(),
+		Message: message,
+		Caller:  zapcore.NewEntryCaller(runtime.Caller(2)),
+	}
+
+	_ = logger.core.Write(e, fields)
+}
+
+// Sync flushes all buffered logs to the their destination.
+func (logger *Logger) Sync() (err error) {
+	file, ok := logger.writer.(*os.File)
+	if ok && file != os.Stdout && file != os.Stderr {
+		err = logger.core.Sync()
+	}
+	return
+}
+
+// Close flushes all buffered logs and closes the underlying writer.
+func (logger *Logger) Close() (err error) {
+	closer, ok := logger.writer.(io.Closer)
+	if ok {
+		return closer.Close()
+	}
+	return nil
+}
+
+// Debug uses the fmt.Sprint to construct and log a message.
+func (logger *Logger) Debug(args ...interface{}) {
+	if logger.level <= zapcore.DebugLevel {
+		msg := fmt.Sprint(args...)
+		logger.write(zapcore.DebugLevel, msg, nil)
+	}
+}
+
+// Debugf uses the fmt.Sprintf to log a templated message.
+func (logger *Logger) Debugf(template string, args ...interface{}) {
+	if logger.level <= zapcore.DebugLevel {
+		msg := fmt.Sprintf(template, args...)
+		logger.write(zapcore.DebugLevel, msg, nil)
+	}
+}
+
+// Debugw logs a message with some additional context.
+func (logger *Logger) Debugw(message string, fields ...zapcore.Field) {
+	if logger.level <= zapcore.DebugLevel {
+		logger.write(zapcore.DebugLevel, message, fields)
+	}
+}
+
+// Info uses the fmt.Sprint to construct and log a message.
+func (logger *Logger) Info(args ...interface{}) {
+	if logger.level <= zapcore.InfoLevel {
+		msg := fmt.Sprint(args...)
+		logger.write(zapcore.InfoLevel, msg, nil)
+	}
+}
+
+// Infof uses the fmt.Sprintf to log a templated message.
+func (logger *Logger) Infof(template string, args ...interface{}) {
+	if logger.level <= zapcore.InfoLevel {
+		msg := fmt.Sprintf(template, args...)
+		logger.write(zapcore.InfoLevel, msg, nil)
+	}
+}
+
+// Infow logs a message with some additional context.
+func (logger *Logger) Infow(message string, fields ...zapcore.Field) {
+	if logger.level <= zapcore.InfoLevel {
+		logger.write(zapcore.InfoLevel, message, fields)
+	}
+}
+
+// Warn uses the fmt.Sprint to construct and log a message.
+func (logger *Logger) Warn(args ...interface{}) {
+	if logger.level <= zapcore.WarnLevel {
+		msg := fmt.Sprint(args...)
+		logger.write(zapcore.WarnLevel, msg, nil)
+	}
+}
+
+// Warnf uses the fmt.Sprintf to log a templated message.
+func (logger *Logger) Warnf(template string, args ...interface{}) {
+	if logger.level <= zapcore.WarnLevel {
+		msg := fmt.Sprintf(template, args...)
+		logger.write(zapcore.WarnLevel, msg, nil)
+	}
+}
+
+// Warnw logs a message with some additional context.
+func (logger *Logger) Warnw(message string, fields ...zapcore.Field) {
+	if logger.level <= zapcore.WarnLevel {
+		logger.write(zapcore.WarnLevel, message, fields)
+	}
+}
+
+// Error uses the fmt.Sprint to construct and log a message.
+func (logger *Logger) Error(args ...interface{}) {
+	if logger.level <= zapcore.ErrorLevel {
+		msg := fmt.Sprint(args...)
+		logger.write(zapcore.ErrorLevel, msg, nil)
+	}
+}
+
+// Errorf uses the fmt.Sprintf to log a templated message.
+func (logger *Logger) Errorf(template string, args ...interface{}) {
+	if logger.level <= zapcore.ErrorLevel {
+		msg := fmt.Sprintf(template, args...)
+		logger.write(zapcore.ErrorLevel, msg, nil)
+	}
+}
+
+// Errorw logs a message with some additional context.
+func (logger *Logger) Errorw(message string, fields ...zapcore.Field) {
+	if logger.level <= zapcore.ErrorLevel {
+		logger.write(zapcore.ErrorLevel, message, fields)
+	}
+}
+
+// Panic uses the fmt.Sprint to construct and log a message.
+func (logger *Logger) Panic(args ...interface{}) {
+	if logger.level <= zapcore.PanicLevel {
+		msg := fmt.Sprint(args...)
+		logger.write(zapcore.PanicLevel, msg, nil)
+	}
+}
+
+// Panicf uses the fmt.Sprintf to log a templated message.
+func (logger *Logger) Panicf(template string, args ...interface{}) {
+	if logger.level <= zapcore.PanicLevel {
+		msg := fmt.Sprintf(template, args...)
+		logger.write(zapcore.PanicLevel, msg, nil)
+	}
+}
+
+// Panicw logs a message with some additional context.
+func (logger *Logger) Panicw(message string, fields ...zapcore.Field) {
+	if logger.level <= zapcore.PanicLevel {
+		logger.write(zapcore.PanicLevel, message, fields)
+	}
+}
+
+// Fatal uses the fmt.Sprint to construct and log a message.
+func (logger *Logger) Fatal(args ...interface{}) {
+	if logger.level <= zapcore.FatalLevel {
+		msg := fmt.Sprint(args...)
+		logger.write(zapcore.FatalLevel, msg, nil)
+	}
+}
+
+// Fatalf uses the fmt.Sprintf to log a templated message.
+func (logger *Logger) Fatalf(template string, args ...interface{}) {
+	if logger.level <= zapcore.FatalLevel {
+		msg := fmt.Sprintf(template, args...)
+		logger.write(zapcore.FatalLevel, msg, nil)
+	}
+}
+
+// Fatalw logs a message with some additional context.
+func (logger *Logger) Fatalw(message string, fields ...zapcore.Field) {
+	if logger.level <= zapcore.FatalLevel {
+		logger.write(zapcore.FatalLevel, message, fields)
+	}
+}
+
+// NewLogger sets up a Logger object according to a series of options.
+func NewLogger(opts ...Option) (*Logger, error) {
+	var (
+		writer zapcore.WriteSyncer
+		enc    zapcore.Encoder
+	)
+
+	o := &options{
+		logLevel:   "warn",
+		outputFile: "stderr",
+	}
+	for _, opt := range opts {
+		opt.apply(o)
+	}
+
+	level, ok := levelMap[o.logLevel]
+	if !ok {
+		return nil, fmt.Errorf("unknown log level %s", o.logLevel)
+	}
+
+	logger := &Logger{
+		level: level,
+	}
+
+	if o.writeSyncer != nil {
+		writer = o.writeSyncer
+	} else {
+		if o.outputFile == "stdout" {
+			writer = os.Stdout
+		} else if o.outputFile == "stderr" {
+			writer = os.Stderr
+		} else {
+			file, err := os.OpenFile(o.outputFile, os.O_WRONLY|os.O_APPEND, 0644)
+			if err != nil {
+				return nil, err
+			}
+			writer = file
+		}
+	}
+
+	if writer == os.Stdout || writer == os.Stderr {
+		enc = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
+			MessageKey:     "message",
+			LevelKey:       "level",
+			TimeKey:        "time",
+			NameKey:        "context",
+			CallerKey:      "caller",
+			StacktraceKey:  "backtrace",
+			LineEnding:     zapcore.DefaultLineEnding,
+			EncodeLevel:    zapcore.LowercaseColorLevelEncoder,
+			EncodeTime:     zapcore.RFC3339TimeEncoder,
+			EncodeDuration: zapcore.StringDurationEncoder,
+			EncodeCaller:   zapcore.ShortCallerEncoder,
+		})
+	} else {
+		enc = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
+			MessageKey:     "message",
+			LevelKey:       "level",
+			TimeKey:        "time",
+			NameKey:        "context",
+			CallerKey:      "caller",
+			StacktraceKey:  "backtrace",
+			LineEnding:     zapcore.DefaultLineEnding,
+			EncodeLevel:    zapcore.LowercaseLevelEncoder,
+			EncodeTime:     zapcore.RFC3339NanoTimeEncoder,
+			EncodeDuration: zapcore.StringDurationEncoder,
+			EncodeCaller:   zapcore.ShortCallerEncoder,
+		})
+	}
+	logger.writer = writer
+	logger.core = zapcore.NewCore(enc, writer, level)
+	return logger, nil
+}
diff --git a/pkg/log/logger_test.go b/pkg/log/logger_test.go
new file mode 100644
index 0000000..3f3f258
--- /dev/null
+++ b/pkg/log/logger_test.go
@@ -0,0 +1,102 @@
+package log
+
+import (
+	"bytes"
+	"encoding/json"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/zap"
+)
+
+type fakeWriteSyncer struct {
+	buf bytes.Buffer
+}
+
+type fields struct {
+	Level   string
+	Time    string
+	Message string
+	Name    string
+	Age     int
+}
+
+func (fws *fakeWriteSyncer) Sync() error {
+	return nil
+}
+
+func (fws *fakeWriteSyncer) Write(p []byte) (int, error) {
+	return fws.buf.Write(p)
+}
+
+func (fws *fakeWriteSyncer) bytes() (p []byte) {
+	s := fws.buf.Bytes()
+	p = make([]byte, len(s))
+	copy(p, s)
+	fws.buf.Reset()
+	return
+}
+
+func unmarshalLogMessage(t *testing.T, data []byte) *fields {
+	var f fields
+	err := json.Unmarshal(data, &f)
+	assert.Nil(t, err, "failed to unmarshal log message: ", err)
+	return &f
+}
+
+func TestLogger(t *testing.T) {
+	for level := range levelMap {
+		t.Run("test log with level "+level, func(t *testing.T) {
+			fws := &fakeWriteSyncer{}
+			logger, err := NewLogger(WithLogLevel(level), WithWriteSyncer(fws))
+			assert.Nil(t, err, "failed to new logger: ", err)
+			defer logger.Close()
+
+			rv := reflect.ValueOf(logger)
+
+			handler := rv.MethodByName(http.CanonicalHeaderKey(level))
+			handler.Call([]reflect.Value{reflect.ValueOf("hello")})
+
+			assert.Nil(t, logger.Sync(), "failed to sync logger")
+
+			fields := unmarshalLogMessage(t, fws.bytes())
+			assert.Equal(t, fields.Level, level, "bad log level ", fields.Level)
+			assert.Equal(t, fields.Message, "hello", "bad log message ", fields.Message)
+
+			handler = rv.MethodByName(http.CanonicalHeaderKey(level) + "f")
+			handler.Call([]reflect.Value{reflect.ValueOf("hello I am %s"), reflect.ValueOf("alex")})
+
+			assert.Nil(t, logger.Sync(), "failed to sync logger")
+
+			fields = unmarshalLogMessage(t, fws.bytes())
+			assert.Equal(t, fields.Level, level, "bad log level ", fields.Level)
+			assert.Equal(t, fields.Message, "hello I am alex", "bad log message ", fields.Message)
+
+			handler = rv.MethodByName(http.CanonicalHeaderKey(level) + "w")
+			handler.Call([]reflect.Value{reflect.ValueOf("hello"), reflect.ValueOf(zap.String("name", "alex")), reflect.ValueOf(zap.Int("age", 3))})
+
+			assert.Nil(t, logger.Sync(), "failed to sync logger")
+
+			fields = unmarshalLogMessage(t, fws.bytes())
+			assert.Equal(t, fields.Level, level, "bad log level ", fields.Level)
+			assert.Equal(t, fields.Message, "hello", "bad log message ", fields.Message)
+			assert.Equal(t, fields.Name, "alex", "bad name field ", fields.Name)
+			assert.Equal(t, fields.Age, 3, "bad age field ", fields.Age)
+		})
+	}
+}
+
+func TestLogLevel(t *testing.T) {
+	fws := &fakeWriteSyncer{}
+	logger, err := NewLogger(WithLogLevel("error"), WithWriteSyncer(fws))
+	assert.Nil(t, err, "failed to new logger: ", err)
+	defer logger.Close()
+
+	logger.Warn("this message should be dropped")
+	assert.Nil(t, logger.Sync(), "failed to sync logger")
+
+	p := fws.bytes()
+	assert.Len(t, p, 0, "saw a message which should be dropped")
+}
diff --git a/pkg/log/options.go b/pkg/log/options.go
new file mode 100644
index 0000000..940e615
--- /dev/null
+++ b/pkg/log/options.go
@@ -0,0 +1,54 @@
+package log
+
+import (
+	"go.uber.org/zap/zapcore"
+)
+
+// Option configures how to set up logger.
+type Option interface {
+	apply(*options)
+}
+
+type funcOption struct {
+	do func(*options)
+}
+
+func (fo *funcOption) apply(o *options) {
+	fo.do(o)
+}
+
+type options struct {
+	writeSyncer zapcore.WriteSyncer
+	outputFile  string
+	logLevel    string
+	context     string
+}
+
+// WithLogLevel sets the log level.
+func WithLogLevel(level string) Option {
+	return &funcOption{
+		do: func(o *options) {
+			o.logLevel = level
+		},
+	}
+}
+
+// WithOutputFile sets the output file path.
+func WithOutputFile(file string) Option {
+	return &funcOption{
+		do: func(o *options) {
+			o.outputFile = file
+		},
+	}
+}
+
+// WithWriteSyncer is a low level API which sets the underlying
+// WriteSyncer by providing a zapcore.WriterSyncer,
+// which has high priority than WithOutputFile.
+func WithWriteSyncer(ws zapcore.WriteSyncer) Option {
+	return &funcOption{
+		do: func(o *options) {
+			o.writeSyncer = ws
+		},
+	}
+}