Repository: GoogleCloudPlatform/terraform-validator
Pull request #315 (2021-09-24 15:42:18)

Removed "level" field from error logger
In order to adhere more strictly to the error message format described in #255, we need to not include "level" field in error logging. Since the stable "API" we're defining is the JSON output, I've rewritten the tests against JSON output instead of zap internals; the zap internals don't necessarily give you enough information to know what the JSON output will look like, and I think we're not (currently) testing log output to the extent where it would be useful to have separate test methodology for basic formatting vs. more complex output.

(Also it turns out that a.Equal puts the `expected` value first, so I fixed that for the tests I was touching anyway.)


Patch:
diff --git a/Dockerfile b/Dockerfile
index 359b8f64..db61c2f6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@ FROM golang:1.14
 RUN apt-get update && apt-get -y install wget unzip
 
 WORKDIR /tmp
-RUN wget https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip && unzip terraform_0.12.24_linux_amd64.zip -d /usr/local/bin
+RUN wget https://releases.hashicorp.com/terraform/0.12.31/terraform_0.12.31_linux_amd64.zip && unzip terraform_0.12.31_linux_amd64.zip -d /usr/local/bin
 
 
 ENV GO111MODULE=on
diff --git a/cmd/logger.go b/cmd/logger.go
index db241fda..704430e5 100644
--- a/cmd/logger.go
+++ b/cmd/logger.go
@@ -15,8 +15,6 @@
 package cmd
 
 import (
-	"os"
-
 	"go.uber.org/zap"
 	"go.uber.org/zap/buffer"
 	"go.uber.org/zap/zapcore"
@@ -68,7 +66,7 @@ func newConsoleEncoder(cfg zapcore.EncoderConfig) errorEncoder {
 	}
 }
 
-func newErrorLogger(verbose, useStructuredLogging bool) *zap.Logger {
+func newErrorLogger(verbose, useStructuredLogging bool, writeSyncer zapcore.WriteSyncer) *zap.Logger {
 	// Return a logger that produces expected structured output format for errors
 	var level zap.AtomicLevel
 	options := []zap.Option{
@@ -88,7 +86,7 @@ func newErrorLogger(verbose, useStructuredLogging bool) *zap.Logger {
 
 	encoderConfig := zapcore.EncoderConfig{
 		TimeKey:       "timestamp",
-		LevelKey:      "level",
+		LevelKey:      "",
 		MessageKey:    "",
 		StacktraceKey: "",
 		EncodeLevel:   zapcore.LowercaseLevelEncoder,
@@ -100,11 +98,11 @@ func newErrorLogger(verbose, useStructuredLogging bool) *zap.Logger {
 	} else {
 		encoder = newConsoleEncoder(encoderConfig)
 	}
-	core := zapcore.NewCore(encoder, zapcore.Lock(os.Stderr), level)
+	core := zapcore.NewCore(encoder, writeSyncer, level)
 	return zap.New(core, options...)
 }
 
-func newOutputLogger() *zap.Logger {
+func newOutputLogger(writeSyncer zapcore.WriteSyncer) *zap.Logger {
 	// Return a logger that produces expected structured output format for output
 	options := []zap.Option{
 		zap.Fields(
@@ -125,6 +123,6 @@ func newOutputLogger() *zap.Logger {
 		EncodeTime:    zapcore.RFC3339NanoTimeEncoder,
 	}
 	encoder := zapcore.NewJSONEncoder(encoderConfig)
-	core := zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), level)
+	core := zapcore.NewCore(encoder, writeSyncer, level)
 	return zap.New(core, options...)
 }
diff --git a/cmd/root.go b/cmd/root.go
index 40320884..56051d49 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
 )
 
 const rootCmdDesc = `
@@ -48,12 +49,12 @@ func newRootCmd() (*cobra.Command, *zap.Logger, error) {
 	cmd.PersistentFlags().BoolVar(&o.verbose, "verbose", false, "Log additional output")
 
 	useStructuredLogging := os.Getenv("USE_STRUCTURED_LOGGING") == "true"
-	errorLogger := newErrorLogger(o.verbose, useStructuredLogging)
+	errorLogger := newErrorLogger(o.verbose, useStructuredLogging, zapcore.Lock(os.Stderr))
 	defer errorLogger.Sync()
 	zap.RedirectStdLog(errorLogger)
 	o.errorLogger = errorLogger
 
-	outputLogger := newOutputLogger()
+	outputLogger := newOutputLogger(zapcore.Lock(os.Stdout))
 	defer outputLogger.Sync()
 
 	cmd.AddCommand(newConvertCmd(errorLogger, outputLogger, useStructuredLogging))
diff --git a/go.mod b/go.mod
index 7c626b91..3052695b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,8 @@
 module github.com/GoogleCloudPlatform/terraform-validator
 
 require (
-	github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210917180804-7d769d84f6a1
+	github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210924222355-7083196d162b
 	github.com/forseti-security/config-validator v0.0.0-20210621194145-08e4202b50d8
-	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
 	github.com/golang/protobuf v1.5.2
 	github.com/hashicorp/terraform-json v0.12.0
 	github.com/hashicorp/terraform-plugin-sdk/v2 v2.5.0
@@ -13,9 +12,9 @@ require (
 	github.com/stretchr/testify v1.7.0
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.7.0 // indirect
-	go.uber.org/zap v1.19.1 // indirect
-	google.golang.org/api v0.46.0
-	google.golang.org/genproto v0.0.0-20210503173045-b96a97608f20
+	go.uber.org/zap v1.19.1
+	google.golang.org/api v0.56.0
+	google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71
 )
 
 go 1.14
diff --git a/go.sum b/go.sum
index 33096fc8..9df7f9fe 100644
--- a/go.sum
+++ b/go.sum
@@ -24,8 +24,13 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
 cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -61,20 +66,13 @@ github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxB
 github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
 github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 github.com/GoogleCloudPlatform/declarative-resource-client-library v0.0.0-20210519165700-76bc5cc4eeee h1:WZLlNdsfnB6FHmuyYtMaCEBVoKgUDW4YtXLGac5uB54=
 github.com/GoogleCloudPlatform/declarative-resource-client-library v0.0.0-20210519165700-76bc5cc4eeee/go.mod h1:oEeBHikdF/NrnUy0ornVaY1OT+jGvTqm+LQS0+ZDKzU=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210628183505-f0289a8b1278 h1:Qu1TGmdQRDgly4zL+2ucgc9JSzDoTmD3M5dmhdNHOPI=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210628183505-f0289a8b1278/go.mod h1:G9nbobJVSUiZx3TTUnwk8d519lUtNR5UIoQweRUY3Rs=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210902183351-4366981879d6 h1:NnyYy/gBs5kYEiYSbouF8jekR6AtEV/ssIsdgeP8VBc=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210902183351-4366981879d6/go.mod h1:G9nbobJVSUiZx3TTUnwk8d519lUtNR5UIoQweRUY3Rs=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210902210028-f286d71b9418 h1:awIENM0MgxIdbVUKBAQrb3JQ6PCj7tpP4HyMwhEZRQE=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210902210028-f286d71b9418/go.mod h1:G9nbobJVSUiZx3TTUnwk8d519lUtNR5UIoQweRUY3Rs=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210917180804-7d769d84f6a1 h1:qDglBAfw1FdkuFHl9z0uAJ6/dWHn3LssjBK+UyOsZ94=
-github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210917180804-7d769d84f6a1/go.mod h1:G9nbobJVSUiZx3TTUnwk8d519lUtNR5UIoQweRUY3Rs=
+github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210924222355-7083196d162b h1:K5lLH92cha1COyc3a2r0rCK7xaMNx/OLMKgrrGMGSKc=
+github.com/GoogleCloudPlatform/terraform-google-conversion v0.0.0-20210924222355-7083196d162b/go.mod h1:fISAtsaiVKCO4yOHfGKVUJOINPPrT++rmkpbIJ8PY/A=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
@@ -122,6 +120,7 @@ github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@@ -157,6 +156,7 @@ github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU
 github.com/aws/aws-sdk-go v1.36.30 h1:hAwyfe7eZa7sM+S5mIJZFiNFwJMia9Whz6CYblioLoU=
 github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -188,6 +188,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -249,6 +250,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/esimonov/ifshort v1.0.2/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE=
@@ -425,6 +427,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
 github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -449,6 +452,8 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
 github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
@@ -490,16 +495,18 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -513,6 +520,9 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw=
 github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -521,8 +531,9 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
 github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
@@ -561,6 +572,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@@ -682,7 +694,6 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
 github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
@@ -1128,30 +1139,28 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 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/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
-go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
 go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
 go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
 go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
@@ -1204,8 +1213,9 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@@ -1280,8 +1290,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1294,8 +1305,12 @@ golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1383,8 +1398,15 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
 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-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 h1:c20P3CcPbopVp2f7099WLOqSNKURf30Z0uq66HpijZY=
+golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1514,8 +1536,12 @@ golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 h1:cYSqdOzmV9wJ7lWurRAws06Dmif0Wv6UL4gQLlz+im0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1549,8 +1575,14 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.46.0 h1:jkDWHOBIoNSD0OQpq4rtBVu+Rh325MPjXG1rakAp8JU=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.56.0 h1:08F9XVYTLOGeSQb3xI9C0gXMuQanhdGed0cWFhDozbI=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1591,6 +1623,7 @@ google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfG
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -1613,8 +1646,20 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210503173045-b96a97608f20 h1:ov60aCaYZRD4c3+rjbtkVMhj/5CGviXVJqMCOrQSO3I=
 google.golang.org/genproto v0.0.0-20210503173045-b96a97608f20/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 h1:z+ErRPu0+KS02Td3fOAgdX+lnPDh/VyaABEJPD4JRQs=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1637,14 +1682,21 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1656,8 +1708,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1696,7 +1749,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -1709,7 +1761,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ=
 honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
 k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
 k8s.io/api v0.16.4/go.mod h1:AtzMnsR45tccQss5q8RnF+W8L81DH6XwXwo/joEx9u0=


Test patch:
diff --git a/cmd/convert_test.go b/cmd/convert_test.go
index b931dcb4..8316101e 100644
--- a/cmd/convert_test.go
+++ b/cmd/convert_test.go
@@ -2,6 +2,7 @@ package cmd
 
 import (
 	"context"
+	"encoding/json"
 	"testing"
 
 	"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
@@ -9,7 +10,7 @@ import (
 	"go.uber.org/zap"
 )
 
-func MockReadPlannedAssets(ctx context.Context, path, project, ancestry string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
+func testAssets() []google.Asset {
 	return []google.Asset{
 		google.Asset{
 			Name: "//compute.googleapis.com/projects/my-project/zones/us-central1-a/disks/test-disk",
@@ -30,15 +31,19 @@ func MockReadPlannedAssets(ctx context.Context, path, project, ancestry string,
 				},
 			},
 		},
-	}, nil
+	}
+}
+
+func MockReadPlannedAssets(ctx context.Context, path, project, ancestry string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
+	return testAssets(), nil
 }
 
 func TestConvertRun(t *testing.T) {
 	a := assert.New(t)
 	verbose := true
 	useStructuredLogging := true
-	errorLogger, errorObservedLogs := newTestErrorLogger(verbose, useStructuredLogging)
-	outputLogger, outputObservedLogs := newTestOutputLogger()
+	errorLogger, errorBuf := newTestErrorLogger(verbose, useStructuredLogging)
+	outputLogger, outputBuf := newTestOutputLogger()
 	o := convertOptions{
 		project:              "",
 		ancestry:             "",
@@ -52,25 +57,30 @@ func TestConvertRun(t *testing.T) {
 	err := o.run("/path/to/plan")
 	a.Nil(err)
 
-	errorLogs := errorObservedLogs.AllUntimed()
-	outputLogs := outputObservedLogs.AllUntimed()
+	errorJSON := errorBuf.String()
+	outputJSON := outputBuf.Bytes()
+
+	a.Equal(errorJSON, "")
 
-	a.Len(errorLogs, 0)
-	a.Len(outputLogs, 1)
+	var output map[string]interface{}
+	json.Unmarshal(outputJSON, &output)
 
 	// On a successful run, we should see a list of google assets in the resource_body field
-	fields := outputLogs[0].ContextMap()
-	a.Contains(fields, "resource_body")
-	a.Len(fields["resource_body"], 1)
-	a.IsType([]google.Asset{}, fields["resource_body"])
+	a.Contains(output, "resource_body")
+	a.Len(output["resource_body"], 1)
+
+	var expectedAssets []interface{}
+	expectedAssetJSON, _ := json.Marshal(testAssets())
+	json.Unmarshal(expectedAssetJSON, &expectedAssets)
+	a.Equal(expectedAssets, output["resource_body"])
 }
 
 func TestConvertRunLegacy(t *testing.T) {
 	a := assert.New(t)
 	verbose := true
 	useStructuredLogging := false
-	errorLogger, errorObservedLogs := newTestErrorLogger(verbose, useStructuredLogging)
-	outputLogger, outputObservedLogs := newTestOutputLogger()
+	errorLogger, errorBuf := newTestErrorLogger(verbose, useStructuredLogging)
+	outputLogger, outputBuf := newTestOutputLogger()
 	o := convertOptions{
 		project:              "",
 		ancestry:             "",
@@ -84,10 +94,10 @@ func TestConvertRunLegacy(t *testing.T) {
 	err := o.run("/path/to/plan")
 	a.Nil(err)
 
-	errorLogs := errorObservedLogs.AllUntimed()
-	outputLogs := outputObservedLogs.AllUntimed()
+	errorJSON := errorBuf.String()
+	outputJSON := outputBuf.String()
 
 	// On a successful legacy run, we don't output anything via loggers.
-	a.Len(errorLogs, 0)
-	a.Len(outputLogs, 0)
+	a.Equal(errorJSON, "")
+	a.Equal(outputJSON, "")
 }
diff --git a/cmd/logger_test.go b/cmd/logger_test.go
index 0bf9c224..7c15f1ee 100644
--- a/cmd/logger_test.go
+++ b/cmd/logger_test.go
@@ -1,27 +1,122 @@
 package cmd
 
 import (
+	"bytes"
+	"encoding/json"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
 	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
-	"go.uber.org/zap/zaptest/observer"
 )
 
-func newTestErrorLogger(verbose, useStructuredLogging bool) (*zap.Logger, *observer.ObservedLogs) {
-	logger := newErrorLogger(verbose, useStructuredLogging)
-	observerCore, logs := observer.New(zap.DebugLevel)
-	wrapper := zap.WrapCore(func(zapcore.Core) zapcore.Core {
-		return observerCore
-	})
+type bufferWriteSyncer struct {
+	*bytes.Buffer
+}
+
+func (bws bufferWriteSyncer) Sync() error {
+	return nil
+}
+
+func newTestErrorLogger(verbose, useStructuredLogging bool) (*zap.Logger, *bytes.Buffer) {
+	buf := new(bytes.Buffer)
+	syncer := bufferWriteSyncer{Buffer: buf}
+	logger := newErrorLogger(verbose, useStructuredLogging, syncer)
+	return logger, syncer.Buffer
+}
 
-	return logger.WithOptions(wrapper), logs
+func newTestOutputLogger() (*zap.Logger, *bytes.Buffer) {
+	buf := new(bytes.Buffer)
+	syncer := bufferWriteSyncer{Buffer: buf}
+	logger := newOutputLogger(syncer)
+	return logger, syncer.Buffer
 }
 
-func newTestOutputLogger() (*zap.Logger, *observer.ObservedLogs) {
-	logger := newOutputLogger()
-	observerCore, logs := observer.New(zap.DebugLevel)
-	wrapper := zap.WrapCore(func(zapcore.Core) zapcore.Core {
-		return observerCore
-	})
+func TestErrorLoggerSchema(t *testing.T) {
+	// Expected schema is:
+	// {
+	//     "version": "vX.X.X",
+	//     "timestamp": "RFC 3339-encoded timestamp",
+	//     "error_details": {
+	//         "error": "error type",
+	//         "context": "additional error context (optional)"
+	//     }
+	// }
+	verbose := true
+	useStructuredLogging := true
+
+	errorLogger, buf := newTestErrorLogger(verbose, useStructuredLogging)
+	errorLogger.Info("This is a message")
+
+	outputJSON := buf.Bytes()
+
+	var output map[string]interface{}
+	json.Unmarshal(outputJSON, &output)
+
+	expectedOutput := map[string]interface{}{
+		"version":   "v1.0.0",
+		"timestamp": "tested separately",
+		"error_details": map[string]interface{}{
+			"error":   "This is a message",
+			"context": "",
+		},
+	}
+
+	a := assert.New(t)
+	a.Equal(len(output), len(expectedOutput))
+
+	for k := range expectedOutput {
+		a.Contains(output, k)
+	}
+
+	a.Equal(output["version"], expectedOutput["version"])
+	a.Equal(output["error_details"], expectedOutput["error_details"])
+
+	// This should not fail
+	_, err := time.Parse(time.RFC3339Nano, output["timestamp"].(string))
+	a.Nil(err)
+}
+
+func TestOutputLoggerSchema(t *testing.T) {
+	// Expected schema is:
+	// {
+	//     "version": "vX.X.X",
+	//     "timestamp": "RFC 3339-encoded timestamp",
+	//     "body": "Message content",  // Optional
+	//     "resource_body": {},  // Optional, {} or []
+	// }
+
+	resourceBody := []interface{}{"foo", "bar", "baz"}
+	outputLogger, buf := newTestOutputLogger()
+	outputLogger.Info(
+		"This is a message",
+		zap.Any("resource_body", resourceBody),
+	)
+
+	outputJSON := buf.Bytes()
+
+	var output map[string]interface{}
+	json.Unmarshal(outputJSON, &output)
+
+	expectedOutput := map[string]interface{}{
+		"version":       "v1.0.0",
+		"timestamp":     "tested separately",
+		"body":          "This is a message",
+		"resource_body": resourceBody,
+	}
+
+	a := assert.New(t)
+	a.Equal(len(output), len(expectedOutput))
+
+	for k := range expectedOutput {
+		a.Contains(output, k)
+	}
+
+	a.Equal(output["version"], expectedOutput["version"])
+	a.Equal(output["body"], expectedOutput["body"])
+	a.Equal(output["resource_body"], expectedOutput["resource_body"])
 
-	return logger.WithOptions(wrapper), logs
+	// This should not fail
+	_, err := time.Parse(time.RFC3339Nano, output["timestamp"].(string))
+	a.Nil(err)
 }
diff --git a/cmd/validate_test.go b/cmd/validate_test.go
index 46176f22..d2f7cc05 100644
--- a/cmd/validate_test.go
+++ b/cmd/validate_test.go
@@ -2,6 +2,7 @@ package cmd
 
 import (
 	"context"
+	"encoding/json"
 	"testing"
 
 	"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
@@ -9,13 +10,17 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func MockValidateAssetsNoViolations(ctx context.Context, assets []google.Asset, policyRootPath string) (*validator.AuditResponse, error) {
+func testAuditResponseNoViolations() *validator.AuditResponse {
 	return &validator.AuditResponse{
 		Violations: []*validator.Violation{},
-	}, nil
+	}
 }
 
-func MockValidateAssetsWithViolations(ctx context.Context, assets []google.Asset, policyRootPath string) (*validator.AuditResponse, error) {
+func MockValidateAssetsNoViolations(ctx context.Context, assets []google.Asset, policyRootPath string) (*validator.AuditResponse, error) {
+	return testAuditResponseNoViolations(), nil
+}
+
+func testAuditResponseWithViolations() *validator.AuditResponse {
 	return &validator.AuditResponse{
 		Violations: []*validator.Violation{
 			&validator.Violation{
@@ -25,15 +30,19 @@ func MockValidateAssetsWithViolations(ctx context.Context, assets []google.Asset
 				Severity:   "high",
 			},
 		},
-	}, nil
+	}
+}
+
+func MockValidateAssetsWithViolations(ctx context.Context, assets []google.Asset, policyRootPath string) (*validator.AuditResponse, error) {
+	return testAuditResponseWithViolations(), nil
 }
 
 func TestValidateRun(t *testing.T) {
 	a := assert.New(t)
 	verbose := true
 	useStructuredLogging := true
-	errorLogger, errorObservedLogs := newTestErrorLogger(verbose, useStructuredLogging)
-	outputLogger, outputObservedLogs := newTestOutputLogger()
+	errorLogger, errorBuf := newTestErrorLogger(verbose, useStructuredLogging)
+	outputLogger, outputBuf := newTestOutputLogger()
 	o := validateOptions{
 		project:              "",
 		ancestry:             "",
@@ -51,25 +60,30 @@ func TestValidateRun(t *testing.T) {
 	err := o.run("/path/to/plan")
 	a.ErrorIs(err, errViolations)
 
-	errorLogs := errorObservedLogs.AllUntimed()
-	outputLogs := outputObservedLogs.AllUntimed()
+	errorJSON := errorBuf.String()
+	outputJSON := outputBuf.Bytes()
 
-	a.Len(errorLogs, 0)
-	a.Len(outputLogs, 1)
+	a.Equal("", errorJSON)
+
+	var output map[string]interface{}
+	json.Unmarshal(outputJSON, &output)
 
 	// On a run with violations, we should see a list of violations in the resource_body field
-	fields := outputLogs[0].ContextMap()
-	a.Contains(fields, "resource_body")
-	a.Len(fields["resource_body"], 1)
-	a.IsType([]*validator.Violation{}, fields["resource_body"])
+	a.Contains(output, "resource_body")
+	a.Len(output["resource_body"], 1)
+
+	var expectedAuditResponse map[string]interface{}
+	expectedAuditResponseJSON, _ := json.Marshal(testAuditResponseWithViolations())
+	json.Unmarshal(expectedAuditResponseJSON, &expectedAuditResponse)
+	a.Equal(expectedAuditResponse["violations"], output["resource_body"])
 }
 
 func TestValidateRunNoViolations(t *testing.T) {
 	a := assert.New(t)
 	verbose := true
 	useStructuredLogging := true
-	errorLogger, errorObservedLogs := newTestErrorLogger(verbose, useStructuredLogging)
-	outputLogger, outputObservedLogs := newTestOutputLogger()
+	errorLogger, errorBuf := newTestErrorLogger(verbose, useStructuredLogging)
+	outputLogger, outputBuf := newTestOutputLogger()
 	o := validateOptions{
 		project:              "",
 		ancestry:             "",
@@ -87,25 +101,25 @@ func TestValidateRunNoViolations(t *testing.T) {
 	err := o.run("/path/to/plan")
 	a.Nil(err)
 
-	errorLogs := errorObservedLogs.AllUntimed()
-	outputLogs := outputObservedLogs.AllUntimed()
+	errorJSON := errorBuf.String()
+	outputJSON := outputBuf.Bytes()
+
+	a.Equal("", errorJSON)
 
-	a.Len(errorLogs, 0)
-	a.Len(outputLogs, 1)
+	var output map[string]interface{}
+	json.Unmarshal(outputJSON, &output)
 
 	// On a run with no violations, we should see an empty list of violations in the resource_body field
-	fields := outputLogs[0].ContextMap()
-	a.Contains(fields, "resource_body")
-	a.Len(fields["resource_body"], 0)
-	a.IsType([]*validator.Violation{}, fields["resource_body"])
+	a.Contains(output, "resource_body")
+	a.Len(output["resource_body"], 0)
 }
 
 func TestValidateRunLegacy(t *testing.T) {
 	a := assert.New(t)
 	verbose := true
 	useStructuredLogging := false
-	errorLogger, errorObservedLogs := newTestErrorLogger(verbose, useStructuredLogging)
-	outputLogger, outputObservedLogs := newTestOutputLogger()
+	errorLogger, errorBuf := newTestErrorLogger(verbose, useStructuredLogging)
+	outputLogger, outputBuf := newTestOutputLogger()
 	o := validateOptions{
 		project:              "",
 		ancestry:             "",
@@ -123,20 +137,20 @@ func TestValidateRunLegacy(t *testing.T) {
 	err := o.run("/path/to/plan")
 	a.ErrorIs(err, errViolations)
 
-	errorLogs := errorObservedLogs.AllUntimed()
-	outputLogs := outputObservedLogs.AllUntimed()
+	errorJSON := errorBuf.String()
+	outputJSON := outputBuf.String()
 
 	// On a legacy run with validation errors, loggers should not be used.
-	a.Len(errorLogs, 0)
-	a.Len(outputLogs, 0)
+	a.Equal("", errorJSON)
+	a.Equal("", outputJSON)
 }
 
 func TestValidateRunNoViolationsLegacy(t *testing.T) {
 	a := assert.New(t)
 	verbose := true
 	useStructuredLogging := false
-	errorLogger, errorObservedLogs := newTestErrorLogger(verbose, useStructuredLogging)
-	outputLogger, outputObservedLogs := newTestOutputLogger()
+	errorLogger, errorBuf := newTestErrorLogger(verbose, useStructuredLogging)
+	outputLogger, outputBuf := newTestOutputLogger()
 	o := validateOptions{
 		project:              "",
 		ancestry:             "",
@@ -154,10 +168,10 @@ func TestValidateRunNoViolationsLegacy(t *testing.T) {
 	err := o.run("/path/to/plan")
 	a.Nil(err)
 
-	errorLogs := errorObservedLogs.AllUntimed()
-	outputLogs := outputObservedLogs.AllUntimed()
+	errorJSON := errorBuf.String()
+	outputJSON := outputBuf.String()
 
 	// On a legacy run with no validation errors, loggers should not be used.
-	a.Len(errorLogs, 0)
-	a.Len(outputLogs, 0)
+	a.Equal("", errorJSON)
+	a.Equal("", outputJSON)
 }
diff --git a/test/cli_test.go b/test/cli_test.go
index a787ca14..26b75f5c 100644
--- a/test/cli_test.go
+++ b/test/cli_test.go
@@ -59,6 +59,7 @@ func TestCLI(t *testing.T) {
 		{name: "firewall"},
 		{name: "instance"},
 		{name: "sql"},
+		{name: "example_access_context_manager_service_perimeter"},
 		{name: "example_bigquery_dataset"},
 		{name: "example_bigtable_instance"},
 		{name: "example_compute_disk"},
diff --git a/test/init_test.go b/test/init_test.go
index b8d98fb3..3bf62313 100644
--- a/test/init_test.go
+++ b/test/init_test.go
@@ -20,7 +20,7 @@ const (
 	defaultOrganization    = "12345"
 	defaultFolder          = "67890"
 	defaultProject         = "foobar"
-	defaultProviderVersion = "3.51.0"
+	defaultProviderVersion = "3.84.0"
 )
 
 var (
diff --git a/test/read_test.go b/test/read_test.go
index 85b5e736..206a313c 100644
--- a/test/read_test.go
+++ b/test/read_test.go
@@ -20,6 +20,7 @@ func TestReadPlannedAssetsCoverage(t *testing.T) {
 	cases := []struct {
 		name string
 	}{
+		{name: "example_access_context_manager_service_perimeter"},
 		{name: "example_bigquery_dataset"},
 		{name: "example_bigtable_instance"},
 		{name: "example_compute_disk"},
diff --git a/testdata/templates/example_access_context_manager_service_perimeter.json b/testdata/templates/example_access_context_manager_service_perimeter.json
new file mode 100644
index 00000000..4dfc6f74
--- /dev/null
+++ b/testdata/templates/example_access_context_manager_service_perimeter.json
@@ -0,0 +1,62 @@
+[
+  {
+    "name": "//accesscontextmanager.googleapis.com/accessPolicies/987654/servicePerimeters/restrict_storage",
+    "asset_type": "accesscontextmanager.googleapis.com/ServicePerimeter",
+    "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}",
+    "resource": {
+      "version": "v1",
+      "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/accesscontextmanager/v1/rest",
+      "discovery_name": "ServicePerimeter",
+      "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}",
+      "data": {
+        "name": "accessPolicies/987654/servicePerimeters/restrict_storage",
+        "perimeterType": "PERIMETER_TYPE_REGULAR",
+        "status": {
+          "egressPolicies": [
+            {
+              "egressFrom": {
+                "identityType": "ANY_USER_ACCOUNT"
+              }
+            }
+          ],
+          "ingressPolicies": [
+            {
+              "ingressFrom": {
+                "identityType": "ANY_IDENTITY",
+                "sources": [
+                  {
+                    "accessLevel": "accessPolicies/987654/accessLevels/restrict_storage"
+                  }
+                ]
+              },
+              "ingressTo": {
+                "operations": [
+                  {
+                    "methodSelectors": [
+                      {
+                        "method": "google.storage.objects.create"
+                      }
+                    ],
+                    "serviceName": "storage.googleapis.com"
+                  }
+                ],
+                "resources": [
+                  "*"
+                ]
+              }
+            }
+          ],
+          "resources": [
+            "projects/54321",
+            "projects/4321"
+          ],
+          "restrictedServices": [
+            "bigquery.googleapis.com",
+            "storage.googleapis.com"
+          ]
+        },
+        "title": "restrict_storage"
+      }
+    }
+  }
+]
diff --git a/testdata/templates/example_access_context_manager_service_perimeter.tf b/testdata/templates/example_access_context_manager_service_perimeter.tf
new file mode 100644
index 00000000..84129b04
--- /dev/null
+++ b/testdata/templates/example_access_context_manager_service_perimeter.tf
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+  required_providers {
+    google = {
+      source = "hashicorp/google"
+      version = "~> {{.Provider.version}}"
+    }
+  }
+}
+
+provider "google" {
+  {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}}
+}
+
+resource "google_access_context_manager_service_perimeter" "service-perimeter" {
+  parent = "accessPolicies/987654"
+  name   = "accessPolicies/987654/servicePerimeters/restrict_storage"
+  title  = "restrict_storage"
+
+  status {
+    restricted_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
+    resources = ["projects/54321", "projects/4321"]
+
+    ingress_policies {
+      ingress_from {
+        sources {
+          access_level = "accessPolicies/987654/accessLevels/restrict_storage"
+        }
+        identity_type = "ANY_IDENTITY"
+      }
+
+      ingress_to {
+        resources = ["*"]
+        operations {
+          service_name = "storage.googleapis.com"
+          method_selectors {
+            method = "google.storage.objects.create"
+          }
+        }
+      }
+    }
+
+    egress_policies {
+      egress_from {
+        identity_type = "ANY_USER_ACCOUNT"
+      }
+    }
+  }
+}
diff --git a/testdata/templates/example_access_context_manager_service_perimeter.tfplan.json b/testdata/templates/example_access_context_manager_service_perimeter.tfplan.json
new file mode 100644
index 00000000..b8f59687
--- /dev/null
+++ b/testdata/templates/example_access_context_manager_service_perimeter.tfplan.json
@@ -0,0 +1,324 @@
+{
+  "format_version": "0.1",
+  "terraform_version": "0.12.6",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_access_context_manager_service_perimeter.service-perimeter",
+          "mode": "managed",
+          "type": "google_access_context_manager_service_perimeter",
+          "name": "service-perimeter",
+          "provider_name": "google",
+          "schema_version": 0,
+          "values": {
+            "description": null,
+            "name": "accessPolicies/987654/servicePerimeters/restrict_storage",
+            "parent": "accessPolicies/987654",
+            "perimeter_type": "PERIMETER_TYPE_REGULAR",
+            "spec": [],
+            "status": [
+              {
+                "access_levels": null,
+                "egress_policies": [
+                  {
+                    "egress_from": [
+                      {
+                        "identities": null,
+                        "identity_type": "ANY_USER_ACCOUNT"
+                      }
+                    ],
+                    "egress_to": []
+                  }
+                ],
+                "ingress_policies": [
+                  {
+                    "ingress_from": [
+                      {
+                        "identities": null,
+                        "identity_type": "ANY_IDENTITY",
+                        "sources": [
+                          {
+                            "access_level": "accessPolicies/987654/accessLevels/restrict_storage",
+                            "resource": null
+                          }
+                        ]
+                      }
+                    ],
+                    "ingress_to": [
+                      {
+                        "operations": [
+                          {
+                            "method_selectors": [
+                              {
+                                "method": "google.storage.objects.create",
+                                "permission": null
+                              }
+                            ],
+                            "service_name": "storage.googleapis.com"
+                          }
+                        ],
+                        "resources": [
+                          "*"
+                        ]
+                      }
+                    ]
+                  }
+                ],
+                "resources": [
+                  "projects/54321",
+                  "projects/4321"
+                ],
+                "restricted_services": [
+                  "storage.googleapis.com",
+                  "bigquery.googleapis.com"
+                ],
+                "vpc_accessible_services": []
+              }
+            ],
+            "timeouts": null,
+            "title": "restrict_storage",
+            "use_explicit_dry_run_spec": null
+          }
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_access_context_manager_service_perimeter.service-perimeter",
+      "mode": "managed",
+      "type": "google_access_context_manager_service_perimeter",
+      "name": "service-perimeter",
+      "provider_name": "google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "description": null,
+          "name": "accessPolicies/987654/servicePerimeters/restrict_storage",
+          "parent": "accessPolicies/987654",
+          "perimeter_type": "PERIMETER_TYPE_REGULAR",
+          "spec": [],
+          "status": [
+            {
+              "access_levels": null,
+              "egress_policies": [
+                {
+                  "egress_from": [
+                    {
+                      "identities": null,
+                      "identity_type": "ANY_USER_ACCOUNT"
+                    }
+                  ],
+                  "egress_to": []
+                }
+              ],
+              "ingress_policies": [
+                {
+                  "ingress_from": [
+                    {
+                      "identities": null,
+                      "identity_type": "ANY_IDENTITY",
+                      "sources": [
+                        {
+                          "access_level": "accessPolicies/987654/accessLevels/restrict_storage",
+                          "resource": null
+                        }
+                      ]
+                    }
+                  ],
+                  "ingress_to": [
+                    {
+                      "operations": [
+                        {
+                          "method_selectors": [
+                            {
+                              "method": "google.storage.objects.create",
+                              "permission": null
+                            }
+                          ],
+                          "service_name": "storage.googleapis.com"
+                        }
+                      ],
+                      "resources": [
+                        "*"
+                      ]
+                    }
+                  ]
+                }
+              ],
+              "resources": [
+                "projects/54321",
+                "projects/4321"
+              ],
+              "restricted_services": [
+                "bigquery.googleapis.com",
+                "storage.googleapis.com"
+              ],
+              "vpc_accessible_services": []
+            }
+          ],
+          "timeouts": null,
+          "title": "restrict_storage",
+          "use_explicit_dry_run_spec": null
+        },
+        "after_unknown": {
+          "create_time": true,
+          "id": true,
+          "spec": [],
+          "status": [
+            {
+              "egress_policies": [
+                {
+                  "egress_from": [
+                    {}
+                  ],
+                  "egress_to": []
+                }
+              ],
+              "ingress_policies": [
+                {
+                  "ingress_from": [
+                    {
+                      "sources": [
+                        {}
+                      ]
+                    }
+                  ],
+                  "ingress_to": [
+                    {
+                      "operations": [
+                        {
+                          "method_selectors": [
+                            {}
+                          ]
+                        }
+                      ],
+                      "resources": [
+                        false
+                      ]
+                    }
+                  ]
+                }
+              ],
+              "resources": [
+                false
+              ],
+              "restricted_services": [
+                false
+              ],
+              "vpc_accessible_services": []
+            }
+          ],
+          "update_time": true
+        }
+      }
+    }
+  ],
+  "configuration": {
+    "provider_config": {
+      "google": {
+        "name": "google",
+        "expressions": {
+          "project": {
+            "constant_value": "{{.Provider.project}}"
+          }
+        }
+      }
+    },
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_access_context_manager_service_perimeter.service-perimeter",
+          "mode": "managed",
+          "type": "google_access_context_manager_service_perimeter",
+          "name": "service-perimeter",
+          "provider_config_key": "google",
+          "expressions": {
+            "name": {
+              "constant_value": "accessPolicies/987654/servicePerimeters/restrict_storage"
+            },
+            "parent": {
+              "constant_value": "accessPolicies/987654"
+            },
+            "status": [
+              {
+                "egress_policies": [
+                  {
+                    "egress_from": [
+                      {
+                        "identity_type": {
+                          "constant_value": "ANY_USER_ACCOUNT"
+                        }
+                      }
+                    ]
+                  }
+                ],
+                "ingress_policies": [
+                  {
+                    "ingress_from": [
+                      {
+                        "identity_type": {
+                          "constant_value": "ANY_IDENTITY"
+                        },
+                        "sources": [
+                          {
+                            "access_level": {
+                              "constant_value": "accessPolicies/987654/accessLevels/restrict_storage"
+                            }
+                          }
+                        ]
+                      }
+                    ],
+                    "ingress_to": [
+                      {
+                        "operations": [
+                          {
+                            "method_selectors": [
+                              {
+                                "method": {
+                                  "constant_value": "google.storage.objects.create"
+                                }
+                              }
+                            ],
+                            "service_name": {
+                              "constant_value": "storage.googleapis.com"
+                            }
+                          }
+                        ],
+                        "resources": {
+                          "constant_value": [
+                            "*"
+                          ]
+                        }
+                      }
+                    ]
+                  }
+                ],
+                "resources": {
+                  "constant_value": [
+                    "projects/54321",
+                    "projects/4321"
+                  ]
+                },
+                "restricted_services": {
+                  "constant_value": [
+                    "bigquery.googleapis.com",
+                    "storage.googleapis.com"
+                  ]
+                }
+              }
+            ],
+            "title": {
+              "constant_value": "restrict_storage"
+            }
+          },
+          "schema_version": 0
+        }
+      ]
+    }
+  }
+}
\ No newline at end of file