mirror of https://github.com/knative/pkg.git
				
				
				
			Bump go.uber.org/zap from 1.19.1 to 1.24.0 (#2774)
* Bump go.uber.org/zap from 1.19.1 to 1.24.0 Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.19.1 to 1.24.0. - [Release notes](https://github.com/uber-go/zap/releases) - [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/zap/compare/v1.19.1...v1.24.0) --- updated-dependencies: - dependency-name: go.uber.org/zap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Run ./hack/update-codegen.sh --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									7b50f3c433
								
							
						
					
					
						commit
						72f264817d
					
				
							
								
								
									
										3
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										3
									
								
								go.mod
								
								
								
								
							|  | @ -31,7 +31,7 @@ require ( | ||||||
| 	go.opencensus.io v0.24.0 | 	go.opencensus.io v0.24.0 | ||||||
| 	go.uber.org/atomic v1.9.0 | 	go.uber.org/atomic v1.9.0 | ||||||
| 	go.uber.org/automaxprocs v1.4.0 | 	go.uber.org/automaxprocs v1.4.0 | ||||||
| 	go.uber.org/zap v1.19.1 | 	go.uber.org/zap v1.24.0 | ||||||
| 	golang.org/x/net v0.12.0 | 	golang.org/x/net v0.12.0 | ||||||
| 	golang.org/x/oauth2 v0.8.0 | 	golang.org/x/oauth2 v0.8.0 | ||||||
| 	golang.org/x/sync v0.3.0 | 	golang.org/x/sync v0.3.0 | ||||||
|  | @ -57,6 +57,7 @@ require ( | ||||||
| 	cloud.google.com/go v0.110.2 // indirect | 	cloud.google.com/go v0.110.2 // indirect | ||||||
| 	cloud.google.com/go/compute v1.19.0 // indirect | 	cloud.google.com/go/compute v1.19.0 // indirect | ||||||
| 	cloud.google.com/go/iam v1.0.1 // indirect | 	cloud.google.com/go/iam v1.0.1 // indirect | ||||||
|  | 	github.com/benbjohnson/clock v1.1.0 // indirect | ||||||
| 	github.com/beorn7/perks v1.0.1 // indirect | 	github.com/beorn7/perks v1.0.1 // indirect | ||||||
| 	github.com/cespare/xxhash/v2 v2.2.0 // indirect | 	github.com/cespare/xxhash/v2 v2.2.0 // indirect | ||||||
| 	github.com/emicklei/go-restful/v3 v3.9.0 // indirect | 	github.com/emicklei/go-restful/v3 v3.9.0 // indirect | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										12
									
								
								go.sum
								
								
								
								
							|  | @ -409,7 +409,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de | ||||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= |  | ||||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||||
| go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||||
|  | @ -426,15 +425,14 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= | ||||||
| go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||||||
| go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0= | go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0= | ||||||
| go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= | go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= | ||||||
| go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= |  | ||||||
| go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= | ||||||
| go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= | go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= | ||||||
| go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||||||
| go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= | 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/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= | ||||||
| go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||||||
| go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= | ||||||
| go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= | ||||||
| golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
|  | @ -479,7 +477,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB | ||||||
| golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |  | ||||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||||
| golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= | ||||||
| golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||||
|  | @ -517,7 +514,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R | ||||||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||||
| golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||||
| golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= |  | ||||||
| golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= | ||||||
| golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||||
| golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||||
|  | @ -543,7 +539,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ | ||||||
| golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |  | ||||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= | ||||||
| golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= | ||||||
|  | @ -587,9 +582,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |  | ||||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | @ -661,7 +654,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc | ||||||
| golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||||
| golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||||
| golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||||
| golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= |  | ||||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||||
| golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= | golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= | ||||||
| golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= | golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= | ||||||
|  |  | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | The MIT License (MIT) | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2014 Ben Johnson | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | The MIT License (MIT) | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2014 Ben Johnson | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  | @ -0,0 +1,104 @@ | ||||||
|  | clock | ||||||
|  | ===== | ||||||
|  | 
 | ||||||
|  | Clock is a small library for mocking time in Go. It provides an interface | ||||||
|  | around the standard library's [`time`][time] package so that the application | ||||||
|  | can use the realtime clock while tests can use the mock clock. | ||||||
|  | 
 | ||||||
|  | [time]: http://golang.org/pkg/time/ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## Usage | ||||||
|  | 
 | ||||||
|  | ### Realtime Clock | ||||||
|  | 
 | ||||||
|  | Your application can maintain a `Clock` variable that will allow realtime and | ||||||
|  | mock clocks to be interchangeable. For example, if you had an `Application` type: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | import "github.com/benbjohnson/clock" | ||||||
|  | 
 | ||||||
|  | type Application struct { | ||||||
|  | 	Clock clock.Clock | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You could initialize it to use the realtime clock like this: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | var app Application | ||||||
|  | app.Clock = clock.New() | ||||||
|  | ... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Then all timers and time-related functionality should be performed from the | ||||||
|  | `Clock` variable. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Mocking time | ||||||
|  | 
 | ||||||
|  | In your tests, you will want to use a `Mock` clock: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/benbjohnson/clock" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestApplication_DoSomething(t *testing.T) { | ||||||
|  | 	mock := clock.NewMock() | ||||||
|  | 	app := Application{Clock: mock} | ||||||
|  | 	... | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Now that you've initialized your application to use the mock clock, you can | ||||||
|  | adjust the time programmatically. The mock clock always starts from the Unix | ||||||
|  | epoch (midnight UTC on Jan 1, 1970). | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Controlling time | ||||||
|  | 
 | ||||||
|  | The mock clock provides the same functions that the standard library's `time` | ||||||
|  | package provides. For example, to find the current time, you use the `Now()` | ||||||
|  | function: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | mock := clock.NewMock() | ||||||
|  | 
 | ||||||
|  | // Find the current time. | ||||||
|  | mock.Now().UTC() // 1970-01-01 00:00:00 +0000 UTC | ||||||
|  | 
 | ||||||
|  | // Move the clock forward. | ||||||
|  | mock.Add(2 * time.Hour) | ||||||
|  | 
 | ||||||
|  | // Check the time again. It's 2 hours later! | ||||||
|  | mock.Now().UTC() // 1970-01-01 02:00:00 +0000 UTC | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Timers and Tickers are also controlled by this same mock clock. They will only | ||||||
|  | execute when the clock is moved forward: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | mock := clock.NewMock() | ||||||
|  | count := 0 | ||||||
|  | 
 | ||||||
|  | // Kick off a timer to increment every 1 mock second. | ||||||
|  | go func() { | ||||||
|  |     ticker := clock.Ticker(1 * time.Second) | ||||||
|  |     for { | ||||||
|  |         <-ticker.C | ||||||
|  |         count++ | ||||||
|  |     } | ||||||
|  | }() | ||||||
|  | runtime.Gosched() | ||||||
|  | 
 | ||||||
|  | // Move the clock forward 10 seconds. | ||||||
|  | mock.Add(10 * time.Second) | ||||||
|  | 
 | ||||||
|  | // This prints 10. | ||||||
|  | fmt.Println(count) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,340 @@ | ||||||
|  | package clock | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"sort" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Clock represents an interface to the functions in the standard library time
 | ||||||
|  | // package. Two implementations are available in the clock package. The first
 | ||||||
|  | // is a real-time clock which simply wraps the time package's functions. The
 | ||||||
|  | // second is a mock clock which will only change when
 | ||||||
|  | // programmatically adjusted.
 | ||||||
|  | type Clock interface { | ||||||
|  | 	After(d time.Duration) <-chan time.Time | ||||||
|  | 	AfterFunc(d time.Duration, f func()) *Timer | ||||||
|  | 	Now() time.Time | ||||||
|  | 	Since(t time.Time) time.Duration | ||||||
|  | 	Sleep(d time.Duration) | ||||||
|  | 	Tick(d time.Duration) <-chan time.Time | ||||||
|  | 	Ticker(d time.Duration) *Ticker | ||||||
|  | 	Timer(d time.Duration) *Timer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New returns an instance of a real-time clock.
 | ||||||
|  | func New() Clock { | ||||||
|  | 	return &clock{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // clock implements a real-time clock by simply wrapping the time package functions.
 | ||||||
|  | type clock struct{} | ||||||
|  | 
 | ||||||
|  | func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) } | ||||||
|  | 
 | ||||||
|  | func (c *clock) AfterFunc(d time.Duration, f func()) *Timer { | ||||||
|  | 	return &Timer{timer: time.AfterFunc(d, f)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *clock) Now() time.Time { return time.Now() } | ||||||
|  | 
 | ||||||
|  | func (c *clock) Since(t time.Time) time.Duration { return time.Since(t) } | ||||||
|  | 
 | ||||||
|  | func (c *clock) Sleep(d time.Duration) { time.Sleep(d) } | ||||||
|  | 
 | ||||||
|  | func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) } | ||||||
|  | 
 | ||||||
|  | func (c *clock) Ticker(d time.Duration) *Ticker { | ||||||
|  | 	t := time.NewTicker(d) | ||||||
|  | 	return &Ticker{C: t.C, ticker: t} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *clock) Timer(d time.Duration) *Timer { | ||||||
|  | 	t := time.NewTimer(d) | ||||||
|  | 	return &Timer{C: t.C, timer: t} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Mock represents a mock clock that only moves forward programmatically.
 | ||||||
|  | // It can be preferable to a real-time clock when testing time-based functionality.
 | ||||||
|  | type Mock struct { | ||||||
|  | 	mu     sync.Mutex | ||||||
|  | 	now    time.Time   // current time
 | ||||||
|  | 	timers clockTimers // tickers & timers
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewMock returns an instance of a mock clock.
 | ||||||
|  | // The current time of the mock clock on initialization is the Unix epoch.
 | ||||||
|  | func NewMock() *Mock { | ||||||
|  | 	return &Mock{now: time.Unix(0, 0)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add moves the current time of the mock clock forward by the specified duration.
 | ||||||
|  | // This should only be called from a single goroutine at a time.
 | ||||||
|  | func (m *Mock) Add(d time.Duration) { | ||||||
|  | 	// Calculate the final current time.
 | ||||||
|  | 	t := m.now.Add(d) | ||||||
|  | 
 | ||||||
|  | 	// Continue to execute timers until there are no more before the new time.
 | ||||||
|  | 	for { | ||||||
|  | 		if !m.runNextTimer(t) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Ensure that we end with the new time.
 | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	m.now = t | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	// Give a small buffer to make sure that other goroutines get handled.
 | ||||||
|  | 	gosched() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Set sets the current time of the mock clock to a specific one.
 | ||||||
|  | // This should only be called from a single goroutine at a time.
 | ||||||
|  | func (m *Mock) Set(t time.Time) { | ||||||
|  | 	// Continue to execute timers until there are no more before the new time.
 | ||||||
|  | 	for { | ||||||
|  | 		if !m.runNextTimer(t) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Ensure that we end with the new time.
 | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	m.now = t | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	// Give a small buffer to make sure that other goroutines get handled.
 | ||||||
|  | 	gosched() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // runNextTimer executes the next timer in chronological order and moves the
 | ||||||
|  | // current time to the timer's next tick time. The next time is not executed if
 | ||||||
|  | // its next time is after the max time. Returns true if a timer was executed.
 | ||||||
|  | func (m *Mock) runNextTimer(max time.Time) bool { | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 
 | ||||||
|  | 	// Sort timers by time.
 | ||||||
|  | 	sort.Sort(m.timers) | ||||||
|  | 
 | ||||||
|  | 	// If we have no more timers then exit.
 | ||||||
|  | 	if len(m.timers) == 0 { | ||||||
|  | 		m.mu.Unlock() | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Retrieve next timer. Exit if next tick is after new time.
 | ||||||
|  | 	t := m.timers[0] | ||||||
|  | 	if t.Next().After(max) { | ||||||
|  | 		m.mu.Unlock() | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Move "now" forward and unlock clock.
 | ||||||
|  | 	m.now = t.Next() | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	// Execute timer.
 | ||||||
|  | 	t.Tick(m.now) | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // After waits for the duration to elapse and then sends the current time on the returned channel.
 | ||||||
|  | func (m *Mock) After(d time.Duration) <-chan time.Time { | ||||||
|  | 	return m.Timer(d).C | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AfterFunc waits for the duration to elapse and then executes a function.
 | ||||||
|  | // A Timer is returned that can be stopped.
 | ||||||
|  | func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer { | ||||||
|  | 	t := m.Timer(d) | ||||||
|  | 	t.C = nil | ||||||
|  | 	t.fn = f | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Now returns the current wall time on the mock clock.
 | ||||||
|  | func (m *Mock) Now() time.Time { | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	defer m.mu.Unlock() | ||||||
|  | 	return m.now | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Since returns time since the mock clock's wall time.
 | ||||||
|  | func (m *Mock) Since(t time.Time) time.Duration { | ||||||
|  | 	return m.Now().Sub(t) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sleep pauses the goroutine for the given duration on the mock clock.
 | ||||||
|  | // The clock must be moved forward in a separate goroutine.
 | ||||||
|  | func (m *Mock) Sleep(d time.Duration) { | ||||||
|  | 	<-m.After(d) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Tick is a convenience function for Ticker().
 | ||||||
|  | // It will return a ticker channel that cannot be stopped.
 | ||||||
|  | func (m *Mock) Tick(d time.Duration) <-chan time.Time { | ||||||
|  | 	return m.Ticker(d).C | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Ticker creates a new instance of Ticker.
 | ||||||
|  | func (m *Mock) Ticker(d time.Duration) *Ticker { | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	defer m.mu.Unlock() | ||||||
|  | 	ch := make(chan time.Time, 1) | ||||||
|  | 	t := &Ticker{ | ||||||
|  | 		C:    ch, | ||||||
|  | 		c:    ch, | ||||||
|  | 		mock: m, | ||||||
|  | 		d:    d, | ||||||
|  | 		next: m.now.Add(d), | ||||||
|  | 	} | ||||||
|  | 	m.timers = append(m.timers, (*internalTicker)(t)) | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Timer creates a new instance of Timer.
 | ||||||
|  | func (m *Mock) Timer(d time.Duration) *Timer { | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	defer m.mu.Unlock() | ||||||
|  | 	ch := make(chan time.Time, 1) | ||||||
|  | 	t := &Timer{ | ||||||
|  | 		C:       ch, | ||||||
|  | 		c:       ch, | ||||||
|  | 		mock:    m, | ||||||
|  | 		next:    m.now.Add(d), | ||||||
|  | 		stopped: false, | ||||||
|  | 	} | ||||||
|  | 	m.timers = append(m.timers, (*internalTimer)(t)) | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *Mock) removeClockTimer(t clockTimer) { | ||||||
|  | 	for i, timer := range m.timers { | ||||||
|  | 		if timer == t { | ||||||
|  | 			copy(m.timers[i:], m.timers[i+1:]) | ||||||
|  | 			m.timers[len(m.timers)-1] = nil | ||||||
|  | 			m.timers = m.timers[:len(m.timers)-1] | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	sort.Sort(m.timers) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // clockTimer represents an object with an associated start time.
 | ||||||
|  | type clockTimer interface { | ||||||
|  | 	Next() time.Time | ||||||
|  | 	Tick(time.Time) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // clockTimers represents a list of sortable timers.
 | ||||||
|  | type clockTimers []clockTimer | ||||||
|  | 
 | ||||||
|  | func (a clockTimers) Len() int           { return len(a) } | ||||||
|  | func (a clockTimers) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } | ||||||
|  | func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) } | ||||||
|  | 
 | ||||||
|  | // Timer represents a single event.
 | ||||||
|  | // The current time will be sent on C, unless the timer was created by AfterFunc.
 | ||||||
|  | type Timer struct { | ||||||
|  | 	C       <-chan time.Time | ||||||
|  | 	c       chan time.Time | ||||||
|  | 	timer   *time.Timer // realtime impl, if set
 | ||||||
|  | 	next    time.Time   // next tick time
 | ||||||
|  | 	mock    *Mock       // mock clock, if set
 | ||||||
|  | 	fn      func()      // AfterFunc function, if set
 | ||||||
|  | 	stopped bool        // True if stopped, false if running
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stop turns off the ticker.
 | ||||||
|  | func (t *Timer) Stop() bool { | ||||||
|  | 	if t.timer != nil { | ||||||
|  | 		return t.timer.Stop() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	t.mock.mu.Lock() | ||||||
|  | 	registered := !t.stopped | ||||||
|  | 	t.mock.removeClockTimer((*internalTimer)(t)) | ||||||
|  | 	t.stopped = true | ||||||
|  | 	t.mock.mu.Unlock() | ||||||
|  | 	return registered | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reset changes the expiry time of the timer
 | ||||||
|  | func (t *Timer) Reset(d time.Duration) bool { | ||||||
|  | 	if t.timer != nil { | ||||||
|  | 		return t.timer.Reset(d) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	t.mock.mu.Lock() | ||||||
|  | 	t.next = t.mock.now.Add(d) | ||||||
|  | 	defer t.mock.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	registered := !t.stopped | ||||||
|  | 	if t.stopped { | ||||||
|  | 		t.mock.timers = append(t.mock.timers, (*internalTimer)(t)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	t.stopped = false | ||||||
|  | 	return registered | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type internalTimer Timer | ||||||
|  | 
 | ||||||
|  | func (t *internalTimer) Next() time.Time { return t.next } | ||||||
|  | func (t *internalTimer) Tick(now time.Time) { | ||||||
|  | 	t.mock.mu.Lock() | ||||||
|  | 	if t.fn != nil { | ||||||
|  | 		t.fn() | ||||||
|  | 	} else { | ||||||
|  | 		t.c <- now | ||||||
|  | 	} | ||||||
|  | 	t.mock.removeClockTimer((*internalTimer)(t)) | ||||||
|  | 	t.stopped = true | ||||||
|  | 	t.mock.mu.Unlock() | ||||||
|  | 	gosched() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Ticker holds a channel that receives "ticks" at regular intervals.
 | ||||||
|  | type Ticker struct { | ||||||
|  | 	C      <-chan time.Time | ||||||
|  | 	c      chan time.Time | ||||||
|  | 	ticker *time.Ticker  // realtime impl, if set
 | ||||||
|  | 	next   time.Time     // next tick time
 | ||||||
|  | 	mock   *Mock         // mock clock, if set
 | ||||||
|  | 	d      time.Duration // time between ticks
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stop turns off the ticker.
 | ||||||
|  | func (t *Ticker) Stop() { | ||||||
|  | 	if t.ticker != nil { | ||||||
|  | 		t.ticker.Stop() | ||||||
|  | 	} else { | ||||||
|  | 		t.mock.mu.Lock() | ||||||
|  | 		t.mock.removeClockTimer((*internalTicker)(t)) | ||||||
|  | 		t.mock.mu.Unlock() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reset resets the ticker to a new duration.
 | ||||||
|  | func (t *Ticker) Reset(dur time.Duration) { | ||||||
|  | 	if t.ticker != nil { | ||||||
|  | 		t.ticker.Reset(dur) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type internalTicker Ticker | ||||||
|  | 
 | ||||||
|  | func (t *internalTicker) Next() time.Time { return t.next } | ||||||
|  | func (t *internalTicker) Tick(now time.Time) { | ||||||
|  | 	select { | ||||||
|  | 	case t.c <- now: | ||||||
|  | 	default: | ||||||
|  | 	} | ||||||
|  | 	t.next = now.Add(t.d) | ||||||
|  | 	gosched() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sleep momentarily so that other goroutines can process.
 | ||||||
|  | func gosched() { time.Sleep(1 * time.Millisecond) } | ||||||
|  | @ -96,14 +96,14 @@ Released under the [MIT License](LICENSE.txt). | ||||||
| 
 | 
 | ||||||
| <sup id="footnote-versions">1</sup> In particular, keep in mind that we may be | <sup id="footnote-versions">1</sup> In particular, keep in mind that we may be | ||||||
| benchmarking against slightly older versions of other packages. Versions are | benchmarking against slightly older versions of other packages. Versions are | ||||||
| pinned in zap's [glide.lock][] file. [↩](#anchor-versions) | pinned in the [benchmarks/go.mod][] file. [↩](#anchor-versions) | ||||||
| 
 | 
 | ||||||
| [doc-img]: https://godoc.org/go.uber.org/zap?status.svg | [doc-img]: https://pkg.go.dev/badge/go.uber.org/zap | ||||||
| [doc]: https://godoc.org/go.uber.org/zap | [doc]: https://pkg.go.dev/go.uber.org/zap | ||||||
| [ci-img]: https://travis-ci.com/uber-go/zap.svg?branch=master | [ci-img]: https://github.com/uber-go/zap/actions/workflows/go.yml/badge.svg | ||||||
| [ci]: https://travis-ci.com/uber-go/zap | [ci]: https://github.com/uber-go/zap/actions/workflows/go.yml | ||||||
| [cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg | [cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg | ||||||
| [cov]: https://codecov.io/gh/uber-go/zap | [cov]: https://codecov.io/gh/uber-go/zap | ||||||
| [benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks | [benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks | ||||||
| [glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock | [benchmarks/go.mod]: https://github.com/uber-go/zap/blob/master/benchmarks/go.mod | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,9 +3,110 @@ All notable changes to this project will be documented in this file. | ||||||
| 
 | 
 | ||||||
| This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). | This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). | ||||||
| 
 | 
 | ||||||
|  | ## 1.24.0 (30 Nov 2022) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | * [#1148][]: Add `Level` to both `Logger` and `SugaredLogger` that reports the | ||||||
|  |   current minimum enabled log level. | ||||||
|  | * [#1185][]: `SugaredLogger` turns errors to zap.Error automatically. | ||||||
|  | 
 | ||||||
|  | Thanks to @Abirdcfly, @craigpastro, @nnnkkk7, and @sashamelentyev for their | ||||||
|  | contributions to this release. | ||||||
|  | 
 | ||||||
|  | [#1148]: https://github.coml/uber-go/zap/pull/1148 | ||||||
|  | [#1185]: https://github.coml/uber-go/zap/pull/1185 | ||||||
|  | 
 | ||||||
|  | ## 1.23.0 (24 Aug 2022) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | * [#1147][]: Add a `zapcore.LevelOf` function to determine the level of a | ||||||
|  |   `LevelEnabler` or `Core`. | ||||||
|  | * [#1155][]: Add `zap.Stringers` field constructor to log arrays of objects | ||||||
|  |   that implement `String() string`. | ||||||
|  | 
 | ||||||
|  | [#1147]: https://github.com/uber-go/zap/pull/1147 | ||||||
|  | [#1155]: https://github.com/uber-go/zap/pull/1155 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## 1.22.0 (8 Aug 2022) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | * [#1071][]: Add `zap.Objects` and `zap.ObjectValues` field constructors to log | ||||||
|  |   arrays of objects. With these two constructors, you don't need to implement | ||||||
|  |   `zapcore.ArrayMarshaler` for use with `zap.Array` if those objects implement | ||||||
|  |   `zapcore.ObjectMarshaler`. | ||||||
|  | * [#1079][]: Add `SugaredLogger.WithOptions` to build a copy of an existing | ||||||
|  |   `SugaredLogger` with the provided options applied. | ||||||
|  | * [#1080][]: Add `*ln` variants to `SugaredLogger` for each log level. | ||||||
|  |   These functions provide a string joining behavior similar to `fmt.Println`. | ||||||
|  | * [#1088][]: Add `zap.WithFatalHook` option to control the behavior of the | ||||||
|  |   logger for `Fatal`-level log entries. This defaults to exiting the program. | ||||||
|  | * [#1108][]: Add a `zap.Must` function that you can use with `NewProduction` or | ||||||
|  |   `NewDevelopment` to panic if the system was unable to build the logger. | ||||||
|  | * [#1118][]: Add a `Logger.Log` method that allows specifying the log level for | ||||||
|  |   a statement dynamically. | ||||||
|  | 
 | ||||||
|  | Thanks to @cardil, @craigpastro, @sashamelentyev, @shota3506, and @zhupeijun | ||||||
|  | for their contributions to this release. | ||||||
|  | 
 | ||||||
|  | [#1071]: https://github.com/uber-go/zap/pull/1071 | ||||||
|  | [#1079]: https://github.com/uber-go/zap/pull/1079 | ||||||
|  | [#1080]: https://github.com/uber-go/zap/pull/1080 | ||||||
|  | [#1088]: https://github.com/uber-go/zap/pull/1088 | ||||||
|  | [#1108]: https://github.com/uber-go/zap/pull/1108 | ||||||
|  | [#1118]: https://github.com/uber-go/zap/pull/1118 | ||||||
|  | 
 | ||||||
|  | ## 1.21.0 (7 Feb 2022) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | *  [#1047][]: Add `zapcore.ParseLevel` to parse a `Level` from a string. | ||||||
|  | *  [#1048][]: Add `zap.ParseAtomicLevel` to parse an `AtomicLevel` from a | ||||||
|  |    string. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | * [#1058][]: Fix panic in JSON encoder when `EncodeLevel` is unset. | ||||||
|  | 
 | ||||||
|  | Other changes: | ||||||
|  | * [#1052][]: Improve encoding performance when the `AddCaller` and | ||||||
|  |   `AddStacktrace` options are used together. | ||||||
|  | 
 | ||||||
|  | [#1047]: https://github.com/uber-go/zap/pull/1047 | ||||||
|  | [#1048]: https://github.com/uber-go/zap/pull/1048 | ||||||
|  | [#1052]: https://github.com/uber-go/zap/pull/1052 | ||||||
|  | [#1058]: https://github.com/uber-go/zap/pull/1058 | ||||||
|  | 
 | ||||||
|  | Thanks to @aerosol and @Techassi for their contributions to this release. | ||||||
|  | 
 | ||||||
|  | ## 1.20.0 (4 Jan 2022) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | * [#989][]: Add `EncoderConfig.SkipLineEnding` flag to disable adding newline | ||||||
|  |   characters between log statements. | ||||||
|  | * [#1039][]: Add `EncoderConfig.NewReflectedEncoder` field to customize JSON | ||||||
|  |   encoding of reflected log fields. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | * [#1011][]: Fix inaccurate precision when encoding complex64 as JSON. | ||||||
|  | * [#554][], [#1017][]: Close JSON namespaces opened in `MarshalLogObject` | ||||||
|  |   methods when the methods return. | ||||||
|  | * [#1033][]: Avoid panicking in Sampler core if `thereafter` is zero. | ||||||
|  | 
 | ||||||
|  | Other changes: | ||||||
|  | * [#1028][]: Drop support for Go < 1.15. | ||||||
|  | 
 | ||||||
|  | [#554]: https://github.com/uber-go/zap/pull/554 | ||||||
|  | [#989]: https://github.com/uber-go/zap/pull/989 | ||||||
|  | [#1011]: https://github.com/uber-go/zap/pull/1011 | ||||||
|  | [#1017]: https://github.com/uber-go/zap/pull/1017 | ||||||
|  | [#1028]: https://github.com/uber-go/zap/pull/1028 | ||||||
|  | [#1033]: https://github.com/uber-go/zap/pull/1033 | ||||||
|  | [#1039]: https://github.com/uber-go/zap/pull/1039 | ||||||
|  | 
 | ||||||
|  | Thanks to @psrajat, @lruggieri, @sammyrnycreal for their contributions to this release. | ||||||
|  | 
 | ||||||
| ## 1.19.1 (8 Sep 2021) | ## 1.19.1 (8 Sep 2021) | ||||||
| 
 | 
 | ||||||
| ### Fixed | Bugfixes: | ||||||
| * [#1001][]: JSON: Fix complex number encoding with negative imaginary part. Thanks to @hemantjadon. | * [#1001][]: JSON: Fix complex number encoding with negative imaginary part. Thanks to @hemantjadon. | ||||||
| * [#1003][]: JSON: Fix inaccurate precision when encoding float32. | * [#1003][]: JSON: Fix inaccurate precision when encoding float32. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ you to accept the CLA when you open your pull request. | ||||||
| 
 | 
 | ||||||
| [Fork][fork], then clone the repository: | [Fork][fork], then clone the repository: | ||||||
| 
 | 
 | ||||||
| ``` | ```bash | ||||||
| mkdir -p $GOPATH/src/go.uber.org | mkdir -p $GOPATH/src/go.uber.org | ||||||
| cd $GOPATH/src/go.uber.org | cd $GOPATH/src/go.uber.org | ||||||
| git clone git@github.com:your_github_username/zap.git | git clone git@github.com:your_github_username/zap.git | ||||||
|  | @ -27,21 +27,16 @@ git fetch upstream | ||||||
| 
 | 
 | ||||||
| Make sure that the tests and the linters pass: | Make sure that the tests and the linters pass: | ||||||
| 
 | 
 | ||||||
| ``` | ```bash | ||||||
| make test | make test | ||||||
| make lint | make lint | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| If you're not using the minor version of Go specified in the Makefile's |  | ||||||
| `LINTABLE_MINOR_VERSIONS` variable, `make lint` doesn't do anything. This is |  | ||||||
| fine, but it means that you'll only discover lint failures after you open your |  | ||||||
| pull request. |  | ||||||
| 
 |  | ||||||
| ## Making Changes | ## Making Changes | ||||||
| 
 | 
 | ||||||
| Start by creating a new branch for your changes: | Start by creating a new branch for your changes: | ||||||
| 
 | 
 | ||||||
| ``` | ```bash | ||||||
| cd $GOPATH/src/go.uber.org/zap | cd $GOPATH/src/go.uber.org/zap | ||||||
| git checkout master | git checkout master | ||||||
| git fetch upstream | git fetch upstream | ||||||
|  | @ -52,22 +47,22 @@ git checkout -b cool_new_feature | ||||||
| Make your changes, then ensure that `make lint` and `make test` still pass. If | Make your changes, then ensure that `make lint` and `make test` still pass. If | ||||||
| you're satisfied with your changes, push them to your fork. | you're satisfied with your changes, push them to your fork. | ||||||
| 
 | 
 | ||||||
| ``` | ```bash | ||||||
| git push origin cool_new_feature | git push origin cool_new_feature | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Then use the GitHub UI to open a pull request. | Then use the GitHub UI to open a pull request. | ||||||
| 
 | 
 | ||||||
| At this point, you're waiting on us to review your changes. We *try* to respond | At this point, you're waiting on us to review your changes. We _try_ to respond | ||||||
| to issues and pull requests within a few business days, and we may suggest some | to issues and pull requests within a few business days, and we may suggest some | ||||||
| improvements or alternatives. Once your changes are approved, one of the | improvements or alternatives. Once your changes are approved, one of the | ||||||
| project maintainers will merge them. | project maintainers will merge them. | ||||||
| 
 | 
 | ||||||
| We're much more likely to approve your changes if you: | We're much more likely to approve your changes if you: | ||||||
| 
 | 
 | ||||||
| * Add tests for new functionality. | - Add tests for new functionality. | ||||||
| * Write a [good commit message][commit-message]. | - Write a [good commit message][commit-message]. | ||||||
| * Maintain backward compatibility. | - Maintain backward compatibility. | ||||||
| 
 | 
 | ||||||
| [fork]: https://github.com/uber-go/zap/fork | [fork]: https://github.com/uber-go/zap/fork | ||||||
| [open-issue]: https://github.com/uber-go/zap/issues/new | [open-issue]: https://github.com/uber-go/zap/issues/new | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ and make many small allocations. Put differently, using `encoding/json` and | ||||||
| Zap takes a different approach. It includes a reflection-free, zero-allocation | Zap takes a different approach. It includes a reflection-free, zero-allocation | ||||||
| JSON encoder, and the base `Logger` strives to avoid serialization overhead | JSON encoder, and the base `Logger` strives to avoid serialization overhead | ||||||
| and allocations wherever possible. By building the high-level `SugaredLogger` | and allocations wherever possible. By building the high-level `SugaredLogger` | ||||||
| on that foundation, zap lets users *choose* when they need to count every | on that foundation, zap lets users _choose_ when they need to count every | ||||||
| allocation and when they'd prefer a more familiar, loosely typed API. | allocation and when they'd prefer a more familiar, loosely typed API. | ||||||
| 
 | 
 | ||||||
| As measured by its own [benchmarking suite][], not only is zap more performant | As measured by its own [benchmarking suite][], not only is zap more performant | ||||||
|  | @ -64,40 +64,40 @@ id="anchor-versions">[1](#footnote-versions)</sup> | ||||||
| 
 | 
 | ||||||
| Log a message and 10 fields: | Log a message and 10 fields: | ||||||
| 
 | 
 | ||||||
| | Package | Time | Time % to zap | Objects Allocated | | | Package             |    Time     | Time % to zap | Objects Allocated | | ||||||
| | :------ | :--: | :-----------: | :---------------: | | | :------------------ | :---------: | :-----------: | :---------------: | | ||||||
| | :zap: zap | 862 ns/op | +0% | 5 allocs/op | | :zap: zap           | 2900 ns/op  |      +0%      |    5 allocs/op    | | ||||||
| | :zap: zap (sugared) | 1250 ns/op | +45% | 11 allocs/op | | :zap: zap (sugared) | 3475 ns/op  |     +20%      |   10 allocs/op    | | ||||||
| | zerolog | 4021 ns/op | +366% | 76 allocs/op | | zerolog             | 10639 ns/op |     +267%     |   32 allocs/op    | | ||||||
| | go-kit | 4542 ns/op | +427% | 105 allocs/op | | go-kit              | 14434 ns/op |     +398%     |   59 allocs/op    | | ||||||
| | apex/log | 26785 ns/op | +3007% | 115 allocs/op | | logrus              | 17104 ns/op |     +490%     |   81 allocs/op    | | ||||||
| | logrus | 29501 ns/op | +3322% | 125 allocs/op | | apex/log            | 32424 ns/op |    +1018%     |   66 allocs/op    | | ||||||
| | log15 | 29906 ns/op | +3369% | 122 allocs/op | | log15               | 33579 ns/op |    +1058%     |   76 allocs/op    | | ||||||
| 
 | 
 | ||||||
| Log a message with a logger that already has 10 fields of context: | Log a message with a logger that already has 10 fields of context: | ||||||
| 
 | 
 | ||||||
| | Package | Time | Time % to zap | Objects Allocated | | | Package             |    Time     | Time % to zap | Objects Allocated | | ||||||
| | :------ | :--: | :-----------: | :---------------: | | | :------------------ | :---------: | :-----------: | :---------------: | | ||||||
| | :zap: zap | 126 ns/op | +0% | 0 allocs/op | | :zap: zap           |  373 ns/op  |      +0%      |    0 allocs/op    | | ||||||
| | :zap: zap (sugared) | 187 ns/op | +48% | 2 allocs/op | | :zap: zap (sugared) |  452 ns/op  |     +21%      |    1 allocs/op    | | ||||||
| | zerolog | 88 ns/op | -30% | 0 allocs/op | | zerolog             |  288 ns/op  |     -23%      |    0 allocs/op    | | ||||||
| | go-kit | 5087 ns/op | +3937% | 103 allocs/op | | go-kit              | 11785 ns/op |    +3060%     |   58 allocs/op    | | ||||||
| | log15 | 18548 ns/op | +14621% | 73 allocs/op | | logrus              | 19629 ns/op |    +5162%     |   70 allocs/op    | | ||||||
| | apex/log | 26012 ns/op | +20544% | 104 allocs/op | | log15               | 21866 ns/op |    +5762%     |   72 allocs/op    | | ||||||
| | logrus | 27236 ns/op | +21516% | 113 allocs/op | | apex/log            | 30890 ns/op |    +8182%     |   55 allocs/op    | | ||||||
| 
 | 
 | ||||||
| Log a static string, without any context or `printf`-style templating: | Log a static string, without any context or `printf`-style templating: | ||||||
| 
 | 
 | ||||||
| | Package | Time | Time % to zap | Objects Allocated | | | Package             |    Time    | Time % to zap | Objects Allocated | | ||||||
| | :------ | :--: | :-----------: | :---------------: | | | :------------------ | :--------: | :-----------: | :---------------: | | ||||||
| | :zap: zap | 118 ns/op | +0% | 0 allocs/op | | :zap: zap           | 381 ns/op  |      +0%      |    0 allocs/op    | | ||||||
| | :zap: zap (sugared) | 191 ns/op | +62% | 2 allocs/op | | :zap: zap (sugared) | 410 ns/op  |      +8%      |    1 allocs/op    | | ||||||
| | zerolog | 93 ns/op | -21% | 0 allocs/op | | zerolog             | 369 ns/op  |      -3%      |    0 allocs/op    | | ||||||
| | go-kit | 280 ns/op | +137% | 11 allocs/op | | standard library    | 385 ns/op  |      +1%      |    2 allocs/op    | | ||||||
| | standard library | 499 ns/op | +323% | 2 allocs/op | | go-kit              | 606 ns/op  |     +59%      |   11 allocs/op    | | ||||||
| | apex/log | 1990 ns/op | +1586% | 10 allocs/op | | logrus              | 1730 ns/op |     +354%     |   25 allocs/op    | | ||||||
| | logrus | 3129 ns/op | +2552% | 24 allocs/op | | apex/log            | 1998 ns/op |     +424%     |    7 allocs/op    | | ||||||
| | log15 | 3887 ns/op | +3194% | 23 allocs/op | | log15               | 4546 ns/op |    +1093%     |   22 allocs/op    | | ||||||
| 
 | 
 | ||||||
| ## Development Status: Stable | ## Development Status: Stable | ||||||
| 
 | 
 | ||||||
|  | @ -131,4 +131,3 @@ pinned in the [benchmarks/go.mod][] file. [↩](#anchor-versions) | ||||||
| [cov]: https://codecov.io/gh/uber-go/zap | [cov]: https://codecov.io/gh/uber-go/zap | ||||||
| [benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks | [benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks | ||||||
| [benchmarks/go.mod]: https://github.com/uber-go/zap/blob/master/benchmarks/go.mod | [benchmarks/go.mod]: https://github.com/uber-go/zap/blob/master/benchmarks/go.mod | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,156 @@ | ||||||
|  | // Copyright (c) 2022 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.
 | ||||||
|  | 
 | ||||||
|  | //go:build go1.18
 | ||||||
|  | // +build go1.18
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Objects constructs a field with the given key, holding a list of the
 | ||||||
|  | // provided objects that can be marshaled by Zap.
 | ||||||
|  | //
 | ||||||
|  | // Note that these objects must implement zapcore.ObjectMarshaler directly.
 | ||||||
|  | // That is, if you're trying to marshal a []Request, the MarshalLogObject
 | ||||||
|  | // method must be declared on the Request type, not its pointer (*Request).
 | ||||||
|  | // If it's on the pointer, use ObjectValues.
 | ||||||
|  | //
 | ||||||
|  | // Given an object that implements MarshalLogObject on the value receiver, you
 | ||||||
|  | // can log a slice of those objects with Objects like so:
 | ||||||
|  | //
 | ||||||
|  | //	type Author struct{ ... }
 | ||||||
|  | //	func (a Author) MarshalLogObject(enc zapcore.ObjectEncoder) error
 | ||||||
|  | //
 | ||||||
|  | //	var authors []Author = ...
 | ||||||
|  | //	logger.Info("loading article", zap.Objects("authors", authors))
 | ||||||
|  | //
 | ||||||
|  | // Similarly, given a type that implements MarshalLogObject on its pointer
 | ||||||
|  | // receiver, you can log a slice of pointers to that object with Objects like
 | ||||||
|  | // so:
 | ||||||
|  | //
 | ||||||
|  | //	type Request struct{ ... }
 | ||||||
|  | //	func (r *Request) MarshalLogObject(enc zapcore.ObjectEncoder) error
 | ||||||
|  | //
 | ||||||
|  | //	var requests []*Request = ...
 | ||||||
|  | //	logger.Info("sending requests", zap.Objects("requests", requests))
 | ||||||
|  | //
 | ||||||
|  | // If instead, you have a slice of values of such an object, use the
 | ||||||
|  | // ObjectValues constructor.
 | ||||||
|  | //
 | ||||||
|  | //	var requests []Request = ...
 | ||||||
|  | //	logger.Info("sending requests", zap.ObjectValues("requests", requests))
 | ||||||
|  | func Objects[T zapcore.ObjectMarshaler](key string, values []T) Field { | ||||||
|  | 	return Array(key, objects[T](values)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type objects[T zapcore.ObjectMarshaler] []T | ||||||
|  | 
 | ||||||
|  | func (os objects[T]) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for _, o := range os { | ||||||
|  | 		if err := arr.AppendObject(o); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ObjectMarshalerPtr is a constraint that specifies that the given type
 | ||||||
|  | // implements zapcore.ObjectMarshaler on a pointer receiver.
 | ||||||
|  | type ObjectMarshalerPtr[T any] interface { | ||||||
|  | 	*T | ||||||
|  | 	zapcore.ObjectMarshaler | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ObjectValues constructs a field with the given key, holding a list of the
 | ||||||
|  | // provided objects, where pointers to these objects can be marshaled by Zap.
 | ||||||
|  | //
 | ||||||
|  | // Note that pointers to these objects must implement zapcore.ObjectMarshaler.
 | ||||||
|  | // That is, if you're trying to marshal a []Request, the MarshalLogObject
 | ||||||
|  | // method must be declared on the *Request type, not the value (Request).
 | ||||||
|  | // If it's on the value, use Objects.
 | ||||||
|  | //
 | ||||||
|  | // Given an object that implements MarshalLogObject on the pointer receiver,
 | ||||||
|  | // you can log a slice of those objects with ObjectValues like so:
 | ||||||
|  | //
 | ||||||
|  | //	type Request struct{ ... }
 | ||||||
|  | //	func (r *Request) MarshalLogObject(enc zapcore.ObjectEncoder) error
 | ||||||
|  | //
 | ||||||
|  | //	var requests []Request = ...
 | ||||||
|  | //	logger.Info("sending requests", zap.ObjectValues("requests", requests))
 | ||||||
|  | //
 | ||||||
|  | // If instead, you have a slice of pointers of such an object, use the Objects
 | ||||||
|  | // field constructor.
 | ||||||
|  | //
 | ||||||
|  | //	var requests []*Request = ...
 | ||||||
|  | //	logger.Info("sending requests", zap.Objects("requests", requests))
 | ||||||
|  | func ObjectValues[T any, P ObjectMarshalerPtr[T]](key string, values []T) Field { | ||||||
|  | 	return Array(key, objectValues[T, P](values)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type objectValues[T any, P ObjectMarshalerPtr[T]] []T | ||||||
|  | 
 | ||||||
|  | func (os objectValues[T, P]) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range os { | ||||||
|  | 		// It is necessary for us to explicitly reference the "P" type.
 | ||||||
|  | 		// We cannot simply pass "&os[i]" to AppendObject because its type
 | ||||||
|  | 		// is "*T", which the type system does not consider as
 | ||||||
|  | 		// implementing ObjectMarshaler.
 | ||||||
|  | 		// Only the type "P" satisfies ObjectMarshaler, which we have
 | ||||||
|  | 		// to convert "*T" to explicitly.
 | ||||||
|  | 		var p P = &os[i] | ||||||
|  | 		if err := arr.AppendObject(p); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stringers constructs a field with the given key, holding a list of the
 | ||||||
|  | // output provided by the value's String method
 | ||||||
|  | //
 | ||||||
|  | // Given an object that implements String on the value receiver, you
 | ||||||
|  | // can log a slice of those objects with Objects like so:
 | ||||||
|  | //
 | ||||||
|  | //	type Request struct{ ... }
 | ||||||
|  | //	func (a Request) String() string
 | ||||||
|  | //
 | ||||||
|  | //	var requests []Request = ...
 | ||||||
|  | //	logger.Info("sending requests", zap.Stringers("requests", requests))
 | ||||||
|  | //
 | ||||||
|  | // Note that these objects must implement fmt.Stringer directly.
 | ||||||
|  | // That is, if you're trying to marshal a []Request, the String method
 | ||||||
|  | // must be declared on the Request type, not its pointer (*Request).
 | ||||||
|  | func Stringers[T fmt.Stringer](key string, values []T) Field { | ||||||
|  | 	return Array(key, stringers[T](values)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type stringers[T fmt.Stringer] []T | ||||||
|  | 
 | ||||||
|  | func (os stringers[T]) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for _, o := range os { | ||||||
|  | 		arr.AppendString(o.String()) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -21,7 +21,7 @@ | ||||||
| package zap | package zap | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"errors" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -182,7 +182,7 @@ func (cfg Config) Build(opts ...Option) (*Logger, error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if cfg.Level == (AtomicLevel{}) { | 	if cfg.Level == (AtomicLevel{}) { | ||||||
| 		return nil, fmt.Errorf("missing Level") | 		return nil, errors.New("missing Level") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log := New( | 	log := New( | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ | ||||||
| // they need to count every allocation and when they'd prefer a more familiar,
 | // they need to count every allocation and when they'd prefer a more familiar,
 | ||||||
| // loosely typed API.
 | // loosely typed API.
 | ||||||
| //
 | //
 | ||||||
| // Choosing a Logger
 | // # Choosing a Logger
 | ||||||
| //
 | //
 | ||||||
| // In contexts where performance is nice, but not critical, use the
 | // In contexts where performance is nice, but not critical, use the
 | ||||||
| // SugaredLogger. It's 4-10x faster than other structured logging packages and
 | // SugaredLogger. It's 4-10x faster than other structured logging packages and
 | ||||||
|  | @ -41,14 +41,15 @@ | ||||||
| // variadic number of key-value pairs. (For more advanced use cases, they also
 | // variadic number of key-value pairs. (For more advanced use cases, they also
 | ||||||
| // accept strongly typed fields - see the SugaredLogger.With documentation for
 | // accept strongly typed fields - see the SugaredLogger.With documentation for
 | ||||||
| // details.)
 | // details.)
 | ||||||
| //  sugar := zap.NewExample().Sugar()
 | //
 | ||||||
| //  defer sugar.Sync()
 | //	sugar := zap.NewExample().Sugar()
 | ||||||
| //  sugar.Infow("failed to fetch URL",
 | //	defer sugar.Sync()
 | ||||||
| //    "url", "http://example.com",
 | //	sugar.Infow("failed to fetch URL",
 | ||||||
| //    "attempt", 3,
 | //	  "url", "http://example.com",
 | ||||||
| //    "backoff", time.Second,
 | //	  "attempt", 3,
 | ||||||
| //  )
 | //	  "backoff", time.Second,
 | ||||||
| //  sugar.Infof("failed to fetch URL: %s", "http://example.com")
 | //	)
 | ||||||
|  | //	sugar.Infof("failed to fetch URL: %s", "http://example.com")
 | ||||||
| //
 | //
 | ||||||
| // By default, loggers are unbuffered. However, since zap's low-level APIs
 | // By default, loggers are unbuffered. However, since zap's low-level APIs
 | ||||||
| // allow buffering, calling Sync before letting your process exit is a good
 | // allow buffering, calling Sync before letting your process exit is a good
 | ||||||
|  | @ -57,32 +58,35 @@ | ||||||
| // In the rare contexts where every microsecond and every allocation matter,
 | // In the rare contexts where every microsecond and every allocation matter,
 | ||||||
| // use the Logger. It's even faster than the SugaredLogger and allocates far
 | // use the Logger. It's even faster than the SugaredLogger and allocates far
 | ||||||
| // less, but it only supports strongly-typed, structured logging.
 | // less, but it only supports strongly-typed, structured logging.
 | ||||||
| //  logger := zap.NewExample()
 | //
 | ||||||
| //  defer logger.Sync()
 | //	logger := zap.NewExample()
 | ||||||
| //  logger.Info("failed to fetch URL",
 | //	defer logger.Sync()
 | ||||||
| //    zap.String("url", "http://example.com"),
 | //	logger.Info("failed to fetch URL",
 | ||||||
| //    zap.Int("attempt", 3),
 | //	  zap.String("url", "http://example.com"),
 | ||||||
| //    zap.Duration("backoff", time.Second),
 | //	  zap.Int("attempt", 3),
 | ||||||
| //  )
 | //	  zap.Duration("backoff", time.Second),
 | ||||||
|  | //	)
 | ||||||
| //
 | //
 | ||||||
| // Choosing between the Logger and SugaredLogger doesn't need to be an
 | // Choosing between the Logger and SugaredLogger doesn't need to be an
 | ||||||
| // application-wide decision: converting between the two is simple and
 | // application-wide decision: converting between the two is simple and
 | ||||||
| // inexpensive.
 | // inexpensive.
 | ||||||
| //   logger := zap.NewExample()
 |  | ||||||
| //   defer logger.Sync()
 |  | ||||||
| //   sugar := logger.Sugar()
 |  | ||||||
| //   plain := sugar.Desugar()
 |  | ||||||
| //
 | //
 | ||||||
| // Configuring Zap
 | //	logger := zap.NewExample()
 | ||||||
|  | //	defer logger.Sync()
 | ||||||
|  | //	sugar := logger.Sugar()
 | ||||||
|  | //	plain := sugar.Desugar()
 | ||||||
|  | //
 | ||||||
|  | // # Configuring Zap
 | ||||||
| //
 | //
 | ||||||
| // The simplest way to build a Logger is to use zap's opinionated presets:
 | // The simplest way to build a Logger is to use zap's opinionated presets:
 | ||||||
| // NewExample, NewProduction, and NewDevelopment. These presets build a logger
 | // NewExample, NewProduction, and NewDevelopment. These presets build a logger
 | ||||||
| // with a single function call:
 | // with a single function call:
 | ||||||
| //  logger, err := zap.NewProduction()
 | //
 | ||||||
| //  if err != nil {
 | //	logger, err := zap.NewProduction()
 | ||||||
| //    log.Fatalf("can't initialize zap logger: %v", err)
 | //	if err != nil {
 | ||||||
| //  }
 | //	  log.Fatalf("can't initialize zap logger: %v", err)
 | ||||||
| //  defer logger.Sync()
 | //	}
 | ||||||
|  | //	defer logger.Sync()
 | ||||||
| //
 | //
 | ||||||
| // Presets are fine for small projects, but larger projects and organizations
 | // Presets are fine for small projects, but larger projects and organizations
 | ||||||
| // naturally require a bit more customization. For most users, zap's Config
 | // naturally require a bit more customization. For most users, zap's Config
 | ||||||
|  | @ -94,7 +98,7 @@ | ||||||
| // go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration
 | // go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration
 | ||||||
| // example for sample code.
 | // example for sample code.
 | ||||||
| //
 | //
 | ||||||
| // Extending Zap
 | // # Extending Zap
 | ||||||
| //
 | //
 | ||||||
| // The zap package itself is a relatively thin wrapper around the interfaces
 | // The zap package itself is a relatively thin wrapper around the interfaces
 | ||||||
| // in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g.,
 | // in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g.,
 | ||||||
|  | @ -106,7 +110,7 @@ | ||||||
| // Similarly, package authors can use the high-performance Encoder and Core
 | // Similarly, package authors can use the high-performance Encoder and Core
 | ||||||
| // implementations in the zapcore package to build their own loggers.
 | // implementations in the zapcore package to build their own loggers.
 | ||||||
| //
 | //
 | ||||||
| // Frequently Asked Questions
 | // # Frequently Asked Questions
 | ||||||
| //
 | //
 | ||||||
| // An FAQ covering everything from installation errors to design decisions is
 | // An FAQ covering everything from installation errors to design decisions is
 | ||||||
| // available at https://github.com/uber-go/zap/blob/master/FAQ.md.
 | // available at https://github.com/uber-go/zap/blob/master/FAQ.md.
 | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapco | ||||||
| 
 | 
 | ||||||
| func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { | func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { | ||||||
| 	if encoderConfig.TimeKey != "" && encoderConfig.EncodeTime == nil { | 	if encoderConfig.TimeKey != "" && encoderConfig.EncodeTime == nil { | ||||||
| 		return nil, fmt.Errorf("missing EncodeTime in EncoderConfig") | 		return nil, errors.New("missing EncodeTime in EncoderConfig") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_encoderMutex.RLock() | 	_encoderMutex.RLock() | ||||||
|  |  | ||||||
|  | @ -31,6 +31,7 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | 	_stdLogDefaultDepth      = 1 | ||||||
| 	_loggerWriterDepth       = 2 | 	_loggerWriterDepth       = 2 | ||||||
| 	_programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " + | 	_programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " + | ||||||
| 		"https://github.com/uber-go/zap/issues/new and reference this error: %v" | 		"https://github.com/uber-go/zap/issues/new and reference this error: %v" | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ package zap | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -32,22 +33,23 @@ import ( | ||||||
| // ServeHTTP is a simple JSON endpoint that can report on or change the current
 | // ServeHTTP is a simple JSON endpoint that can report on or change the current
 | ||||||
| // logging level.
 | // logging level.
 | ||||||
| //
 | //
 | ||||||
| // GET
 | // # GET
 | ||||||
| //
 | //
 | ||||||
| // The GET request returns a JSON description of the current logging level like:
 | // The GET request returns a JSON description of the current logging level like:
 | ||||||
| //   {"level":"info"}
 |  | ||||||
| //
 | //
 | ||||||
| // PUT
 | //	{"level":"info"}
 | ||||||
|  | //
 | ||||||
|  | // # PUT
 | ||||||
| //
 | //
 | ||||||
| // The PUT request changes the logging level. It is perfectly safe to change the
 | // The PUT request changes the logging level. It is perfectly safe to change the
 | ||||||
| // logging level while a program is running. Two content types are supported:
 | // logging level while a program is running. Two content types are supported:
 | ||||||
| //
 | //
 | ||||||
| //    Content-Type: application/x-www-form-urlencoded
 | //	Content-Type: application/x-www-form-urlencoded
 | ||||||
| //
 | //
 | ||||||
| // With this content type, the level can be provided through the request body or
 | // With this content type, the level can be provided through the request body or
 | ||||||
| // a query parameter. The log level is URL encoded like:
 | // a query parameter. The log level is URL encoded like:
 | ||||||
| //
 | //
 | ||||||
| //    level=debug
 | //	level=debug
 | ||||||
| //
 | //
 | ||||||
| // The request body takes precedence over the query parameter, if both are
 | // The request body takes precedence over the query parameter, if both are
 | ||||||
| // specified.
 | // specified.
 | ||||||
|  | @ -55,18 +57,17 @@ import ( | ||||||
| // This content type is the default for a curl PUT request. Following are two
 | // This content type is the default for a curl PUT request. Following are two
 | ||||||
| // example curl requests that both set the logging level to debug.
 | // example curl requests that both set the logging level to debug.
 | ||||||
| //
 | //
 | ||||||
| //    curl -X PUT localhost:8080/log/level?level=debug
 | //	curl -X PUT localhost:8080/log/level?level=debug
 | ||||||
| //    curl -X PUT localhost:8080/log/level -d level=debug
 | //	curl -X PUT localhost:8080/log/level -d level=debug
 | ||||||
| //
 | //
 | ||||||
| // For any other content type, the payload is expected to be JSON encoded and
 | // For any other content type, the payload is expected to be JSON encoded and
 | ||||||
| // look like:
 | // look like:
 | ||||||
| //
 | //
 | ||||||
| //   {"level":"info"}
 | //	{"level":"info"}
 | ||||||
| //
 | //
 | ||||||
| // An example curl request could look like this:
 | // An example curl request could look like this:
 | ||||||
| //
 | //
 | ||||||
| //    curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}'
 | //	curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}'
 | ||||||
| //
 |  | ||||||
| func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { | func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
| 	type errorResponse struct { | 	type errorResponse struct { | ||||||
| 		Error string `json:"error"` | 		Error string `json:"error"` | ||||||
|  | @ -108,7 +109,7 @@ func decodePutRequest(contentType string, r *http.Request) (zapcore.Level, error | ||||||
| func decodePutURL(r *http.Request) (zapcore.Level, error) { | func decodePutURL(r *http.Request) (zapcore.Level, error) { | ||||||
| 	lvl := r.FormValue("level") | 	lvl := r.FormValue("level") | ||||||
| 	if lvl == "" { | 	if lvl == "" { | ||||||
| 		return 0, fmt.Errorf("must specify logging level") | 		return 0, errors.New("must specify logging level") | ||||||
| 	} | 	} | ||||||
| 	var l zapcore.Level | 	var l zapcore.Level | ||||||
| 	if err := l.UnmarshalText([]byte(lvl)); err != nil { | 	if err := l.UnmarshalText([]byte(lvl)); err != nil { | ||||||
|  | @ -125,7 +126,7 @@ func decodePutJSON(body io.Reader) (zapcore.Level, error) { | ||||||
| 		return 0, fmt.Errorf("malformed request body: %v", err) | 		return 0, fmt.Errorf("malformed request body: %v", err) | ||||||
| 	} | 	} | ||||||
| 	if pld.Level == nil { | 	if pld.Level == nil { | ||||||
| 		return 0, fmt.Errorf("must specify logging level") | 		return 0, errors.New("must specify logging level") | ||||||
| 	} | 	} | ||||||
| 	return *pld.Level, nil | 	return *pld.Level, nil | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,24 +24,25 @@ package exit | ||||||
| 
 | 
 | ||||||
| import "os" | import "os" | ||||||
| 
 | 
 | ||||||
| var real = func() { os.Exit(1) } | var _exit = os.Exit | ||||||
| 
 | 
 | ||||||
| // Exit normally terminates the process by calling os.Exit(1). If the package
 | // With terminates the process by calling os.Exit(code). If the package is
 | ||||||
| // is stubbed, it instead records a call in the testing spy.
 | // stubbed, it instead records a call in the testing spy.
 | ||||||
| func Exit() { | func With(code int) { | ||||||
| 	real() | 	_exit(code) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // A StubbedExit is a testing fake for os.Exit.
 | // A StubbedExit is a testing fake for os.Exit.
 | ||||||
| type StubbedExit struct { | type StubbedExit struct { | ||||||
| 	Exited bool | 	Exited bool | ||||||
| 	prev   func() | 	Code   int | ||||||
|  | 	prev   func(code int) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Stub substitutes a fake for the call to os.Exit(1).
 | // Stub substitutes a fake for the call to os.Exit(1).
 | ||||||
| func Stub() *StubbedExit { | func Stub() *StubbedExit { | ||||||
| 	s := &StubbedExit{prev: real} | 	s := &StubbedExit{prev: _exit} | ||||||
| 	real = s.exit | 	_exit = s.exit | ||||||
| 	return s | 	return s | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -56,9 +57,10 @@ func WithStub(f func()) *StubbedExit { | ||||||
| 
 | 
 | ||||||
| // Unstub restores the previous exit function.
 | // Unstub restores the previous exit function.
 | ||||||
| func (se *StubbedExit) Unstub() { | func (se *StubbedExit) Unstub() { | ||||||
| 	real = se.prev | 	_exit = se.prev | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (se *StubbedExit) exit() { | func (se *StubbedExit) exit(code int) { | ||||||
| 	se.Exited = true | 	se.Exited = true | ||||||
|  | 	se.Code = code | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| // Copyright (c) 2019 Uber Technologies, Inc.
 | // Copyright (c) 2022 Uber Technologies, Inc.
 | ||||||
| //
 | //
 | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
| // of this software and associated documentation files (the "Software"), to deal
 | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | @ -18,9 +18,18 @@ | ||||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
| // THE SOFTWARE.
 | // THE SOFTWARE.
 | ||||||
| 
 | 
 | ||||||
| // See #682 for more information.
 | package internal | ||||||
| // +build go1.12
 |  | ||||||
| 
 | 
 | ||||||
| package zap | import "go.uber.org/zap/zapcore" | ||||||
| 
 | 
 | ||||||
| const _stdLogDefaultDepth = 1 | // LeveledEnabler is an interface satisfied by LevelEnablers that are able to
 | ||||||
|  | // report their own level.
 | ||||||
|  | //
 | ||||||
|  | // This interface is defined to use more conveniently in tests and non-zapcore
 | ||||||
|  | // packages.
 | ||||||
|  | // This cannot be imported from zapcore because of the cyclic dependency.
 | ||||||
|  | type LeveledEnabler interface { | ||||||
|  | 	zapcore.LevelEnabler | ||||||
|  | 
 | ||||||
|  | 	Level() zapcore.Level | ||||||
|  | } | ||||||
|  | @ -0,0 +1,50 @@ | ||||||
|  | // Copyright (c) 2021 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package ztest | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/benbjohnson/clock" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // MockClock provides control over the time.
 | ||||||
|  | type MockClock struct{ m *clock.Mock } | ||||||
|  | 
 | ||||||
|  | // NewMockClock builds a new mock clock that provides control of time.
 | ||||||
|  | func NewMockClock() *MockClock { | ||||||
|  | 	return &MockClock{clock.NewMock()} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Now reports the current time.
 | ||||||
|  | func (c *MockClock) Now() time.Time { | ||||||
|  | 	return c.m.Now() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewTicker returns a time.Ticker that ticks at the specified frequency.
 | ||||||
|  | func (c *MockClock) NewTicker(d time.Duration) *time.Ticker { | ||||||
|  | 	return &time.Ticker{C: c.m.Ticker(d).C} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add progresses time by the given duration.
 | ||||||
|  | func (c *MockClock) Add(d time.Duration) { | ||||||
|  | 	c.m.Add(d) | ||||||
|  | } | ||||||
|  | @ -42,11 +42,11 @@ func Sleep(base time.Duration) { | ||||||
| // Initialize checks the environment and alters the timeout scale accordingly.
 | // Initialize checks the environment and alters the timeout scale accordingly.
 | ||||||
| // It returns a function to undo the scaling.
 | // It returns a function to undo the scaling.
 | ||||||
| func Initialize(factor string) func() { | func Initialize(factor string) func() { | ||||||
| 	original := _timeoutScale |  | ||||||
| 	fv, err := strconv.ParseFloat(factor, 64) | 	fv, err := strconv.ParseFloat(factor, 64) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
|  | 	original := _timeoutScale | ||||||
| 	_timeoutScale = fv | 	_timeoutScale = fv | ||||||
| 	return func() { _timeoutScale = original } | 	return func() { _timeoutScale = original } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ package ztest | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io/ioutil" | 	"io" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -50,12 +50,12 @@ func (s *Syncer) Called() bool { | ||||||
| 	return s.called | 	return s.called | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // A Discarder sends all writes to ioutil.Discard.
 | // A Discarder sends all writes to io.Discard.
 | ||||||
| type Discarder struct{ Syncer } | type Discarder struct{ Syncer } | ||||||
| 
 | 
 | ||||||
| // Write implements io.Writer.
 | // Write implements io.Writer.
 | ||||||
| func (d *Discarder) Write(b []byte) (int, error) { | func (d *Discarder) Write(b []byte) (int, error) { | ||||||
| 	return ioutil.Discard.Write(b) | 	return io.Discard.Write(b) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FailWriter is a WriteSyncer that always returns an error on writes.
 | // FailWriter is a WriteSyncer that always returns an error on writes.
 | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ package zap | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"go.uber.org/atomic" | 	"go.uber.org/atomic" | ||||||
|  | 	"go.uber.org/zap/internal" | ||||||
| 	"go.uber.org/zap/zapcore" | 	"go.uber.org/zap/zapcore" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -70,6 +71,8 @@ type AtomicLevel struct { | ||||||
| 	l *atomic.Int32 | 	l *atomic.Int32 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var _ internal.LeveledEnabler = AtomicLevel{} | ||||||
|  | 
 | ||||||
| // NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging
 | // NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging
 | ||||||
| // enabled.
 | // enabled.
 | ||||||
| func NewAtomicLevel() AtomicLevel { | func NewAtomicLevel() AtomicLevel { | ||||||
|  | @ -86,6 +89,23 @@ func NewAtomicLevelAt(l zapcore.Level) AtomicLevel { | ||||||
| 	return a | 	return a | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ParseAtomicLevel parses an AtomicLevel based on a lowercase or all-caps ASCII
 | ||||||
|  | // representation of the log level. If the provided ASCII representation is
 | ||||||
|  | // invalid an error is returned.
 | ||||||
|  | //
 | ||||||
|  | // This is particularly useful when dealing with text input to configure log
 | ||||||
|  | // levels.
 | ||||||
|  | func ParseAtomicLevel(text string) (AtomicLevel, error) { | ||||||
|  | 	a := NewAtomicLevel() | ||||||
|  | 	l, err := zapcore.ParseLevel(text) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return a, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	a.SetLevel(l) | ||||||
|  | 	return a, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Enabled implements the zapcore.LevelEnabler interface, which allows the
 | // Enabled implements the zapcore.LevelEnabler interface, which allows the
 | ||||||
| // AtomicLevel to be used in place of traditional static levels.
 | // AtomicLevel to be used in place of traditional static levels.
 | ||||||
| func (lvl AtomicLevel) Enabled(l zapcore.Level) bool { | func (lvl AtomicLevel) Enabled(l zapcore.Level) bool { | ||||||
|  |  | ||||||
|  | @ -22,11 +22,11 @@ package zap | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"runtime" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"go.uber.org/zap/internal/bufferpool" | ||||||
| 	"go.uber.org/zap/zapcore" | 	"go.uber.org/zap/zapcore" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -42,7 +42,7 @@ type Logger struct { | ||||||
| 
 | 
 | ||||||
| 	development bool | 	development bool | ||||||
| 	addCaller   bool | 	addCaller   bool | ||||||
| 	onFatal     zapcore.CheckWriteAction // default is WriteThenFatal
 | 	onFatal     zapcore.CheckWriteHook // default is WriteThenFatal
 | ||||||
| 
 | 
 | ||||||
| 	name        string | 	name        string | ||||||
| 	errorOutput zapcore.WriteSyncer | 	errorOutput zapcore.WriteSyncer | ||||||
|  | @ -85,7 +85,7 @@ func New(core zapcore.Core, options ...Option) *Logger { | ||||||
| func NewNop() *Logger { | func NewNop() *Logger { | ||||||
| 	return &Logger{ | 	return &Logger{ | ||||||
| 		core:        zapcore.NewNopCore(), | 		core:        zapcore.NewNopCore(), | ||||||
| 		errorOutput: zapcore.AddSync(ioutil.Discard), | 		errorOutput: zapcore.AddSync(io.Discard), | ||||||
| 		addStack:    zapcore.FatalLevel + 1, | 		addStack:    zapcore.FatalLevel + 1, | ||||||
| 		clock:       zapcore.DefaultClock, | 		clock:       zapcore.DefaultClock, | ||||||
| 	} | 	} | ||||||
|  | @ -107,6 +107,19 @@ func NewDevelopment(options ...Option) (*Logger, error) { | ||||||
| 	return NewDevelopmentConfig().Build(options...) | 	return NewDevelopmentConfig().Build(options...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Must is a helper that wraps a call to a function returning (*Logger, error)
 | ||||||
|  | // and panics if the error is non-nil. It is intended for use in variable
 | ||||||
|  | // initialization such as:
 | ||||||
|  | //
 | ||||||
|  | //	var logger = zap.Must(zap.NewProduction())
 | ||||||
|  | func Must(logger *Logger, err error) *Logger { | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return logger | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // NewExample builds a Logger that's designed for use in zap's testable
 | // NewExample builds a Logger that's designed for use in zap's testable
 | ||||||
| // examples. It writes DebugLevel and above logs to standard out as JSON, but
 | // examples. It writes DebugLevel and above logs to standard out as JSON, but
 | ||||||
| // omits the timestamp and calling function to keep example output
 | // omits the timestamp and calling function to keep example output
 | ||||||
|  | @ -170,6 +183,13 @@ func (log *Logger) With(fields ...Field) *Logger { | ||||||
| 	return l | 	return l | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Level reports the minimum enabled level for this logger.
 | ||||||
|  | //
 | ||||||
|  | // For NopLoggers, this is [zapcore.InvalidLevel].
 | ||||||
|  | func (log *Logger) Level() zapcore.Level { | ||||||
|  | 	return zapcore.LevelOf(log.core) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Check returns a CheckedEntry if logging a message at the specified level
 | // Check returns a CheckedEntry if logging a message at the specified level
 | ||||||
| // is enabled. It's a completely optional optimization; in high-performance
 | // is enabled. It's a completely optional optimization; in high-performance
 | ||||||
| // applications, Check can help avoid allocating a slice to hold fields.
 | // applications, Check can help avoid allocating a slice to hold fields.
 | ||||||
|  | @ -177,6 +197,14 @@ func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { | ||||||
| 	return log.check(lvl, msg) | 	return log.check(lvl, msg) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Log logs a message at the specified level. The message includes any fields
 | ||||||
|  | // passed at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | func (log *Logger) Log(lvl zapcore.Level, msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(lvl, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Debug logs a message at DebugLevel. The message includes any fields passed
 | // Debug logs a message at DebugLevel. The message includes any fields passed
 | ||||||
| // at the log site, as well as any fields accumulated on the logger.
 | // at the log site, as well as any fields accumulated on the logger.
 | ||||||
| func (log *Logger) Debug(msg string, fields ...Field) { | func (log *Logger) Debug(msg string, fields ...Field) { | ||||||
|  | @ -259,8 +287,10 @@ func (log *Logger) clone() *Logger { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { | func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { | ||||||
| 	// check must always be called directly by a method in the Logger interface
 | 	// Logger.check must always be called directly by a method in the
 | ||||||
| 	// (e.g., Check, Info, Fatal).
 | 	// Logger interface (e.g., Check, Info, Fatal).
 | ||||||
|  | 	// This skips Logger.check and the Info/Fatal/Check/etc. method that
 | ||||||
|  | 	// called it.
 | ||||||
| 	const callerSkipOffset = 2 | 	const callerSkipOffset = 2 | ||||||
| 
 | 
 | ||||||
| 	// Check the level first to reduce the cost of disabled log calls.
 | 	// Check the level first to reduce the cost of disabled log calls.
 | ||||||
|  | @ -283,18 +313,27 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { | ||||||
| 	// Set up any required terminal behavior.
 | 	// Set up any required terminal behavior.
 | ||||||
| 	switch ent.Level { | 	switch ent.Level { | ||||||
| 	case zapcore.PanicLevel: | 	case zapcore.PanicLevel: | ||||||
| 		ce = ce.Should(ent, zapcore.WriteThenPanic) | 		ce = ce.After(ent, zapcore.WriteThenPanic) | ||||||
| 	case zapcore.FatalLevel: | 	case zapcore.FatalLevel: | ||||||
| 		onFatal := log.onFatal | 		onFatal := log.onFatal | ||||||
| 		// Noop is the default value for CheckWriteAction, and it leads to
 | 		// nil or WriteThenNoop will lead to continued execution after
 | ||||||
| 		// continued execution after a Fatal which is unexpected.
 | 		// a Fatal log entry, which is unexpected. For example,
 | ||||||
| 		if onFatal == zapcore.WriteThenNoop { | 		//
 | ||||||
|  | 		//   f, err := os.Open(..)
 | ||||||
|  | 		//   if err != nil {
 | ||||||
|  | 		//     log.Fatal("cannot open", zap.Error(err))
 | ||||||
|  | 		//   }
 | ||||||
|  | 		//   fmt.Println(f.Name())
 | ||||||
|  | 		//
 | ||||||
|  | 		// The f.Name() will panic if we continue execution after the
 | ||||||
|  | 		// log.Fatal.
 | ||||||
|  | 		if onFatal == nil || onFatal == zapcore.WriteThenNoop { | ||||||
| 			onFatal = zapcore.WriteThenFatal | 			onFatal = zapcore.WriteThenFatal | ||||||
| 		} | 		} | ||||||
| 		ce = ce.Should(ent, onFatal) | 		ce = ce.After(ent, onFatal) | ||||||
| 	case zapcore.DPanicLevel: | 	case zapcore.DPanicLevel: | ||||||
| 		if log.development { | 		if log.development { | ||||||
| 			ce = ce.Should(ent, zapcore.WriteThenPanic) | 			ce = ce.After(ent, zapcore.WriteThenPanic) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -307,42 +346,55 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { | ||||||
| 
 | 
 | ||||||
| 	// Thread the error output through to the CheckedEntry.
 | 	// Thread the error output through to the CheckedEntry.
 | ||||||
| 	ce.ErrorOutput = log.errorOutput | 	ce.ErrorOutput = log.errorOutput | ||||||
| 	if log.addCaller { | 
 | ||||||
| 		frame, defined := getCallerFrame(log.callerSkip + callerSkipOffset) | 	addStack := log.addStack.Enabled(ce.Level) | ||||||
| 		if !defined { | 	if !log.addCaller && !addStack { | ||||||
|  | 		return ce | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Adding the caller or stack trace requires capturing the callers of
 | ||||||
|  | 	// this function. We'll share information between these two.
 | ||||||
|  | 	stackDepth := stacktraceFirst | ||||||
|  | 	if addStack { | ||||||
|  | 		stackDepth = stacktraceFull | ||||||
|  | 	} | ||||||
|  | 	stack := captureStacktrace(log.callerSkip+callerSkipOffset, stackDepth) | ||||||
|  | 	defer stack.Free() | ||||||
|  | 
 | ||||||
|  | 	if stack.Count() == 0 { | ||||||
|  | 		if log.addCaller { | ||||||
| 			fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", ent.Time.UTC()) | 			fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", ent.Time.UTC()) | ||||||
| 			log.errorOutput.Sync() | 			log.errorOutput.Sync() | ||||||
| 		} | 		} | ||||||
|  | 		return ce | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		ce.Entry.Caller = zapcore.EntryCaller{ | 	frame, more := stack.Next() | ||||||
| 			Defined:  defined, | 
 | ||||||
|  | 	if log.addCaller { | ||||||
|  | 		ce.Caller = zapcore.EntryCaller{ | ||||||
|  | 			Defined:  frame.PC != 0, | ||||||
| 			PC:       frame.PC, | 			PC:       frame.PC, | ||||||
| 			File:     frame.File, | 			File:     frame.File, | ||||||
| 			Line:     frame.Line, | 			Line:     frame.Line, | ||||||
| 			Function: frame.Function, | 			Function: frame.Function, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if log.addStack.Enabled(ce.Entry.Level) { | 
 | ||||||
| 		ce.Entry.Stack = StackSkip("", log.callerSkip+callerSkipOffset).String | 	if addStack { | ||||||
|  | 		buffer := bufferpool.Get() | ||||||
|  | 		defer buffer.Free() | ||||||
|  | 
 | ||||||
|  | 		stackfmt := newStackFormatter(buffer) | ||||||
|  | 
 | ||||||
|  | 		// We've already extracted the first frame, so format that
 | ||||||
|  | 		// separately and defer to stackfmt for the rest.
 | ||||||
|  | 		stackfmt.FormatFrame(frame) | ||||||
|  | 		if more { | ||||||
|  | 			stackfmt.FormatStack(stack) | ||||||
|  | 		} | ||||||
|  | 		ce.Stack = buffer.String() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return ce | 	return ce | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // getCallerFrame gets caller frame. The argument skip is the number of stack
 |  | ||||||
| // frames to ascend, with 0 identifying the caller of getCallerFrame. The
 |  | ||||||
| // boolean ok is false if it was not possible to recover the information.
 |  | ||||||
| //
 |  | ||||||
| // Note: This implementation is similar to runtime.Caller, but it returns the whole frame.
 |  | ||||||
| func getCallerFrame(skip int) (frame runtime.Frame, ok bool) { |  | ||||||
| 	const skipOffset = 2 // skip getCallerFrame and Callers
 |  | ||||||
| 
 |  | ||||||
| 	pc := make([]uintptr, 1) |  | ||||||
| 	numFrames := runtime.Callers(skip+skipOffset, pc) |  | ||||||
| 	if numFrames < 1 { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	frame, _ = runtime.CallersFrames(pc).Next() |  | ||||||
| 	return frame, frame.PC != 0 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -133,9 +133,28 @@ func IncreaseLevel(lvl zapcore.LevelEnabler) Option { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // OnFatal sets the action to take on fatal logs.
 | // OnFatal sets the action to take on fatal logs.
 | ||||||
|  | //
 | ||||||
|  | // Deprecated: Use [WithFatalHook] instead.
 | ||||||
| func OnFatal(action zapcore.CheckWriteAction) Option { | func OnFatal(action zapcore.CheckWriteAction) Option { | ||||||
|  | 	return WithFatalHook(action) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithFatalHook sets a CheckWriteHook to run on fatal logs.
 | ||||||
|  | // Zap will call this hook after writing a log statement with a Fatal level.
 | ||||||
|  | //
 | ||||||
|  | // For example, the following builds a logger that will exit the current
 | ||||||
|  | // goroutine after writing a fatal log message, but it will not exit the
 | ||||||
|  | // program.
 | ||||||
|  | //
 | ||||||
|  | //	zap.New(core, zap.WithFatalHook(zapcore.WriteThenGoexit))
 | ||||||
|  | //
 | ||||||
|  | // It is important that the provided CheckWriteHook stops the control flow at
 | ||||||
|  | // the current statement to meet expectations of callers of the logger.
 | ||||||
|  | // We recommend calling os.Exit or runtime.Goexit inside custom hooks at
 | ||||||
|  | // minimum.
 | ||||||
|  | func WithFatalHook(hook zapcore.CheckWriteHook) Option { | ||||||
| 	return optionFunc(func(log *Logger) { | 	return optionFunc(func(log *Logger) { | ||||||
| 		log.onFatal = action | 		log.onFatal = hook | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| // Copyright (c) 2016 Uber Technologies, Inc.
 | // Copyright (c) 2016-2022 Uber Technologies, Inc.
 | ||||||
| //
 | //
 | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
| // of this software and associated documentation files (the "Software"), to deal
 | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | @ -26,6 +26,7 @@ import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
|  | @ -34,23 +35,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| const schemeFile = "file" | const schemeFile = "file" | ||||||
| 
 | 
 | ||||||
| var ( | var _sinkRegistry = newSinkRegistry() | ||||||
| 	_sinkMutex     sync.RWMutex |  | ||||||
| 	_sinkFactories map[string]func(*url.URL) (Sink, error) // keyed by scheme
 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	resetSinkRegistry() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func resetSinkRegistry() { |  | ||||||
| 	_sinkMutex.Lock() |  | ||||||
| 	defer _sinkMutex.Unlock() |  | ||||||
| 
 |  | ||||||
| 	_sinkFactories = map[string]func(*url.URL) (Sink, error){ |  | ||||||
| 		schemeFile: newFileSink, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // Sink defines the interface to write to and close logger destinations.
 | // Sink defines the interface to write to and close logger destinations.
 | ||||||
| type Sink interface { | type Sink interface { | ||||||
|  | @ -58,10 +43,6 @@ type Sink interface { | ||||||
| 	io.Closer | 	io.Closer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type nopCloserSink struct{ zapcore.WriteSyncer } |  | ||||||
| 
 |  | ||||||
| func (nopCloserSink) Close() error { return nil } |  | ||||||
| 
 |  | ||||||
| type errSinkNotFound struct { | type errSinkNotFound struct { | ||||||
| 	scheme string | 	scheme string | ||||||
| } | } | ||||||
|  | @ -70,16 +51,29 @@ func (e *errSinkNotFound) Error() string { | ||||||
| 	return fmt.Sprintf("no sink found for scheme %q", e.scheme) | 	return fmt.Sprintf("no sink found for scheme %q", e.scheme) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RegisterSink registers a user-supplied factory for all sinks with a
 | type nopCloserSink struct{ zapcore.WriteSyncer } | ||||||
| // particular scheme.
 | 
 | ||||||
| //
 | func (nopCloserSink) Close() error { return nil } | ||||||
| // All schemes must be ASCII, valid under section 3.1 of RFC 3986
 | 
 | ||||||
| // (https://tools.ietf.org/html/rfc3986#section-3.1), and must not already
 | type sinkRegistry struct { | ||||||
| // have a factory registered. Zap automatically registers a factory for the
 | 	mu        sync.Mutex | ||||||
| // "file" scheme.
 | 	factories map[string]func(*url.URL) (Sink, error)          // keyed by scheme
 | ||||||
| func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { | 	openFile  func(string, int, os.FileMode) (*os.File, error) // type matches os.OpenFile
 | ||||||
| 	_sinkMutex.Lock() | } | ||||||
| 	defer _sinkMutex.Unlock() | 
 | ||||||
|  | func newSinkRegistry() *sinkRegistry { | ||||||
|  | 	sr := &sinkRegistry{ | ||||||
|  | 		factories: make(map[string]func(*url.URL) (Sink, error)), | ||||||
|  | 		openFile:  os.OpenFile, | ||||||
|  | 	} | ||||||
|  | 	sr.RegisterSink(schemeFile, sr.newFileSinkFromURL) | ||||||
|  | 	return sr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RegisterScheme registers the given factory for the specific scheme.
 | ||||||
|  | func (sr *sinkRegistry) RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { | ||||||
|  | 	sr.mu.Lock() | ||||||
|  | 	defer sr.mu.Unlock() | ||||||
| 
 | 
 | ||||||
| 	if scheme == "" { | 	if scheme == "" { | ||||||
| 		return errors.New("can't register a sink factory for empty string") | 		return errors.New("can't register a sink factory for empty string") | ||||||
|  | @ -88,14 +82,22 @@ func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("%q is not a valid scheme: %v", scheme, err) | 		return fmt.Errorf("%q is not a valid scheme: %v", scheme, err) | ||||||
| 	} | 	} | ||||||
| 	if _, ok := _sinkFactories[normalized]; ok { | 	if _, ok := sr.factories[normalized]; ok { | ||||||
| 		return fmt.Errorf("sink factory already registered for scheme %q", normalized) | 		return fmt.Errorf("sink factory already registered for scheme %q", normalized) | ||||||
| 	} | 	} | ||||||
| 	_sinkFactories[normalized] = factory | 	sr.factories[normalized] = factory | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newSink(rawURL string) (Sink, error) { | func (sr *sinkRegistry) newSink(rawURL string) (Sink, error) { | ||||||
|  | 	// URL parsing doesn't work well for Windows paths such as `c:\log.txt`, as scheme is set to
 | ||||||
|  | 	// the drive, and path is unset unless `c:/log.txt` is used.
 | ||||||
|  | 	// To avoid Windows-specific URL handling, we instead check IsAbs to open as a file.
 | ||||||
|  | 	// filepath.IsAbs is OS-specific, so IsAbs('c:/log.txt') is false outside of Windows.
 | ||||||
|  | 	if filepath.IsAbs(rawURL) { | ||||||
|  | 		return sr.newFileSinkFromPath(rawURL) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	u, err := url.Parse(rawURL) | 	u, err := url.Parse(rawURL) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err) | 		return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err) | ||||||
|  | @ -104,16 +106,27 @@ func newSink(rawURL string) (Sink, error) { | ||||||
| 		u.Scheme = schemeFile | 		u.Scheme = schemeFile | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_sinkMutex.RLock() | 	sr.mu.Lock() | ||||||
| 	factory, ok := _sinkFactories[u.Scheme] | 	factory, ok := sr.factories[u.Scheme] | ||||||
| 	_sinkMutex.RUnlock() | 	sr.mu.Unlock() | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, &errSinkNotFound{u.Scheme} | 		return nil, &errSinkNotFound{u.Scheme} | ||||||
| 	} | 	} | ||||||
| 	return factory(u) | 	return factory(u) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newFileSink(u *url.URL) (Sink, error) { | // RegisterSink registers a user-supplied factory for all sinks with a
 | ||||||
|  | // particular scheme.
 | ||||||
|  | //
 | ||||||
|  | // All schemes must be ASCII, valid under section 0.1 of RFC 3986
 | ||||||
|  | // (https://tools.ietf.org/html/rfc3983#section-3.1), and must not already
 | ||||||
|  | // have a factory registered. Zap automatically registers a factory for the
 | ||||||
|  | // "file" scheme.
 | ||||||
|  | func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { | ||||||
|  | 	return _sinkRegistry.RegisterSink(scheme, factory) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sr *sinkRegistry) newFileSinkFromURL(u *url.URL) (Sink, error) { | ||||||
| 	if u.User != nil { | 	if u.User != nil { | ||||||
| 		return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) | 		return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) | ||||||
| 	} | 	} | ||||||
|  | @ -130,13 +143,18 @@ func newFileSink(u *url.URL) (Sink, error) { | ||||||
| 	if hn := u.Hostname(); hn != "" && hn != "localhost" { | 	if hn := u.Hostname(); hn != "" && hn != "localhost" { | ||||||
| 		return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) | 		return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) | ||||||
| 	} | 	} | ||||||
| 	switch u.Path { | 
 | ||||||
|  | 	return sr.newFileSinkFromPath(u.Path) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sr *sinkRegistry) newFileSinkFromPath(path string) (Sink, error) { | ||||||
|  | 	switch path { | ||||||
| 	case "stdout": | 	case "stdout": | ||||||
| 		return nopCloserSink{os.Stdout}, nil | 		return nopCloserSink{os.Stdout}, nil | ||||||
| 	case "stderr": | 	case "stderr": | ||||||
| 		return nopCloserSink{os.Stderr}, nil | 		return nopCloserSink{os.Stderr}, nil | ||||||
| 	} | 	} | ||||||
| 	return os.OpenFile(u.Path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) | 	return sr.openFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func normalizeScheme(s string) (string, error) { | func normalizeScheme(s string) (string, error) { | ||||||
|  |  | ||||||
|  | @ -24,62 +24,153 @@ import ( | ||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
|  | 	"go.uber.org/zap/buffer" | ||||||
| 	"go.uber.org/zap/internal/bufferpool" | 	"go.uber.org/zap/internal/bufferpool" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var _stacktracePool = sync.Pool{ | ||||||
| 	_stacktracePool = sync.Pool{ | 	New: func() interface{} { | ||||||
| 		New: func() interface{} { | 		return &stacktrace{ | ||||||
| 			return newProgramCounters(64) | 			storage: make([]uintptr, 64), | ||||||
| 		}, | 		} | ||||||
| 	} | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type stacktrace struct { | ||||||
|  | 	pcs    []uintptr // program counters; always a subslice of storage
 | ||||||
|  | 	frames *runtime.Frames | ||||||
|  | 
 | ||||||
|  | 	// The size of pcs varies depending on requirements:
 | ||||||
|  | 	// it will be one if the only the first frame was requested,
 | ||||||
|  | 	// and otherwise it will reflect the depth of the call stack.
 | ||||||
|  | 	//
 | ||||||
|  | 	// storage decouples the slice we need (pcs) from the slice we pool.
 | ||||||
|  | 	// We will always allocate a reasonably large storage, but we'll use
 | ||||||
|  | 	// only as much of it as we need.
 | ||||||
|  | 	storage []uintptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // stacktraceDepth specifies how deep of a stack trace should be captured.
 | ||||||
|  | type stacktraceDepth int | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// stacktraceFirst captures only the first frame.
 | ||||||
|  | 	stacktraceFirst stacktraceDepth = iota | ||||||
|  | 
 | ||||||
|  | 	// stacktraceFull captures the entire call stack, allocating more
 | ||||||
|  | 	// storage for it if needed.
 | ||||||
|  | 	stacktraceFull | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // captureStacktrace captures a stack trace of the specified depth, skipping
 | ||||||
|  | // the provided number of frames. skip=0 identifies the caller of
 | ||||||
|  | // captureStacktrace.
 | ||||||
|  | //
 | ||||||
|  | // The caller must call Free on the returned stacktrace after using it.
 | ||||||
|  | func captureStacktrace(skip int, depth stacktraceDepth) *stacktrace { | ||||||
|  | 	stack := _stacktracePool.Get().(*stacktrace) | ||||||
|  | 
 | ||||||
|  | 	switch depth { | ||||||
|  | 	case stacktraceFirst: | ||||||
|  | 		stack.pcs = stack.storage[:1] | ||||||
|  | 	case stacktraceFull: | ||||||
|  | 		stack.pcs = stack.storage | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers
 | ||||||
|  | 	// itself. +2 to skip captureStacktrace and runtime.Callers.
 | ||||||
|  | 	numFrames := runtime.Callers( | ||||||
|  | 		skip+2, | ||||||
|  | 		stack.pcs, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// runtime.Callers truncates the recorded stacktrace if there is no
 | ||||||
|  | 	// room in the provided slice. For the full stack trace, keep expanding
 | ||||||
|  | 	// storage until there are fewer frames than there is room.
 | ||||||
|  | 	if depth == stacktraceFull { | ||||||
|  | 		pcs := stack.pcs | ||||||
|  | 		for numFrames == len(pcs) { | ||||||
|  | 			pcs = make([]uintptr, len(pcs)*2) | ||||||
|  | 			numFrames = runtime.Callers(skip+2, pcs) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Discard old storage instead of returning it to the pool.
 | ||||||
|  | 		// This will adjust the pool size over time if stack traces are
 | ||||||
|  | 		// consistently very deep.
 | ||||||
|  | 		stack.storage = pcs | ||||||
|  | 		stack.pcs = pcs[:numFrames] | ||||||
|  | 	} else { | ||||||
|  | 		stack.pcs = stack.pcs[:numFrames] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	stack.frames = runtime.CallersFrames(stack.pcs) | ||||||
|  | 	return stack | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Free releases resources associated with this stacktrace
 | ||||||
|  | // and returns it back to the pool.
 | ||||||
|  | func (st *stacktrace) Free() { | ||||||
|  | 	st.frames = nil | ||||||
|  | 	st.pcs = nil | ||||||
|  | 	_stacktracePool.Put(st) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Count reports the total number of frames in this stacktrace.
 | ||||||
|  | // Count DOES NOT change as Next is called.
 | ||||||
|  | func (st *stacktrace) Count() int { | ||||||
|  | 	return len(st.pcs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Next returns the next frame in the stack trace,
 | ||||||
|  | // and a boolean indicating whether there are more after it.
 | ||||||
|  | func (st *stacktrace) Next() (_ runtime.Frame, more bool) { | ||||||
|  | 	return st.frames.Next() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func takeStacktrace(skip int) string { | func takeStacktrace(skip int) string { | ||||||
|  | 	stack := captureStacktrace(skip+1, stacktraceFull) | ||||||
|  | 	defer stack.Free() | ||||||
|  | 
 | ||||||
| 	buffer := bufferpool.Get() | 	buffer := bufferpool.Get() | ||||||
| 	defer buffer.Free() | 	defer buffer.Free() | ||||||
| 	programCounters := _stacktracePool.Get().(*programCounters) |  | ||||||
| 	defer _stacktracePool.Put(programCounters) |  | ||||||
| 
 |  | ||||||
| 	var numFrames int |  | ||||||
| 	for { |  | ||||||
| 		// Skip the call to runtime.Callers and takeStacktrace so that the
 |  | ||||||
| 		// program counters start at the caller of takeStacktrace.
 |  | ||||||
| 		numFrames = runtime.Callers(skip+2, programCounters.pcs) |  | ||||||
| 		if numFrames < len(programCounters.pcs) { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		// Don't put the too-short counter slice back into the pool; this lets
 |  | ||||||
| 		// the pool adjust if we consistently take deep stacktraces.
 |  | ||||||
| 		programCounters = newProgramCounters(len(programCounters.pcs) * 2) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	i := 0 |  | ||||||
| 	frames := runtime.CallersFrames(programCounters.pcs[:numFrames]) |  | ||||||
| 
 |  | ||||||
| 	// Note: On the last iteration, frames.Next() returns false, with a valid
 |  | ||||||
| 	// frame, but we ignore this frame. The last frame is a a runtime frame which
 |  | ||||||
| 	// adds noise, since it's only either runtime.main or runtime.goexit.
 |  | ||||||
| 	for frame, more := frames.Next(); more; frame, more = frames.Next() { |  | ||||||
| 		if i != 0 { |  | ||||||
| 			buffer.AppendByte('\n') |  | ||||||
| 		} |  | ||||||
| 		i++ |  | ||||||
| 		buffer.AppendString(frame.Function) |  | ||||||
| 		buffer.AppendByte('\n') |  | ||||||
| 		buffer.AppendByte('\t') |  | ||||||
| 		buffer.AppendString(frame.File) |  | ||||||
| 		buffer.AppendByte(':') |  | ||||||
| 		buffer.AppendInt(int64(frame.Line)) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
|  | 	stackfmt := newStackFormatter(buffer) | ||||||
|  | 	stackfmt.FormatStack(stack) | ||||||
| 	return buffer.String() | 	return buffer.String() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type programCounters struct { | // stackFormatter formats a stack trace into a readable string representation.
 | ||||||
| 	pcs []uintptr | type stackFormatter struct { | ||||||
|  | 	b        *buffer.Buffer | ||||||
|  | 	nonEmpty bool // whehther we've written at least one frame already
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newProgramCounters(size int) *programCounters { | // newStackFormatter builds a new stackFormatter.
 | ||||||
| 	return &programCounters{make([]uintptr, size)} | func newStackFormatter(b *buffer.Buffer) stackFormatter { | ||||||
|  | 	return stackFormatter{b: b} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FormatStack formats all remaining frames in the provided stacktrace -- minus
 | ||||||
|  | // the final runtime.main/runtime.goexit frame.
 | ||||||
|  | func (sf *stackFormatter) FormatStack(stack *stacktrace) { | ||||||
|  | 	// Note: On the last iteration, frames.Next() returns false, with a valid
 | ||||||
|  | 	// frame, but we ignore this frame. The last frame is a runtime frame which
 | ||||||
|  | 	// adds noise, since it's only either runtime.main or runtime.goexit.
 | ||||||
|  | 	for frame, more := stack.Next(); more; frame, more = stack.Next() { | ||||||
|  | 		sf.FormatFrame(frame) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FormatFrame formats the given frame.
 | ||||||
|  | func (sf *stackFormatter) FormatFrame(frame runtime.Frame) { | ||||||
|  | 	if sf.nonEmpty { | ||||||
|  | 		sf.b.AppendByte('\n') | ||||||
|  | 	} | ||||||
|  | 	sf.nonEmpty = true | ||||||
|  | 	sf.b.AppendString(frame.Function) | ||||||
|  | 	sf.b.AppendByte('\n') | ||||||
|  | 	sf.b.AppendByte('\t') | ||||||
|  | 	sf.b.AppendString(frame.File) | ||||||
|  | 	sf.b.AppendByte(':') | ||||||
|  | 	sf.b.AppendInt(int64(frame.Line)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -31,6 +31,7 @@ import ( | ||||||
| const ( | const ( | ||||||
| 	_oddNumberErrMsg    = "Ignored key without a value." | 	_oddNumberErrMsg    = "Ignored key without a value." | ||||||
| 	_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." | 	_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." | ||||||
|  | 	_multipleErrMsg     = "Multiple errors without a key." | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // A SugaredLogger wraps the base Logger functionality in a slower, but less
 | // A SugaredLogger wraps the base Logger functionality in a slower, but less
 | ||||||
|  | @ -38,10 +39,19 @@ const ( | ||||||
| // method.
 | // method.
 | ||||||
| //
 | //
 | ||||||
| // Unlike the Logger, the SugaredLogger doesn't insist on structured logging.
 | // Unlike the Logger, the SugaredLogger doesn't insist on structured logging.
 | ||||||
| // For each log level, it exposes three methods: one for loosely-typed
 | // For each log level, it exposes four methods:
 | ||||||
| // structured logging, one for println-style formatting, and one for
 | //
 | ||||||
| // printf-style formatting. For example, SugaredLoggers can produce InfoLevel
 | //   - methods named after the log level for log.Print-style logging
 | ||||||
| // output with Infow ("info with" structured context), Info, or Infof.
 | //   - methods ending in "w" for loosely-typed structured logging
 | ||||||
|  | //   - methods ending in "f" for log.Printf-style logging
 | ||||||
|  | //   - methods ending in "ln" for log.Println-style logging
 | ||||||
|  | //
 | ||||||
|  | // For example, the methods for InfoLevel are:
 | ||||||
|  | //
 | ||||||
|  | //	Info(...any)           Print-style logging
 | ||||||
|  | //	Infow(...any)          Structured logging (read as "info with")
 | ||||||
|  | //	Infof(string, ...any)  Printf-style logging
 | ||||||
|  | //	Infoln(...any)         Println-style logging
 | ||||||
| type SugaredLogger struct { | type SugaredLogger struct { | ||||||
| 	base *Logger | 	base *Logger | ||||||
| } | } | ||||||
|  | @ -61,27 +71,40 @@ func (s *SugaredLogger) Named(name string) *SugaredLogger { | ||||||
| 	return &SugaredLogger{base: s.base.Named(name)} | 	return &SugaredLogger{base: s.base.Named(name)} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // WithOptions clones the current SugaredLogger, applies the supplied Options,
 | ||||||
|  | // and returns the result. It's safe to use concurrently.
 | ||||||
|  | func (s *SugaredLogger) WithOptions(opts ...Option) *SugaredLogger { | ||||||
|  | 	base := s.base.clone() | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt.apply(base) | ||||||
|  | 	} | ||||||
|  | 	return &SugaredLogger{base: base} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // With adds a variadic number of fields to the logging context. It accepts a
 | // With adds a variadic number of fields to the logging context. It accepts a
 | ||||||
| // mix of strongly-typed Field objects and loosely-typed key-value pairs. When
 | // mix of strongly-typed Field objects and loosely-typed key-value pairs. When
 | ||||||
| // processing pairs, the first element of the pair is used as the field key
 | // processing pairs, the first element of the pair is used as the field key
 | ||||||
| // and the second as the field value.
 | // and the second as the field value.
 | ||||||
| //
 | //
 | ||||||
| // For example,
 | // For example,
 | ||||||
| //   sugaredLogger.With(
 | //
 | ||||||
| //     "hello", "world",
 | //	 sugaredLogger.With(
 | ||||||
| //     "failure", errors.New("oh no"),
 | //	   "hello", "world",
 | ||||||
| //     Stack(),
 | //	   "failure", errors.New("oh no"),
 | ||||||
| //     "count", 42,
 | //	   Stack(),
 | ||||||
| //     "user", User{Name: "alice"},
 | //	   "count", 42,
 | ||||||
| //  )
 | //	   "user", User{Name: "alice"},
 | ||||||
|  | //	)
 | ||||||
|  | //
 | ||||||
| // is the equivalent of
 | // is the equivalent of
 | ||||||
| //   unsugared.With(
 | //
 | ||||||
| //     String("hello", "world"),
 | //	unsugared.With(
 | ||||||
| //     String("failure", "oh no"),
 | //	  String("hello", "world"),
 | ||||||
| //     Stack(),
 | //	  String("failure", "oh no"),
 | ||||||
| //     Int("count", 42),
 | //	  Stack(),
 | ||||||
| //     Object("user", User{Name: "alice"}),
 | //	  Int("count", 42),
 | ||||||
| //   )
 | //	  Object("user", User{Name: "alice"}),
 | ||||||
|  | //	)
 | ||||||
| //
 | //
 | ||||||
| // Note that the keys in key-value pairs should be strings. In development,
 | // Note that the keys in key-value pairs should be strings. In development,
 | ||||||
| // passing a non-string key panics. In production, the logger is more
 | // passing a non-string key panics. In production, the logger is more
 | ||||||
|  | @ -92,6 +115,13 @@ func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger { | ||||||
| 	return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)} | 	return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Level reports the minimum enabled level for this logger.
 | ||||||
|  | //
 | ||||||
|  | // For NopLoggers, this is [zapcore.InvalidLevel].
 | ||||||
|  | func (s *SugaredLogger) Level() zapcore.Level { | ||||||
|  | 	return zapcore.LevelOf(s.base.core) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Debug uses fmt.Sprint to construct and log a message.
 | // Debug uses fmt.Sprint to construct and log a message.
 | ||||||
| func (s *SugaredLogger) Debug(args ...interface{}) { | func (s *SugaredLogger) Debug(args ...interface{}) { | ||||||
| 	s.log(DebugLevel, "", args, nil) | 	s.log(DebugLevel, "", args, nil) | ||||||
|  | @ -168,7 +198,8 @@ func (s *SugaredLogger) Fatalf(template string, args ...interface{}) { | ||||||
| // pairs are treated as they are in With.
 | // pairs are treated as they are in With.
 | ||||||
| //
 | //
 | ||||||
| // When debug-level logging is disabled, this is much faster than
 | // When debug-level logging is disabled, this is much faster than
 | ||||||
| //  s.With(keysAndValues).Debug(msg)
 | //
 | ||||||
|  | //	s.With(keysAndValues).Debug(msg)
 | ||||||
| func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { | func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { | ||||||
| 	s.log(DebugLevel, msg, nil, keysAndValues) | 	s.log(DebugLevel, msg, nil, keysAndValues) | ||||||
| } | } | ||||||
|  | @ -210,11 +241,48 @@ func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) { | ||||||
| 	s.log(FatalLevel, msg, nil, keysAndValues) | 	s.log(FatalLevel, msg, nil, keysAndValues) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Debugln uses fmt.Sprintln to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Debugln(args ...interface{}) { | ||||||
|  | 	s.logln(DebugLevel, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Infoln uses fmt.Sprintln to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Infoln(args ...interface{}) { | ||||||
|  | 	s.logln(InfoLevel, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Warnln uses fmt.Sprintln to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Warnln(args ...interface{}) { | ||||||
|  | 	s.logln(WarnLevel, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Errorln uses fmt.Sprintln to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Errorln(args ...interface{}) { | ||||||
|  | 	s.logln(ErrorLevel, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DPanicln uses fmt.Sprintln to construct and log a message. In development, the
 | ||||||
|  | // logger then panics. (See DPanicLevel for details.)
 | ||||||
|  | func (s *SugaredLogger) DPanicln(args ...interface{}) { | ||||||
|  | 	s.logln(DPanicLevel, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Panicln uses fmt.Sprintln to construct and log a message, then panics.
 | ||||||
|  | func (s *SugaredLogger) Panicln(args ...interface{}) { | ||||||
|  | 	s.logln(PanicLevel, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fatalln uses fmt.Sprintln to construct and log a message, then calls os.Exit.
 | ||||||
|  | func (s *SugaredLogger) Fatalln(args ...interface{}) { | ||||||
|  | 	s.logln(FatalLevel, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Sync flushes any buffered log entries.
 | // Sync flushes any buffered log entries.
 | ||||||
| func (s *SugaredLogger) Sync() error { | func (s *SugaredLogger) Sync() error { | ||||||
| 	return s.base.Sync() | 	return s.base.Sync() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // log message with Sprint, Sprintf, or neither.
 | ||||||
| func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) { | func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) { | ||||||
| 	// If logging at this level is completely disabled, skip the overhead of
 | 	// If logging at this level is completely disabled, skip the overhead of
 | ||||||
| 	// string formatting.
 | 	// string formatting.
 | ||||||
|  | @ -228,6 +296,18 @@ func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interf | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // logln message with Sprintln
 | ||||||
|  | func (s *SugaredLogger) logln(lvl zapcore.Level, fmtArgs []interface{}, context []interface{}) { | ||||||
|  | 	if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	msg := getMessageln(fmtArgs) | ||||||
|  | 	if ce := s.base.Check(lvl, msg); ce != nil { | ||||||
|  | 		ce.Write(s.sweetenFields(context)...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // getMessage format with Sprint, Sprintf, or neither.
 | // getMessage format with Sprint, Sprintf, or neither.
 | ||||||
| func getMessage(template string, fmtArgs []interface{}) string { | func getMessage(template string, fmtArgs []interface{}) string { | ||||||
| 	if len(fmtArgs) == 0 { | 	if len(fmtArgs) == 0 { | ||||||
|  | @ -246,15 +326,24 @@ func getMessage(template string, fmtArgs []interface{}) string { | ||||||
| 	return fmt.Sprint(fmtArgs...) | 	return fmt.Sprint(fmtArgs...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // getMessageln format with Sprintln.
 | ||||||
|  | func getMessageln(fmtArgs []interface{}) string { | ||||||
|  | 	msg := fmt.Sprintln(fmtArgs...) | ||||||
|  | 	return msg[:len(msg)-1] | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { | func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { | ||||||
| 	if len(args) == 0 { | 	if len(args) == 0 { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Allocate enough space for the worst case; if users pass only structured
 | 	var ( | ||||||
| 	// fields, we shouldn't penalize them with extra allocations.
 | 		// Allocate enough space for the worst case; if users pass only structured
 | ||||||
| 	fields := make([]Field, 0, len(args)) | 		// fields, we shouldn't penalize them with extra allocations.
 | ||||||
| 	var invalid invalidPairs | 		fields    = make([]Field, 0, len(args)) | ||||||
|  | 		invalid   invalidPairs | ||||||
|  | 		seenError bool | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	for i := 0; i < len(args); { | 	for i := 0; i < len(args); { | ||||||
| 		// This is a strongly-typed field. Consume it and move on.
 | 		// This is a strongly-typed field. Consume it and move on.
 | ||||||
|  | @ -264,6 +353,18 @@ func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		// If it is an error, consume it and move on.
 | ||||||
|  | 		if err, ok := args[i].(error); ok { | ||||||
|  | 			if !seenError { | ||||||
|  | 				seenError = true | ||||||
|  | 				fields = append(fields, Error(err)) | ||||||
|  | 			} else { | ||||||
|  | 				s.base.Error(_multipleErrMsg, Error(err)) | ||||||
|  | 			} | ||||||
|  | 			i++ | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		// Make sure this element isn't a dangling key.
 | 		// Make sure this element isn't a dangling key.
 | ||||||
| 		if i == len(args)-1 { | 		if i == len(args)-1 { | ||||||
| 			s.base.Error(_oddNumberErrMsg, Any("ignored", args[i])) | 			s.base.Error(_oddNumberErrMsg, Any("ignored", args[i])) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| // Copyright (c) 2016 Uber Technologies, Inc.
 | // Copyright (c) 2016-2022 Uber Technologies, Inc.
 | ||||||
| //
 | //
 | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
| // of this software and associated documentation files (the "Software"), to deal
 | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | @ -23,7 +23,6 @@ package zap | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" |  | ||||||
| 
 | 
 | ||||||
| 	"go.uber.org/zap/zapcore" | 	"go.uber.org/zap/zapcore" | ||||||
| 
 | 
 | ||||||
|  | @ -69,9 +68,9 @@ func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { | ||||||
| 
 | 
 | ||||||
| 	var openErr error | 	var openErr error | ||||||
| 	for _, path := range paths { | 	for _, path := range paths { | ||||||
| 		sink, err := newSink(path) | 		sink, err := _sinkRegistry.newSink(path) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			openErr = multierr.Append(openErr, fmt.Errorf("couldn't open sink %q: %v", path, err)) | 			openErr = multierr.Append(openErr, fmt.Errorf("open sink %q: %w", path, err)) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		writers = append(writers, sink) | 		writers = append(writers, sink) | ||||||
|  | @ -79,7 +78,7 @@ func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { | ||||||
| 	} | 	} | ||||||
| 	if openErr != nil { | 	if openErr != nil { | ||||||
| 		close() | 		close() | ||||||
| 		return writers, nil, openErr | 		return nil, nil, openErr | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return writers, close, nil | 	return writers, close, nil | ||||||
|  | @ -93,7 +92,7 @@ func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { | ||||||
| // using zapcore.NewMultiWriteSyncer and zapcore.Lock individually.
 | // using zapcore.NewMultiWriteSyncer and zapcore.Lock individually.
 | ||||||
| func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer { | func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer { | ||||||
| 	if len(writers) == 0 { | 	if len(writers) == 0 { | ||||||
| 		return zapcore.AddSync(ioutil.Discard) | 		return zapcore.AddSync(io.Discard) | ||||||
| 	} | 	} | ||||||
| 	return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)) | 	return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -43,6 +43,37 @@ const ( | ||||||
| //
 | //
 | ||||||
| // BufferedWriteSyncer is safe for concurrent use. You don't need to use
 | // BufferedWriteSyncer is safe for concurrent use. You don't need to use
 | ||||||
| // zapcore.Lock for WriteSyncers with BufferedWriteSyncer.
 | // zapcore.Lock for WriteSyncers with BufferedWriteSyncer.
 | ||||||
|  | //
 | ||||||
|  | // To set up a BufferedWriteSyncer, construct a WriteSyncer for your log
 | ||||||
|  | // destination (*os.File is a valid WriteSyncer), wrap it with
 | ||||||
|  | // BufferedWriteSyncer, and defer a Stop() call for when you no longer need the
 | ||||||
|  | // object.
 | ||||||
|  | //
 | ||||||
|  | //	 func main() {
 | ||||||
|  | //	   ws := ... // your log destination
 | ||||||
|  | //	   bws := &zapcore.BufferedWriteSyncer{WS: ws}
 | ||||||
|  | //	   defer bws.Stop()
 | ||||||
|  | //
 | ||||||
|  | //	   // ...
 | ||||||
|  | //	   core := zapcore.NewCore(enc, bws, lvl)
 | ||||||
|  | //	   logger := zap.New(core)
 | ||||||
|  | //
 | ||||||
|  | //	   // ...
 | ||||||
|  | //	}
 | ||||||
|  | //
 | ||||||
|  | // By default, a BufferedWriteSyncer will buffer up to 256 kilobytes of logs,
 | ||||||
|  | // waiting at most 30 seconds between flushes.
 | ||||||
|  | // You can customize these parameters by setting the Size or FlushInterval
 | ||||||
|  | // fields.
 | ||||||
|  | // For example, the following buffers up to 512 kB of logs before flushing them
 | ||||||
|  | // to Stderr, with a maximum of one minute between each flush.
 | ||||||
|  | //
 | ||||||
|  | //	ws := &BufferedWriteSyncer{
 | ||||||
|  | //	  WS:            os.Stderr,
 | ||||||
|  | //	  Size:          512 * 1024, // 512 kB
 | ||||||
|  | //	  FlushInterval: time.Minute,
 | ||||||
|  | //	}
 | ||||||
|  | //	defer ws.Stop()
 | ||||||
| type BufferedWriteSyncer struct { | type BufferedWriteSyncer struct { | ||||||
| 	// WS is the WriteSyncer around which BufferedWriteSyncer will buffer
 | 	// WS is the WriteSyncer around which BufferedWriteSyncer will buffer
 | ||||||
| 	// writes.
 | 	// writes.
 | ||||||
|  |  | ||||||
|  | @ -20,9 +20,7 @@ | ||||||
| 
 | 
 | ||||||
| package zapcore | package zapcore | ||||||
| 
 | 
 | ||||||
| import ( | import "time" | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| // DefaultClock is the default clock used by Zap in operations that require
 | // DefaultClock is the default clock used by Zap in operations that require
 | ||||||
| // time. This clock uses the system clock for all operations.
 | // time. This clock uses the system clock for all operations.
 | ||||||
|  |  | ||||||
|  | @ -125,11 +125,7 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, | ||||||
| 		line.AppendString(ent.Stack) | 		line.AppendString(ent.Stack) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if c.LineEnding != "" { | 	line.AppendString(c.LineEnding) | ||||||
| 		line.AppendString(c.LineEnding) |  | ||||||
| 	} else { |  | ||||||
| 		line.AppendString(DefaultLineEnding) |  | ||||||
| 	} |  | ||||||
| 	return line, nil | 	return line, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -69,6 +69,15 @@ type ioCore struct { | ||||||
| 	out WriteSyncer | 	out WriteSyncer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	_ Core           = (*ioCore)(nil) | ||||||
|  | 	_ leveledEnabler = (*ioCore)(nil) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (c *ioCore) Level() Level { | ||||||
|  | 	return LevelOf(c.LevelEnabler) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *ioCore) With(fields []Field) Core { | func (c *ioCore) With(fields []Field) Core { | ||||||
| 	clone := c.clone() | 	clone := c.clone() | ||||||
| 	addFields(clone.enc, fields) | 	addFields(clone.enc, fields) | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ package zapcore | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"go.uber.org/zap/buffer" | 	"go.uber.org/zap/buffer" | ||||||
|  | @ -187,10 +188,13 @@ func (e *TimeEncoder) UnmarshalText(text []byte) error { | ||||||
| 
 | 
 | ||||||
| // UnmarshalYAML unmarshals YAML to a TimeEncoder.
 | // UnmarshalYAML unmarshals YAML to a TimeEncoder.
 | ||||||
| // If value is an object with a "layout" field, it will be unmarshaled to  TimeEncoder with given layout.
 | // If value is an object with a "layout" field, it will be unmarshaled to  TimeEncoder with given layout.
 | ||||||
| //     timeEncoder:
 | //
 | ||||||
| //       layout: 06/01/02 03:04pm
 | //	timeEncoder:
 | ||||||
|  | //	  layout: 06/01/02 03:04pm
 | ||||||
|  | //
 | ||||||
| // If value is string, it uses UnmarshalText.
 | // If value is string, it uses UnmarshalText.
 | ||||||
| //     timeEncoder: iso8601
 | //
 | ||||||
|  | //	timeEncoder: iso8601
 | ||||||
| func (e *TimeEncoder) UnmarshalYAML(unmarshal func(interface{}) error) error { | func (e *TimeEncoder) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||||||
| 	var o struct { | 	var o struct { | ||||||
| 		Layout string `json:"layout" yaml:"layout"` | 		Layout string `json:"layout" yaml:"layout"` | ||||||
|  | @ -312,14 +316,15 @@ func (e *NameEncoder) UnmarshalText(text []byte) error { | ||||||
| type EncoderConfig struct { | type EncoderConfig struct { | ||||||
| 	// Set the keys used for each log entry. If any key is empty, that portion
 | 	// Set the keys used for each log entry. If any key is empty, that portion
 | ||||||
| 	// of the entry is omitted.
 | 	// of the entry is omitted.
 | ||||||
| 	MessageKey    string `json:"messageKey" yaml:"messageKey"` | 	MessageKey     string `json:"messageKey" yaml:"messageKey"` | ||||||
| 	LevelKey      string `json:"levelKey" yaml:"levelKey"` | 	LevelKey       string `json:"levelKey" yaml:"levelKey"` | ||||||
| 	TimeKey       string `json:"timeKey" yaml:"timeKey"` | 	TimeKey        string `json:"timeKey" yaml:"timeKey"` | ||||||
| 	NameKey       string `json:"nameKey" yaml:"nameKey"` | 	NameKey        string `json:"nameKey" yaml:"nameKey"` | ||||||
| 	CallerKey     string `json:"callerKey" yaml:"callerKey"` | 	CallerKey      string `json:"callerKey" yaml:"callerKey"` | ||||||
| 	FunctionKey   string `json:"functionKey" yaml:"functionKey"` | 	FunctionKey    string `json:"functionKey" yaml:"functionKey"` | ||||||
| 	StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` | 	StacktraceKey  string `json:"stacktraceKey" yaml:"stacktraceKey"` | ||||||
| 	LineEnding    string `json:"lineEnding" yaml:"lineEnding"` | 	SkipLineEnding bool   `json:"skipLineEnding" yaml:"skipLineEnding"` | ||||||
|  | 	LineEnding     string `json:"lineEnding" yaml:"lineEnding"` | ||||||
| 	// Configure the primitive representations of common complex types. For
 | 	// Configure the primitive representations of common complex types. For
 | ||||||
| 	// example, some users may want all time.Times serialized as floating-point
 | 	// example, some users may want all time.Times serialized as floating-point
 | ||||||
| 	// seconds since epoch, while others may prefer ISO8601 strings.
 | 	// seconds since epoch, while others may prefer ISO8601 strings.
 | ||||||
|  | @ -330,6 +335,9 @@ type EncoderConfig struct { | ||||||
| 	// Unlike the other primitive type encoders, EncodeName is optional. The
 | 	// Unlike the other primitive type encoders, EncodeName is optional. The
 | ||||||
| 	// zero value falls back to FullNameEncoder.
 | 	// zero value falls back to FullNameEncoder.
 | ||||||
| 	EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` | 	EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` | ||||||
|  | 	// Configure the encoder for interface{} type objects.
 | ||||||
|  | 	// If not provided, objects are encoded using json.Encoder
 | ||||||
|  | 	NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"` | ||||||
| 	// Configures the field separator used by the console encoder. Defaults
 | 	// Configures the field separator used by the console encoder. Defaults
 | ||||||
| 	// to tab.
 | 	// to tab.
 | ||||||
| 	ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` | 	ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` | ||||||
|  |  | ||||||
|  | @ -27,10 +27,9 @@ import ( | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"go.uber.org/multierr" | ||||||
| 	"go.uber.org/zap/internal/bufferpool" | 	"go.uber.org/zap/internal/bufferpool" | ||||||
| 	"go.uber.org/zap/internal/exit" | 	"go.uber.org/zap/internal/exit" | ||||||
| 
 |  | ||||||
| 	"go.uber.org/multierr" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -152,6 +151,27 @@ type Entry struct { | ||||||
| 	Stack      string | 	Stack      string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // CheckWriteHook is a custom action that may be executed after an entry is
 | ||||||
|  | // written.
 | ||||||
|  | //
 | ||||||
|  | // Register one on a CheckedEntry with the After method.
 | ||||||
|  | //
 | ||||||
|  | //	if ce := logger.Check(...); ce != nil {
 | ||||||
|  | //	  ce = ce.After(hook)
 | ||||||
|  | //	  ce.Write(...)
 | ||||||
|  | //	}
 | ||||||
|  | //
 | ||||||
|  | // You can configure the hook for Fatal log statements at the logger level with
 | ||||||
|  | // the zap.WithFatalHook option.
 | ||||||
|  | type CheckWriteHook interface { | ||||||
|  | 	// OnWrite is invoked with the CheckedEntry that was written and a list
 | ||||||
|  | 	// of fields added with that entry.
 | ||||||
|  | 	//
 | ||||||
|  | 	// The list of fields DOES NOT include fields that were already added
 | ||||||
|  | 	// to the logger with the With method.
 | ||||||
|  | 	OnWrite(*CheckedEntry, []Field) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // CheckWriteAction indicates what action to take after a log entry is
 | // CheckWriteAction indicates what action to take after a log entry is
 | ||||||
| // processed. Actions are ordered in increasing severity.
 | // processed. Actions are ordered in increasing severity.
 | ||||||
| type CheckWriteAction uint8 | type CheckWriteAction uint8 | ||||||
|  | @ -164,21 +184,36 @@ const ( | ||||||
| 	WriteThenGoexit | 	WriteThenGoexit | ||||||
| 	// WriteThenPanic causes a panic after Write.
 | 	// WriteThenPanic causes a panic after Write.
 | ||||||
| 	WriteThenPanic | 	WriteThenPanic | ||||||
| 	// WriteThenFatal causes a fatal os.Exit after Write.
 | 	// WriteThenFatal causes an os.Exit(1) after Write.
 | ||||||
| 	WriteThenFatal | 	WriteThenFatal | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // OnWrite implements the OnWrite method to keep CheckWriteAction compatible
 | ||||||
|  | // with the new CheckWriteHook interface which deprecates CheckWriteAction.
 | ||||||
|  | func (a CheckWriteAction) OnWrite(ce *CheckedEntry, _ []Field) { | ||||||
|  | 	switch a { | ||||||
|  | 	case WriteThenGoexit: | ||||||
|  | 		runtime.Goexit() | ||||||
|  | 	case WriteThenPanic: | ||||||
|  | 		panic(ce.Message) | ||||||
|  | 	case WriteThenFatal: | ||||||
|  | 		exit.With(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ CheckWriteHook = CheckWriteAction(0) | ||||||
|  | 
 | ||||||
| // CheckedEntry is an Entry together with a collection of Cores that have
 | // CheckedEntry is an Entry together with a collection of Cores that have
 | ||||||
| // already agreed to log it.
 | // already agreed to log it.
 | ||||||
| //
 | //
 | ||||||
| // CheckedEntry references should be created by calling AddCore or Should on a
 | // CheckedEntry references should be created by calling AddCore or After on a
 | ||||||
| // nil *CheckedEntry. References are returned to a pool after Write, and MUST
 | // nil *CheckedEntry. References are returned to a pool after Write, and MUST
 | ||||||
| // NOT be retained after calling their Write method.
 | // NOT be retained after calling their Write method.
 | ||||||
| type CheckedEntry struct { | type CheckedEntry struct { | ||||||
| 	Entry | 	Entry | ||||||
| 	ErrorOutput WriteSyncer | 	ErrorOutput WriteSyncer | ||||||
| 	dirty       bool // best-effort detection of pool misuse
 | 	dirty       bool // best-effort detection of pool misuse
 | ||||||
| 	should      CheckWriteAction | 	after       CheckWriteHook | ||||||
| 	cores       []Core | 	cores       []Core | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -186,7 +221,7 @@ func (ce *CheckedEntry) reset() { | ||||||
| 	ce.Entry = Entry{} | 	ce.Entry = Entry{} | ||||||
| 	ce.ErrorOutput = nil | 	ce.ErrorOutput = nil | ||||||
| 	ce.dirty = false | 	ce.dirty = false | ||||||
| 	ce.should = WriteThenNoop | 	ce.after = nil | ||||||
| 	for i := range ce.cores { | 	for i := range ce.cores { | ||||||
| 		// don't keep references to cores
 | 		// don't keep references to cores
 | ||||||
| 		ce.cores[i] = nil | 		ce.cores[i] = nil | ||||||
|  | @ -224,17 +259,11 @@ func (ce *CheckedEntry) Write(fields ...Field) { | ||||||
| 		ce.ErrorOutput.Sync() | 		ce.ErrorOutput.Sync() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	should, msg := ce.should, ce.Message | 	hook := ce.after | ||||||
| 	putCheckedEntry(ce) | 	if hook != nil { | ||||||
| 
 | 		hook.OnWrite(ce, fields) | ||||||
| 	switch should { |  | ||||||
| 	case WriteThenPanic: |  | ||||||
| 		panic(msg) |  | ||||||
| 	case WriteThenFatal: |  | ||||||
| 		exit.Exit() |  | ||||||
| 	case WriteThenGoexit: |  | ||||||
| 		runtime.Goexit() |  | ||||||
| 	} | 	} | ||||||
|  | 	putCheckedEntry(ce) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be
 | // AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be
 | ||||||
|  | @ -252,11 +281,20 @@ func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { | ||||||
| // Should sets this CheckedEntry's CheckWriteAction, which controls whether a
 | // Should sets this CheckedEntry's CheckWriteAction, which controls whether a
 | ||||||
| // Core will panic or fatal after writing this log entry. Like AddCore, it's
 | // Core will panic or fatal after writing this log entry. Like AddCore, it's
 | ||||||
| // safe to call on nil CheckedEntry references.
 | // safe to call on nil CheckedEntry references.
 | ||||||
|  | //
 | ||||||
|  | // Deprecated: Use [CheckedEntry.After] instead.
 | ||||||
| func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry { | func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry { | ||||||
|  | 	return ce.After(ent, should) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // After sets this CheckEntry's CheckWriteHook, which will be called after this
 | ||||||
|  | // log entry has been written. It's safe to call this on nil CheckedEntry
 | ||||||
|  | // references.
 | ||||||
|  | func (ce *CheckedEntry) After(ent Entry, hook CheckWriteHook) *CheckedEntry { | ||||||
| 	if ce == nil { | 	if ce == nil { | ||||||
| 		ce = getCheckedEntry() | 		ce = getCheckedEntry() | ||||||
| 		ce.Entry = ent | 		ce.Entry = ent | ||||||
| 	} | 	} | ||||||
| 	ce.should = should | 	ce.after = hook | ||||||
| 	return ce | 	return ce | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -36,13 +36,13 @@ import ( | ||||||
| // causer (from github.com/pkg/errors), a ${key}Causes field is added with an
 | // causer (from github.com/pkg/errors), a ${key}Causes field is added with an
 | ||||||
| // array of objects containing the errors this error was comprised of.
 | // array of objects containing the errors this error was comprised of.
 | ||||||
| //
 | //
 | ||||||
| //  {
 | //	{
 | ||||||
| //    "error": err.Error(),
 | //	  "error": err.Error(),
 | ||||||
| //    "errorVerbose": fmt.Sprintf("%+v", err),
 | //	  "errorVerbose": fmt.Sprintf("%+v", err),
 | ||||||
| //    "errorCauses": [
 | //	  "errorCauses": [
 | ||||||
| //      ...
 | //	    ...
 | ||||||
| //    ],
 | //	  ],
 | ||||||
| //  }
 | //	}
 | ||||||
| func encodeError(key string, err error, enc ObjectEncoder) (retErr error) { | func encodeError(key string, err error, enc ObjectEncoder) (retErr error) { | ||||||
| 	// Try to capture panics (from nil references or otherwise) when calling
 | 	// Try to capture panics (from nil references or otherwise) when calling
 | ||||||
| 	// the Error() method
 | 	// the Error() method
 | ||||||
|  |  | ||||||
|  | @ -27,6 +27,11 @@ type hooked struct { | ||||||
| 	funcs []func(Entry) error | 	funcs []func(Entry) error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	_ Core           = (*hooked)(nil) | ||||||
|  | 	_ leveledEnabler = (*hooked)(nil) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // RegisterHooks wraps a Core and runs a collection of user-defined callback
 | // RegisterHooks wraps a Core and runs a collection of user-defined callback
 | ||||||
| // hooks each time a message is logged. Execution of the callbacks is blocking.
 | // hooks each time a message is logged. Execution of the callbacks is blocking.
 | ||||||
| //
 | //
 | ||||||
|  | @ -40,6 +45,10 @@ func RegisterHooks(core Core, hooks ...func(Entry) error) Core { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (h *hooked) Level() Level { | ||||||
|  | 	return LevelOf(h.Core) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (h *hooked) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { | func (h *hooked) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { | ||||||
| 	// Let the wrapped Core decide whether to log this message or not. This
 | 	// Let the wrapped Core decide whether to log this message or not. This
 | ||||||
| 	// also gives the downstream a chance to register itself directly with the
 | 	// also gives the downstream a chance to register itself directly with the
 | ||||||
|  |  | ||||||
|  | @ -27,6 +27,11 @@ type levelFilterCore struct { | ||||||
| 	level LevelEnabler | 	level LevelEnabler | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	_ Core           = (*levelFilterCore)(nil) | ||||||
|  | 	_ leveledEnabler = (*levelFilterCore)(nil) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // NewIncreaseLevelCore creates a core that can be used to increase the level of
 | // NewIncreaseLevelCore creates a core that can be used to increase the level of
 | ||||||
| // an existing Core. It cannot be used to decrease the logging level, as it acts
 | // an existing Core. It cannot be used to decrease the logging level, as it acts
 | ||||||
| // as a filter before calling the underlying core. If level decreases the log level,
 | // as a filter before calling the underlying core. If level decreases the log level,
 | ||||||
|  | @ -45,6 +50,10 @@ func (c *levelFilterCore) Enabled(lvl Level) bool { | ||||||
| 	return c.level.Enabled(lvl) | 	return c.level.Enabled(lvl) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *levelFilterCore) Level() Level { | ||||||
|  | 	return LevelOf(c.level) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *levelFilterCore) With(fields []Field) Core { | func (c *levelFilterCore) With(fields []Field) Core { | ||||||
| 	return &levelFilterCore{c.core.With(fields), c.level} | 	return &levelFilterCore{c.core.With(fields), c.level} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,7 +22,6 @@ package zapcore | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"encoding/json" |  | ||||||
| 	"math" | 	"math" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  | @ -64,7 +63,7 @@ type jsonEncoder struct { | ||||||
| 
 | 
 | ||||||
| 	// for encoding generic values by reflection
 | 	// for encoding generic values by reflection
 | ||||||
| 	reflectBuf *buffer.Buffer | 	reflectBuf *buffer.Buffer | ||||||
| 	reflectEnc *json.Encoder | 	reflectEnc ReflectedEncoder | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder
 | // NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder
 | ||||||
|  | @ -72,7 +71,9 @@ type jsonEncoder struct { | ||||||
| //
 | //
 | ||||||
| // Note that the encoder doesn't deduplicate keys, so it's possible to produce
 | // Note that the encoder doesn't deduplicate keys, so it's possible to produce
 | ||||||
| // a message like
 | // a message like
 | ||||||
| //   {"foo":"bar","foo":"baz"}
 | //
 | ||||||
|  | //	{"foo":"bar","foo":"baz"}
 | ||||||
|  | //
 | ||||||
| // This is permitted by the JSON specification, but not encouraged. Many
 | // This is permitted by the JSON specification, but not encouraged. Many
 | ||||||
| // libraries will ignore duplicate key-value pairs (typically keeping the last
 | // libraries will ignore duplicate key-value pairs (typically keeping the last
 | ||||||
| // pair) when unmarshaling, but users should attempt to avoid adding duplicate
 | // pair) when unmarshaling, but users should attempt to avoid adding duplicate
 | ||||||
|  | @ -82,6 +83,17 @@ func NewJSONEncoder(cfg EncoderConfig) Encoder { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { | func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { | ||||||
|  | 	if cfg.SkipLineEnding { | ||||||
|  | 		cfg.LineEnding = "" | ||||||
|  | 	} else if cfg.LineEnding == "" { | ||||||
|  | 		cfg.LineEnding = DefaultLineEnding | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If no EncoderConfig.NewReflectedEncoder is provided by the user, then use default
 | ||||||
|  | 	if cfg.NewReflectedEncoder == nil { | ||||||
|  | 		cfg.NewReflectedEncoder = defaultReflectedEncoder | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return &jsonEncoder{ | 	return &jsonEncoder{ | ||||||
| 		EncoderConfig: &cfg, | 		EncoderConfig: &cfg, | ||||||
| 		buf:           bufferpool.Get(), | 		buf:           bufferpool.Get(), | ||||||
|  | @ -118,6 +130,11 @@ func (enc *jsonEncoder) AddComplex128(key string, val complex128) { | ||||||
| 	enc.AppendComplex128(val) | 	enc.AppendComplex128(val) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (enc *jsonEncoder) AddComplex64(key string, val complex64) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendComplex64(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (enc *jsonEncoder) AddDuration(key string, val time.Duration) { | func (enc *jsonEncoder) AddDuration(key string, val time.Duration) { | ||||||
| 	enc.addKey(key) | 	enc.addKey(key) | ||||||
| 	enc.AppendDuration(val) | 	enc.AppendDuration(val) | ||||||
|  | @ -141,10 +158,7 @@ func (enc *jsonEncoder) AddInt64(key string, val int64) { | ||||||
| func (enc *jsonEncoder) resetReflectBuf() { | func (enc *jsonEncoder) resetReflectBuf() { | ||||||
| 	if enc.reflectBuf == nil { | 	if enc.reflectBuf == nil { | ||||||
| 		enc.reflectBuf = bufferpool.Get() | 		enc.reflectBuf = bufferpool.Get() | ||||||
| 		enc.reflectEnc = json.NewEncoder(enc.reflectBuf) | 		enc.reflectEnc = enc.NewReflectedEncoder(enc.reflectBuf) | ||||||
| 
 |  | ||||||
| 		// For consistency with our custom JSON encoder.
 |  | ||||||
| 		enc.reflectEnc.SetEscapeHTML(false) |  | ||||||
| 	} else { | 	} else { | ||||||
| 		enc.reflectBuf.Reset() | 		enc.reflectBuf.Reset() | ||||||
| 	} | 	} | ||||||
|  | @ -206,10 +220,16 @@ func (enc *jsonEncoder) AppendArray(arr ArrayMarshaler) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error { | func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error { | ||||||
|  | 	// Close ONLY new openNamespaces that are created during
 | ||||||
|  | 	// AppendObject().
 | ||||||
|  | 	old := enc.openNamespaces | ||||||
|  | 	enc.openNamespaces = 0 | ||||||
| 	enc.addElementSeparator() | 	enc.addElementSeparator() | ||||||
| 	enc.buf.AppendByte('{') | 	enc.buf.AppendByte('{') | ||||||
| 	err := obj.MarshalLogObject(enc) | 	err := obj.MarshalLogObject(enc) | ||||||
| 	enc.buf.AppendByte('}') | 	enc.buf.AppendByte('}') | ||||||
|  | 	enc.closeOpenNamespaces() | ||||||
|  | 	enc.openNamespaces = old | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -225,20 +245,23 @@ func (enc *jsonEncoder) AppendByteString(val []byte) { | ||||||
| 	enc.buf.AppendByte('"') | 	enc.buf.AppendByte('"') | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *jsonEncoder) AppendComplex128(val complex128) { | // appendComplex appends the encoded form of the provided complex128 value.
 | ||||||
|  | // precision specifies the encoding precision for the real and imaginary
 | ||||||
|  | // components of the complex number.
 | ||||||
|  | func (enc *jsonEncoder) appendComplex(val complex128, precision int) { | ||||||
| 	enc.addElementSeparator() | 	enc.addElementSeparator() | ||||||
| 	// Cast to a platform-independent, fixed-size type.
 | 	// Cast to a platform-independent, fixed-size type.
 | ||||||
| 	r, i := float64(real(val)), float64(imag(val)) | 	r, i := float64(real(val)), float64(imag(val)) | ||||||
| 	enc.buf.AppendByte('"') | 	enc.buf.AppendByte('"') | ||||||
| 	// Because we're always in a quoted string, we can use strconv without
 | 	// Because we're always in a quoted string, we can use strconv without
 | ||||||
| 	// special-casing NaN and +/-Inf.
 | 	// special-casing NaN and +/-Inf.
 | ||||||
| 	enc.buf.AppendFloat(r, 64) | 	enc.buf.AppendFloat(r, precision) | ||||||
| 	// If imaginary part is less than 0, minus (-) sign is added by default
 | 	// If imaginary part is less than 0, minus (-) sign is added by default
 | ||||||
| 	// by AppendFloat.
 | 	// by AppendFloat.
 | ||||||
| 	if i >= 0 { | 	if i >= 0 { | ||||||
| 		enc.buf.AppendByte('+') | 		enc.buf.AppendByte('+') | ||||||
| 	} | 	} | ||||||
| 	enc.buf.AppendFloat(i, 64) | 	enc.buf.AppendFloat(i, precision) | ||||||
| 	enc.buf.AppendByte('i') | 	enc.buf.AppendByte('i') | ||||||
| 	enc.buf.AppendByte('"') | 	enc.buf.AppendByte('"') | ||||||
| } | } | ||||||
|  | @ -301,28 +324,28 @@ func (enc *jsonEncoder) AppendUint64(val uint64) { | ||||||
| 	enc.buf.AppendUint(val) | 	enc.buf.AppendUint(val) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *jsonEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) } | func (enc *jsonEncoder) AddInt(k string, v int)         { enc.AddInt64(k, int64(v)) } | ||||||
| func (enc *jsonEncoder) AddInt(k string, v int)             { enc.AddInt64(k, int64(v)) } | func (enc *jsonEncoder) AddInt32(k string, v int32)     { enc.AddInt64(k, int64(v)) } | ||||||
| func (enc *jsonEncoder) AddInt32(k string, v int32)         { enc.AddInt64(k, int64(v)) } | func (enc *jsonEncoder) AddInt16(k string, v int16)     { enc.AddInt64(k, int64(v)) } | ||||||
| func (enc *jsonEncoder) AddInt16(k string, v int16)         { enc.AddInt64(k, int64(v)) } | func (enc *jsonEncoder) AddInt8(k string, v int8)       { enc.AddInt64(k, int64(v)) } | ||||||
| func (enc *jsonEncoder) AddInt8(k string, v int8)           { enc.AddInt64(k, int64(v)) } | func (enc *jsonEncoder) AddUint(k string, v uint)       { enc.AddUint64(k, uint64(v)) } | ||||||
| func (enc *jsonEncoder) AddUint(k string, v uint)           { enc.AddUint64(k, uint64(v)) } | func (enc *jsonEncoder) AddUint32(k string, v uint32)   { enc.AddUint64(k, uint64(v)) } | ||||||
| func (enc *jsonEncoder) AddUint32(k string, v uint32)       { enc.AddUint64(k, uint64(v)) } | func (enc *jsonEncoder) AddUint16(k string, v uint16)   { enc.AddUint64(k, uint64(v)) } | ||||||
| func (enc *jsonEncoder) AddUint16(k string, v uint16)       { enc.AddUint64(k, uint64(v)) } | func (enc *jsonEncoder) AddUint8(k string, v uint8)     { enc.AddUint64(k, uint64(v)) } | ||||||
| func (enc *jsonEncoder) AddUint8(k string, v uint8)         { enc.AddUint64(k, uint64(v)) } | func (enc *jsonEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) } | ||||||
| func (enc *jsonEncoder) AddUintptr(k string, v uintptr)     { enc.AddUint64(k, uint64(v)) } | func (enc *jsonEncoder) AppendComplex64(v complex64)    { enc.appendComplex(complex128(v), 32) } | ||||||
| func (enc *jsonEncoder) AppendComplex64(v complex64)        { enc.AppendComplex128(complex128(v)) } | func (enc *jsonEncoder) AppendComplex128(v complex128)  { enc.appendComplex(complex128(v), 64) } | ||||||
| func (enc *jsonEncoder) AppendFloat64(v float64)            { enc.appendFloat(v, 64) } | func (enc *jsonEncoder) AppendFloat64(v float64)        { enc.appendFloat(v, 64) } | ||||||
| func (enc *jsonEncoder) AppendFloat32(v float32)            { enc.appendFloat(float64(v), 32) } | func (enc *jsonEncoder) AppendFloat32(v float32)        { enc.appendFloat(float64(v), 32) } | ||||||
| func (enc *jsonEncoder) AppendInt(v int)                    { enc.AppendInt64(int64(v)) } | func (enc *jsonEncoder) AppendInt(v int)                { enc.AppendInt64(int64(v)) } | ||||||
| func (enc *jsonEncoder) AppendInt32(v int32)                { enc.AppendInt64(int64(v)) } | func (enc *jsonEncoder) AppendInt32(v int32)            { enc.AppendInt64(int64(v)) } | ||||||
| func (enc *jsonEncoder) AppendInt16(v int16)                { enc.AppendInt64(int64(v)) } | func (enc *jsonEncoder) AppendInt16(v int16)            { enc.AppendInt64(int64(v)) } | ||||||
| func (enc *jsonEncoder) AppendInt8(v int8)                  { enc.AppendInt64(int64(v)) } | func (enc *jsonEncoder) AppendInt8(v int8)              { enc.AppendInt64(int64(v)) } | ||||||
| func (enc *jsonEncoder) AppendUint(v uint)                  { enc.AppendUint64(uint64(v)) } | func (enc *jsonEncoder) AppendUint(v uint)              { enc.AppendUint64(uint64(v)) } | ||||||
| func (enc *jsonEncoder) AppendUint32(v uint32)              { enc.AppendUint64(uint64(v)) } | func (enc *jsonEncoder) AppendUint32(v uint32)          { enc.AppendUint64(uint64(v)) } | ||||||
| func (enc *jsonEncoder) AppendUint16(v uint16)              { enc.AppendUint64(uint64(v)) } | func (enc *jsonEncoder) AppendUint16(v uint16)          { enc.AppendUint64(uint64(v)) } | ||||||
| func (enc *jsonEncoder) AppendUint8(v uint8)                { enc.AppendUint64(uint64(v)) } | func (enc *jsonEncoder) AppendUint8(v uint8)            { enc.AppendUint64(uint64(v)) } | ||||||
| func (enc *jsonEncoder) AppendUintptr(v uintptr)            { enc.AppendUint64(uint64(v)) } | func (enc *jsonEncoder) AppendUintptr(v uintptr)        { enc.AppendUint64(uint64(v)) } | ||||||
| 
 | 
 | ||||||
| func (enc *jsonEncoder) Clone() Encoder { | func (enc *jsonEncoder) Clone() Encoder { | ||||||
| 	clone := enc.clone() | 	clone := enc.clone() | ||||||
|  | @ -343,7 +366,7 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, | ||||||
| 	final := enc.clone() | 	final := enc.clone() | ||||||
| 	final.buf.AppendByte('{') | 	final.buf.AppendByte('{') | ||||||
| 
 | 
 | ||||||
| 	if final.LevelKey != "" { | 	if final.LevelKey != "" && final.EncodeLevel != nil { | ||||||
| 		final.addKey(final.LevelKey) | 		final.addKey(final.LevelKey) | ||||||
| 		cur := final.buf.Len() | 		cur := final.buf.Len() | ||||||
| 		final.EncodeLevel(ent.Level, final) | 		final.EncodeLevel(ent.Level, final) | ||||||
|  | @ -404,11 +427,7 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, | ||||||
| 		final.AddString(final.StacktraceKey, ent.Stack) | 		final.AddString(final.StacktraceKey, ent.Stack) | ||||||
| 	} | 	} | ||||||
| 	final.buf.AppendByte('}') | 	final.buf.AppendByte('}') | ||||||
| 	if final.LineEnding != "" { | 	final.buf.AppendString(final.LineEnding) | ||||||
| 		final.buf.AppendString(final.LineEnding) |  | ||||||
| 	} else { |  | ||||||
| 		final.buf.AppendString(DefaultLineEnding) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	ret := final.buf | 	ret := final.buf | ||||||
| 	putJSONEncoder(final) | 	putJSONEncoder(final) | ||||||
|  | @ -423,6 +442,7 @@ func (enc *jsonEncoder) closeOpenNamespaces() { | ||||||
| 	for i := 0; i < enc.openNamespaces; i++ { | 	for i := 0; i < enc.openNamespaces; i++ { | ||||||
| 		enc.buf.AppendByte('}') | 		enc.buf.AppendByte('}') | ||||||
| 	} | 	} | ||||||
|  | 	enc.openNamespaces = 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *jsonEncoder) addKey(key string) { | func (enc *jsonEncoder) addKey(key string) { | ||||||
|  |  | ||||||
|  | @ -53,8 +53,62 @@ const ( | ||||||
| 
 | 
 | ||||||
| 	_minLevel = DebugLevel | 	_minLevel = DebugLevel | ||||||
| 	_maxLevel = FatalLevel | 	_maxLevel = FatalLevel | ||||||
|  | 
 | ||||||
|  | 	// InvalidLevel is an invalid value for Level.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Core implementations may panic if they see messages of this level.
 | ||||||
|  | 	InvalidLevel = _maxLevel + 1 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // ParseLevel parses a level based on the lower-case or all-caps ASCII
 | ||||||
|  | // representation of the log level. If the provided ASCII representation is
 | ||||||
|  | // invalid an error is returned.
 | ||||||
|  | //
 | ||||||
|  | // This is particularly useful when dealing with text input to configure log
 | ||||||
|  | // levels.
 | ||||||
|  | func ParseLevel(text string) (Level, error) { | ||||||
|  | 	var level Level | ||||||
|  | 	err := level.UnmarshalText([]byte(text)) | ||||||
|  | 	return level, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type leveledEnabler interface { | ||||||
|  | 	LevelEnabler | ||||||
|  | 
 | ||||||
|  | 	Level() Level | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // LevelOf reports the minimum enabled log level for the given LevelEnabler
 | ||||||
|  | // from Zap's supported log levels, or [InvalidLevel] if none of them are
 | ||||||
|  | // enabled.
 | ||||||
|  | //
 | ||||||
|  | // A LevelEnabler may implement a 'Level() Level' method to override the
 | ||||||
|  | // behavior of this function.
 | ||||||
|  | //
 | ||||||
|  | //	func (c *core) Level() Level {
 | ||||||
|  | //		return c.currentLevel
 | ||||||
|  | //	}
 | ||||||
|  | //
 | ||||||
|  | // It is recommended that [Core] implementations that wrap other cores use
 | ||||||
|  | // LevelOf to retrieve the level of the wrapped core. For example,
 | ||||||
|  | //
 | ||||||
|  | //	func (c *coreWrapper) Level() Level {
 | ||||||
|  | //		return zapcore.LevelOf(c.wrappedCore)
 | ||||||
|  | //	}
 | ||||||
|  | func LevelOf(enab LevelEnabler) Level { | ||||||
|  | 	if lvler, ok := enab.(leveledEnabler); ok { | ||||||
|  | 		return lvler.Level() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for lvl := _minLevel; lvl <= _maxLevel; lvl++ { | ||||||
|  | 		if enab.Enabled(lvl) { | ||||||
|  | 			return lvl | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return InvalidLevel | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // String returns a lower-case ASCII representation of the log level.
 | // String returns a lower-case ASCII representation of the log level.
 | ||||||
| func (l Level) String() string { | func (l Level) String() string { | ||||||
| 	switch l { | 	switch l { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| // Copyright (c) 2019 Uber Technologies, Inc.
 | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
| //
 | //
 | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
| // of this software and associated documentation files (the "Software"), to deal
 | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | @ -18,9 +18,24 @@ | ||||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
| // THE SOFTWARE.
 | // THE SOFTWARE.
 | ||||||
| 
 | 
 | ||||||
| // See #682 for more information.
 | package zapcore | ||||||
| // +build !go1.12
 |  | ||||||
| 
 | 
 | ||||||
| package zap | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| const _stdLogDefaultDepth = 2 | // ReflectedEncoder serializes log fields that can't be serialized with Zap's
 | ||||||
|  | // JSON encoder. These have the ReflectType field type.
 | ||||||
|  | // Use EncoderConfig.NewReflectedEncoder to set this.
 | ||||||
|  | type ReflectedEncoder interface { | ||||||
|  | 	// Encode encodes and writes to the underlying data stream.
 | ||||||
|  | 	Encode(interface{}) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func defaultReflectedEncoder(w io.Writer) ReflectedEncoder { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	// For consistency with our custom JSON encoder.
 | ||||||
|  | 	enc.SetEscapeHTML(false) | ||||||
|  | 	return enc | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| // Copyright (c) 2016 Uber Technologies, Inc.
 | // Copyright (c) 2016-2022 Uber Technologies, Inc.
 | ||||||
| //
 | //
 | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
| // of this software and associated documentation files (the "Software"), to deal
 | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | @ -113,12 +113,12 @@ func nopSamplingHook(Entry, SamplingDecision) {} | ||||||
| // This hook may be used to get visibility into the performance of the sampler.
 | // This hook may be used to get visibility into the performance of the sampler.
 | ||||||
| // For example, use it to track metrics of dropped versus sampled logs.
 | // For example, use it to track metrics of dropped versus sampled logs.
 | ||||||
| //
 | //
 | ||||||
| //  var dropped atomic.Int64
 | //	var dropped atomic.Int64
 | ||||||
| //  zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) {
 | //	zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) {
 | ||||||
| //    if dec&zapcore.LogDropped > 0 {
 | //	  if dec&zapcore.LogDropped > 0 {
 | ||||||
| //      dropped.Inc()
 | //	    dropped.Inc()
 | ||||||
| //    }
 | //	  }
 | ||||||
| //  })
 | //	})
 | ||||||
| func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption { | func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption { | ||||||
| 	return optionFunc(func(s *sampler) { | 	return optionFunc(func(s *sampler) { | ||||||
| 		s.hook = hook | 		s.hook = hook | ||||||
|  | @ -133,10 +133,21 @@ func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption { | ||||||
| // each tick. If more Entries with the same level and message are seen during
 | // each tick. If more Entries with the same level and message are seen during
 | ||||||
| // the same interval, every Mth message is logged and the rest are dropped.
 | // the same interval, every Mth message is logged and the rest are dropped.
 | ||||||
| //
 | //
 | ||||||
|  | // For example,
 | ||||||
|  | //
 | ||||||
|  | //	core = NewSamplerWithOptions(core, time.Second, 10, 5)
 | ||||||
|  | //
 | ||||||
|  | // This will log the first 10 log entries with the same level and message
 | ||||||
|  | // in a one second interval as-is. Following that, it will allow through
 | ||||||
|  | // every 5th log entry with the same level and message in that interval.
 | ||||||
|  | //
 | ||||||
|  | // If thereafter is zero, the Core will drop all log entries after the first N
 | ||||||
|  | // in that interval.
 | ||||||
|  | //
 | ||||||
| // Sampler can be configured to report sampling decisions with the SamplerHook
 | // Sampler can be configured to report sampling decisions with the SamplerHook
 | ||||||
| // option.
 | // option.
 | ||||||
| //
 | //
 | ||||||
| // Keep in mind that zap's sampling implementation is optimized for speed over
 | // Keep in mind that Zap's sampling implementation is optimized for speed over
 | ||||||
| // absolute precision; under load, each tick may be slightly over- or
 | // absolute precision; under load, each tick may be slightly over- or
 | ||||||
| // under-sampled.
 | // under-sampled.
 | ||||||
| func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core { | func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core { | ||||||
|  | @ -164,6 +175,11 @@ type sampler struct { | ||||||
| 	hook              func(Entry, SamplingDecision) | 	hook              func(Entry, SamplingDecision) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	_ Core           = (*sampler)(nil) | ||||||
|  | 	_ leveledEnabler = (*sampler)(nil) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // NewSampler creates a Core that samples incoming entries, which
 | // NewSampler creates a Core that samples incoming entries, which
 | ||||||
| // caps the CPU and I/O load of logging while attempting to preserve a
 | // caps the CPU and I/O load of logging while attempting to preserve a
 | ||||||
| // representative subset of your logs.
 | // representative subset of your logs.
 | ||||||
|  | @ -181,6 +197,10 @@ func NewSampler(core Core, tick time.Duration, first, thereafter int) Core { | ||||||
| 	return NewSamplerWithOptions(core, tick, first, thereafter) | 	return NewSamplerWithOptions(core, tick, first, thereafter) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (s *sampler) Level() Level { | ||||||
|  | 	return LevelOf(s.Core) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (s *sampler) With(fields []Field) Core { | func (s *sampler) With(fields []Field) Core { | ||||||
| 	return &sampler{ | 	return &sampler{ | ||||||
| 		Core:       s.Core.With(fields), | 		Core:       s.Core.With(fields), | ||||||
|  | @ -200,7 +220,7 @@ func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { | ||||||
| 	if ent.Level >= _minLevel && ent.Level <= _maxLevel { | 	if ent.Level >= _minLevel && ent.Level <= _maxLevel { | ||||||
| 		counter := s.counts.get(ent.Level, ent.Message) | 		counter := s.counts.get(ent.Level, ent.Message) | ||||||
| 		n := counter.IncCheckReset(ent.Time, s.tick) | 		n := counter.IncCheckReset(ent.Time, s.tick) | ||||||
| 		if n > s.first && (n-s.first)%s.thereafter != 0 { | 		if n > s.first && (s.thereafter == 0 || (n-s.first)%s.thereafter != 0) { | ||||||
| 			s.hook(ent, LogDropped) | 			s.hook(ent, LogDropped) | ||||||
| 			return ce | 			return ce | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| // Copyright (c) 2016 Uber Technologies, Inc.
 | // Copyright (c) 2016-2022 Uber Technologies, Inc.
 | ||||||
| //
 | //
 | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
| // of this software and associated documentation files (the "Software"), to deal
 | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | @ -24,6 +24,11 @@ import "go.uber.org/multierr" | ||||||
| 
 | 
 | ||||||
| type multiCore []Core | type multiCore []Core | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	_ leveledEnabler = multiCore(nil) | ||||||
|  | 	_ Core           = multiCore(nil) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // NewTee creates a Core that duplicates log entries into two or more
 | // NewTee creates a Core that duplicates log entries into two or more
 | ||||||
| // underlying Cores.
 | // underlying Cores.
 | ||||||
| //
 | //
 | ||||||
|  | @ -48,6 +53,16 @@ func (mc multiCore) With(fields []Field) Core { | ||||||
| 	return clone | 	return clone | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (mc multiCore) Level() Level { | ||||||
|  | 	minLvl := _maxLevel // mc is never empty
 | ||||||
|  | 	for i := range mc { | ||||||
|  | 		if lvl := LevelOf(mc[i]); lvl < minLvl { | ||||||
|  | 			minLvl = lvl | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return minLvl | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (mc multiCore) Enabled(lvl Level) bool { | func (mc multiCore) Enabled(lvl Level) bool { | ||||||
| 	for i := range mc { | 	for i := range mc { | ||||||
| 		if mc[i].Enabled(lvl) { | 		if mc[i].Enabled(lvl) { | ||||||
|  |  | ||||||
|  | @ -61,7 +61,7 @@ func WrapOptions(zapOpts ...zap.Option) LoggerOption { | ||||||
| // NewLogger builds a new Logger that logs all messages to the given
 | // NewLogger builds a new Logger that logs all messages to the given
 | ||||||
| // testing.TB.
 | // testing.TB.
 | ||||||
| //
 | //
 | ||||||
| //   logger := zaptest.NewLogger(t)
 | //	logger := zaptest.NewLogger(t)
 | ||||||
| //
 | //
 | ||||||
| // Use this with a *testing.T or *testing.B to get logs which get printed only
 | // Use this with a *testing.T or *testing.B to get logs which get printed only
 | ||||||
| // if a test fails or if you ran go test -v.
 | // if a test fails or if you ran go test -v.
 | ||||||
|  | @ -69,11 +69,11 @@ func WrapOptions(zapOpts ...zap.Option) LoggerOption { | ||||||
| // The returned logger defaults to logging debug level messages and above.
 | // The returned logger defaults to logging debug level messages and above.
 | ||||||
| // This may be changed by passing a zaptest.Level during construction.
 | // This may be changed by passing a zaptest.Level during construction.
 | ||||||
| //
 | //
 | ||||||
| //   logger := zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel))
 | //	logger := zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel))
 | ||||||
| //
 | //
 | ||||||
| // You may also pass zap.Option's to customize test logger.
 | // You may also pass zap.Option's to customize test logger.
 | ||||||
| //
 | //
 | ||||||
| //   logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller()))
 | //	logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller()))
 | ||||||
| func NewLogger(t TestingT, opts ...LoggerOption) *zap.Logger { | func NewLogger(t TestingT, opts ...LoggerOption) *zap.Logger { | ||||||
| 	cfg := loggerOptions{ | 	cfg := loggerOptions{ | ||||||
| 		Level: zapcore.DebugLevel, | 		Level: zapcore.DebugLevel, | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ type ( | ||||||
| 	// A Syncer is a spy for the Sync portion of zapcore.WriteSyncer.
 | 	// A Syncer is a spy for the Sync portion of zapcore.WriteSyncer.
 | ||||||
| 	Syncer = ztest.Syncer | 	Syncer = ztest.Syncer | ||||||
| 
 | 
 | ||||||
| 	// A Discarder sends all writes to ioutil.Discard.
 | 	// A Discarder sends all writes to io.Discard.
 | ||||||
| 	Discarder = ztest.Discarder | 	Discarder = ztest.Discarder | ||||||
| 
 | 
 | ||||||
| 	// FailWriter is a WriteSyncer that always returns an error on writes.
 | 	// FailWriter is a WriteSyncer that always returns an error on writes.
 | ||||||
|  |  | ||||||
|  | @ -29,6 +29,9 @@ contrib.go.opencensus.io/exporter/prometheus | ||||||
| # contrib.go.opencensus.io/exporter/zipkin v0.1.2 | # contrib.go.opencensus.io/exporter/zipkin v0.1.2 | ||||||
| ## explicit | ## explicit | ||||||
| contrib.go.opencensus.io/exporter/zipkin | contrib.go.opencensus.io/exporter/zipkin | ||||||
|  | # github.com/benbjohnson/clock v1.1.0 | ||||||
|  | ## explicit; go 1.15 | ||||||
|  | github.com/benbjohnson/clock | ||||||
| # github.com/beorn7/perks v1.0.1 | # github.com/beorn7/perks v1.0.1 | ||||||
| ## explicit; go 1.11 | ## explicit; go 1.11 | ||||||
| github.com/beorn7/perks/quantile | github.com/beorn7/perks/quantile | ||||||
|  | @ -299,10 +302,11 @@ go.uber.org/automaxprocs/maxprocs | ||||||
| # go.uber.org/multierr v1.6.0 | # go.uber.org/multierr v1.6.0 | ||||||
| ## explicit; go 1.12 | ## explicit; go 1.12 | ||||||
| go.uber.org/multierr | go.uber.org/multierr | ||||||
| # go.uber.org/zap v1.19.1 | # go.uber.org/zap v1.24.0 | ||||||
| ## explicit; go 1.13 | ## explicit; go 1.19 | ||||||
| go.uber.org/zap | go.uber.org/zap | ||||||
| go.uber.org/zap/buffer | go.uber.org/zap/buffer | ||||||
|  | go.uber.org/zap/internal | ||||||
| go.uber.org/zap/internal/bufferpool | go.uber.org/zap/internal/bufferpool | ||||||
| go.uber.org/zap/internal/color | go.uber.org/zap/internal/color | ||||||
| go.uber.org/zap/internal/exit | go.uber.org/zap/internal/exit | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue