Repository: google/go-containerregistry
Pull request #816 (2020-11-06 01:04:26)

Revamp debug logs
1. Drop places that can log creds. May bring this back with build tags?
2. Introduce pkg/internal/redact to hold a context that signals we
should omit the body when dumping logs. Maybe this should live in
the logs package, but internal for now should be fine.
3. Stop logging the request and response body for blobs and token
responses.

jonjohnsonjr: cc @ahmetb
codecov-io: # [Codecov](https://codecov.io/gh/google/go-containerregistry/pull/816?src=pr&el=h1) Report
> Merging [#816](https://codecov.io/gh/google/go-containerregistry/pull/816?src=pr&el=desc) into [master](https://codecov.io/gh/google/go-containerregistry/commit/a14ada4496c9349f81af6bfa9ecd654cd19ff581?el=desc) will **decrease** coverage by `0.18%`.
> The diff coverage is `53.57%`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-containerregistry/pull/816/graphs/tree.svg?width=650&height=150&src=pr&token=wG7iiDCdZ4)](https://codecov.io/gh/google/go-containerregistry/pull/816?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #816      +/-   ##
==========================================
- Coverage   74.77%   74.58%   -0.19%     
==========================================
  Files         103      103              
  Lines        4313     4332      +19     
==========================================
+ Hits         3225     3231       +6     
- Misses        611      619       +8     
- Partials      477      482       +5     
```


| [Impacted Files](https://codecov.io/gh/google/go-containerregistry/pull/816?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/v1/google/auth.go](https://codecov.io/gh/google/go-containerregistry/pull/816/diff?src=pr&el=tree#diff-cGtnL3YxL2dvb2dsZS9hdXRoLmdv) | `58.49% <0.00%> (-8.82%)` | :arrow_down: |
| [pkg/authn/keychain.go](https://codecov.io/gh/google/go-containerregistry/pull/816/diff?src=pr&el=tree#diff-cGtnL2F1dGhuL2tleWNoYWluLmdv) | `76.47% <38.46%> (-23.53%)` | :arrow_down: |
| [pkg/v1/remote/transport/logger.go](https://codecov.io/gh/google/go-containerregistry/pull/816/diff?src=pr&el=tree#diff-cGtnL3YxL3JlbW90ZS90cmFuc3BvcnQvbG9nZ2VyLmdv) | `100.00% <100.00%> (ø)` | |
| [pkg/v1/layout/write.go](https://codecov.io/gh/google/go-containerregistry/pull/816/diff?src=pr&el=tree#diff-cGtnL3YxL2xheW91dC93cml0ZS5nbw==) | `48.41% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-containerregistry/pull/816?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-containerregistry/pull/816?src=pr&el=footer). Last update [a14ada4...5774bd5](https://codecov.io/gh/google/go-containerregistry/pull/816?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

ahmetb: I found req/resp dumps very useful.
The only time it wasn't useful was when the content type was a blob.

so I was doing something like this to download a layer into a file.

`crane blob -v us-central1-docker.pkg.dev/ahmetb-demo/ahmetb-demo/busybox@sha256:9758c28807f21c13d05c704821fdd56c0b9574912f9b916c65e1df3e6b8bc572 > blobfile` 

and `-v` ended up printing the layer blob (garbage) into the log stream as well.
jonjohnsonjr: Trying to figure out a good way to actually filter out stuff you don't care about. We don't want to log the token response and we don't want to log binary blobs, but it's impossible to determine those from just the request or response in this context. We could do something janky like modify the context to tell the logger not to log certain requests...
jonjohnsonjr: I've implemented my janky suggestion to thread this through `ctx`.

Note some things:
1. `Authorization: <redacted>`
2. `[body redacted: basic token response contains credentials]`
3. `[body redacted: omitting binary blobs from logs]`

```
$ crane blob -v ubuntu@sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf > /dev/null
2020/11/07 11:32:23 No matching credentials were found, falling back on anonymous
2020/11/07 11:32:23 --> GET https://index.docker.io/v2/
2020/11/07 11:32:23 GET /v2/ HTTP/1.1
Host: index.docker.io
User-Agent: Go-http-client/1.1
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:32:23 <-- 401 https://index.docker.io/v2/ (494.178722ms)
2020/11/07 11:32:23 HTTP/1.1 401 Unauthorized
Content-Length: 87
Content-Type: application/json
Date: Sat, 07 Nov 2020 19:32:23 GMT
Docker-Distribution-Api-Version: registry/2.0
Strict-Transport-Security: max-age=31536000
Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io"

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}

2020/11/07 11:32:23 --> GET https://auth.docker.io/token?scope=repository%3Alibrary%2Fubuntu%3Apull&service=registry.docker.io
2020/11/07 11:32:23 GET /token?scope=repository%3Alibrary%2Fubuntu%3Apull&service=registry.docker.io HTTP/1.1
Host: auth.docker.io
User-Agent: go-containerregistry
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:32:24 <-- 200 https://auth.docker.io/token?scope=repository%3Alibrary%2Fubuntu%3Apull&service=registry.docker.io (403.668243ms) [body redacted: basic token response contains credentials]
2020/11/07 11:32:24 HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Date: Sat, 07 Nov 2020 19:32:24 GMT
Strict-Transport-Security: max-age=31536000


2020/11/07 11:32:24 --> GET https://index.docker.io/v2/library/ubuntu/blobs/sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf
2020/11/07 11:32:24 GET /v2/library/ubuntu/blobs/sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf HTTP/1.1
Host: index.docker.io
User-Agent: go-containerregistry
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:32:24 <-- 307 https://index.docker.io/v2/library/ubuntu/blobs/sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf (95.80319ms) [body redacted: omitting binary blobs from logs]
2020/11/07 11:32:24 HTTP/1.1 307 Temporary Redirect
Content-Type: application/octet-stream
Date: Sat, 07 Nov 2020 19:32:24 GMT
Docker-Distribution-Api-Version: registry/2.0
Location: https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/6a/6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf/data?verify=1604780544-woHbF5xmW4Wavkf%2FvB1C1RNeZZQ%3D
Strict-Transport-Security: max-age=31536000
Content-Length: 0


2020/11/07 11:32:24 --> GET https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/6a/6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf/data?verify=1604780544-woHbF5xmW4Wavkf%2FvB1C1RNeZZQ%3D
2020/11/07 11:32:24 GET /registry-v2/docker/registry/v2/blobs/sha256/6a/6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf/data?verify=1604780544-woHbF5xmW4Wavkf%2FvB1C1RNeZZQ%3D HTTP/1.1
Host: production.cloudflare.docker.com
User-Agent: go-containerregistry
Authorization: <redacted>
Referer: https://index.docker.io/v2/library/ubuntu/blobs/sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf
Accept-Encoding: gzip


2020/11/07 11:32:24 <-- 200 https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/6a/6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf/data?verify=1604780544-woHbF5xmW4Wavkf%2FvB1C1RNeZZQ%3D (105.603087ms) [body redacted: omitting binary blobs from logs]
2020/11/07 11:32:24 HTTP/2.0 200 OK
Content-Length: 28558714
Accept-Ranges: bytes
Age: 201801
Cache-Control: public, max-age=14400
Cf-Cache-Status: HIT
Cf-Ray: 5ee97ae4de037959-SEA
Cf-Request-Id: 0645cb230600007959be0c8000000001
Content-Type: application/octet-stream
Date: Sat, 07 Nov 2020 19:32:24 GMT
Etag: "3c8f0561bcab424ddb8887f42759da13"
Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Expires: Sat, 07 Nov 2020 23:32:24 GMT
Last-Modified: Thu, 08 Oct 2020 15:19:51 GMT
Server: cloudflare
Set-Cookie: __cfduid=db24da07e2e9df330b9cf6bafd265e4d61604777544; expires=Mon, 07-Dec-20 19:32:24 GMT; path=/; domain=.production.cloudflare.docker.com; HttpOnly; SameSite=Lax; Secure
Vary: Accept-Encoding
X-Amz-Id-2: bZnMOqLoGLAI2Ply9pwVRIIRvJFIakdJpbKgJG+rF/hWd+xuCxywAiiNJFs4yFh4cx74eL8sFQk=
X-Amz-Request-Id: 565DB4977C19FB85
X-Amz-Storage-Class: INTELLIGENT_TIERING
X-Amz-Version-Id: uNmm5NKwwibXznv27C.AD4gWWogAu5GB
```

Nicely, config blob is not omitted:
```
$ crane config -v ubuntu | jq .created
2020/11/07 11:38:01 No matching credentials were found, falling back on anonymous
2020/11/07 11:38:01 --> GET https://index.docker.io/v2/
2020/11/07 11:38:01 GET /v2/ HTTP/1.1
Host: index.docker.io
User-Agent: Go-http-client/1.1
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:38:02 <-- 401 https://index.docker.io/v2/ (481.631242ms)
2020/11/07 11:38:02 HTTP/1.1 401 Unauthorized
Content-Length: 87
Content-Type: application/json
Date: Sat, 07 Nov 2020 19:38:02 GMT
Docker-Distribution-Api-Version: registry/2.0
Strict-Transport-Security: max-age=31536000
Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io"

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}

2020/11/07 11:38:02 --> GET https://auth.docker.io/token?scope=repository%3Alibrary%2Fubuntu%3Apull&service=registry.docker.io
2020/11/07 11:38:02 GET /token?scope=repository%3Alibrary%2Fubuntu%3Apull&service=registry.docker.io HTTP/1.1
Host: auth.docker.io
User-Agent: go-containerregistry
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:38:02 <-- 200 https://auth.docker.io/token?scope=repository%3Alibrary%2Fubuntu%3Apull&service=registry.docker.io (407.501085ms) [body redacted: basic token response contains credentials]
2020/11/07 11:38:02 HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Date: Sat, 07 Nov 2020 19:38:02 GMT
Strict-Transport-Security: max-age=31536000


2020/11/07 11:38:02 --> GET https://index.docker.io/v2/library/ubuntu/manifests/latest
2020/11/07 11:38:02 GET /v2/library/ubuntu/manifests/latest HTTP/1.1
Host: index.docker.io
User-Agent: go-containerregistry
Accept: application/vnd.docker.distribution.manifest.v1+json,application/vnd.docker.distribution.manifest.v1+prettyjws,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.index.v1+json
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:38:02 <-- 200 https://index.docker.io/v2/library/ubuntu/manifests/latest (156.544952ms)
2020/11/07 11:38:02 HTTP/1.1 200 OK
Content-Length: 1201
Content-Type: application/vnd.docker.distribution.manifest.list.v2+json
Date: Sat, 07 Nov 2020 19:38:02 GMT
Docker-Content-Digest: sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1"
Strict-Transport-Security: max-age=31536000

{"manifests":[{"digest":"sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956","mediaType":"application\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"amd64","os":"linux"},"size":943},{"digest":"sha256:ebc8925ee51929a9d691f260b34dd6ed59b4cc2f6ca61e515ce08d31d38e0a87","mediaType":"application\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm","os":"linux","variant":"v7"},"size":943},{"digest":"sha256:65cd340c0735f062e84507c3c2298502b5446037cc282bc1f3ac720ef42fb137","mediaType":"application\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm64","os":"linux","variant":"v8"},"size":943},{"digest":"sha256:ad426b7dd24d6fa923c1f46dce9a747082d0ef306716aefdc959e4cd23ffd22b","mediaType":"application\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"ppc64le","os":"linux"},"size":943},{"digest":"sha256:c534dfe09e599c2f32d286b1222df26bf4d943ae5a434c54c07dbe63a3a5f457","mediaType":"application\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"s390x","os":"linux"},"size":943}],"mediaType":"application\/vnd.docker.distribution.manifest.list.v2+json","schemaVersion":2}
2020/11/07 11:38:02 --> GET https://index.docker.io/v2/library/ubuntu/manifests/sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956
2020/11/07 11:38:02 GET /v2/library/ubuntu/manifests/sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956 HTTP/1.1
Host: index.docker.io
User-Agent: go-containerregistry
Accept: application/vnd.docker.distribution.manifest.v2+json
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:38:02 <-- 200 https://index.docker.io/v2/library/ubuntu/manifests/sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956 (146.998232ms)
2020/11/07 11:38:02 HTTP/1.1 200 OK
Content-Length: 943
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Date: Sat, 07 Nov 2020 19:38:02 GMT
Docker-Content-Digest: sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956"
Strict-Transport-Security: max-age=31536000

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 3352,
      "digest": "sha256:d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 28558714,
         "digest": "sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 847,
         "digest": "sha256:ba13d3bc422b493440f97a8f148d245e1999cb616cb05876edc3ef29e79852f2"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 162,
         "digest": "sha256:a254829d9e55168306fd80a49e02eb015551facee9c444d9dce3b26d19238b82"
      }
   ]
}
2020/11/07 11:38:02 --> GET https://index.docker.io/v2/library/ubuntu/blobs/sha256:d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1
2020/11/07 11:38:02 GET /v2/library/ubuntu/blobs/sha256:d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1 HTTP/1.1
Host: index.docker.io
User-Agent: go-containerregistry
Authorization: <redacted>
Accept-Encoding: gzip


2020/11/07 11:38:03 <-- 307 https://index.docker.io/v2/library/ubuntu/blobs/sha256:d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1 (98.773373ms)
2020/11/07 11:38:03 HTTP/1.1 307 Temporary Redirect
Content-Type: application/octet-stream
Date: Sat, 07 Nov 2020 19:38:03 GMT
Docker-Distribution-Api-Version: registry/2.0
Location: https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/d7/d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1/data?verify=1604780883-qCMAezARAKLJspjPZOu7QeBcdVU%3D
Strict-Transport-Security: max-age=31536000
Content-Length: 0


2020/11/07 11:38:03 --> GET https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/d7/d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1/data?verify=1604780883-qCMAezARAKLJspjPZOu7QeBcdVU%3D
2020/11/07 11:38:03 GET /registry-v2/docker/registry/v2/blobs/sha256/d7/d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1/data?verify=1604780883-qCMAezARAKLJspjPZOu7QeBcdVU%3D HTTP/1.1
Host: production.cloudflare.docker.com
User-Agent: go-containerregistry
Authorization: <redacted>
Referer: https://index.docker.io/v2/library/ubuntu/blobs/sha256:d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1
Accept-Encoding: gzip


2020/11/07 11:38:03 <-- 200 https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/d7/d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1/data?verify=1604780883-qCMAezARAKLJspjPZOu7QeBcdVU%3D (104.349078ms)
2020/11/07 11:38:03 HTTP/2.0 200 OK
Content-Length: 3352
Accept-Ranges: bytes
Age: 286646
Cache-Control: public, max-age=14400
Cf-Cache-Status: HIT
Cf-Ray: 5ee98329dfa6146e-SEA
Cf-Request-Id: 0645d04e270000146ee5187000000001
Content-Type: application/octet-stream
Date: Sat, 07 Nov 2020 19:38:03 GMT
Etag: "fe268949a0f7d74e2e13805e03f49cbb"
Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Expires: Sat, 07 Nov 2020 23:38:03 GMT
Last-Modified: Fri, 23 Oct 2020 17:33:30 GMT
Server: cloudflare
Set-Cookie: __cfduid=de468f60f28083d52dc85d256dd33ea661604777883; expires=Mon, 07-Dec-20 19:38:03 GMT; path=/; domain=.production.cloudflare.docker.com; HttpOnly; SameSite=Lax; Secure
Vary: Accept-Encoding
X-Amz-Id-2: ZoeBd9VAPCmM51HylyTpxLqLl5IbPfYN/qPf8z62OH6BNktn/r20/ZYBOUFZAhfGdaBV/rNit9c=
X-Amz-Request-Id: FF329A71108D1856
X-Amz-Version-Id: Cz_.hhn0XL9BNrmNuiFt1A_P.X0TidMU

{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"],"ArgsEscaped":true,"Image":"sha256:8d5c6d6340d4365a65dc5becb6ecb055dcf505527c94d2dfa167f9b99a48a985","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"917be43da4a1ceb52416d44076c5f99b0d20ceca3f5f9a2918a020e05ec3616a","container_config":{"Hostname":"917be43da4a1","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/bin/bash\"]"],"ArgsEscaped":true,"Image":"sha256:8d5c6d6340d4365a65dc5becb6ecb055dcf505527c94d2dfa167f9b99a48a985","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2020-10-23T17:32:36.438324921Z","docker_version":"18.09.7","history":[{"created":"2020-10-23T17:32:33.883592185Z","created_by":"/bin/sh -c #(nop) ADD file:435d9776fdd3a1834f344fb82e459dbbb67cd50c71ab5e29b719273888d5bb7c in / "},{"created":"2020-10-23T17:32:34.71810844Z","created_by":"/bin/sh -c set -xe \t\t\u0026\u0026 echo '#!/bin/sh' \u003e /usr/sbin/policy-rc.d \t\u0026\u0026 echo 'exit 101' \u003e\u003e /usr/sbin/policy-rc.d \t\u0026\u0026 chmod +x /usr/sbin/policy-rc.d \t\t\u0026\u0026 dpkg-divert --local --rename --add /sbin/initctl \t\u0026\u0026 cp -a /usr/sbin/policy-rc.d /sbin/initctl \t\u0026\u0026 sed -i 's/^exit.*/exit 0/' /sbin/initctl \t\t\u0026\u0026 echo 'force-unsafe-io' \u003e /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \t\t\u0026\u0026 echo 'DPkg::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' \u003e /etc/apt/apt.conf.d/docker-clean \t\u0026\u0026 echo 'APT::Update::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' \u003e\u003e /etc/apt/apt.conf.d/docker-clean \t\u0026\u0026 echo 'Dir::Cache::pkgcache \"\"; Dir::Cache::srcpkgcache \"\";' \u003e\u003e /etc/apt/apt.conf.d/docker-clean \t\t\u0026\u0026 echo 'Acquire::Languages \"none\";' \u003e /etc/apt/apt.conf.d/docker-no-languages \t\t\u0026\u0026 echo 'Acquire::GzipIndexes \"true\"; Acquire::CompressionTypes::Order:: \"gz\";' \u003e /etc/apt/apt.conf.d/docker-gzip-indexes \t\t\u0026\u0026 echo 'Apt::AutoRemove::SuggestsImportant \"false\";' \u003e /etc/apt/apt.conf.d/docker-autoremove-suggests"},{"created":"2020-10-23T17:32:35.470778446Z","created_by":"/bin/sh -c [ -z \"$(apt-get indextargets)\" ]","empty_layer":true},{"created":"2020-10-23T17:32:36.258836592Z","created_by":"/bin/sh -c mkdir -p /run/systemd \u0026\u0026 echo 'docker' \u003e /run/systemd/container"},{"created":"2020-10-23T17:32:36.438324921Z","created_by":"/bin/sh -c #(nop)  CMD [\"/bin/bash\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:47dde53750b4a8ed24acebe52cf31ad131e73a9611048fc2f92c9b6274ab4bf3","sha256:0c2689e3f9206b1c4adfb16a1976d25bd270755e734588409b31ef29e3e756d6","sha256:cc9d18e90faad04bc3893cfaa50b7846ee75f48f5b8377a213fa52af2189095c"]}}
"2020-10-23T17:32:36.438324921Z"
```
jonjohnsonjr: cc @mattmoor this also includes timing info from the transport, which you may find useful

Patch:
diff --git a/pkg/authn/keychain.go b/pkg/authn/keychain.go
index 7bafc2fa..60eebc75 100644
--- a/pkg/authn/keychain.go
+++ b/pkg/authn/keychain.go
@@ -15,12 +15,10 @@
 package authn
 
 import (
-	"encoding/json"
 	"os"
 
 	"github.com/docker/cli/cli/config"
 	"github.com/docker/cli/cli/config/types"
-	"github.com/google/go-containerregistry/pkg/logs"
 	"github.com/google/go-containerregistry/pkg/name"
 )
 
@@ -76,12 +74,6 @@ func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
 	if err != nil {
 		return nil, err
 	}
-	if logs.Enabled(logs.Debug) {
-		b, err := json.Marshal(cfg)
-		if err == nil {
-			logs.Debug.Printf("defaultKeychain.Resolve(%q) = %s", key, string(b))
-		}
-	}
 
 	empty := types.AuthConfig{}
 	if cfg == empty {
diff --git a/pkg/internal/redact/redact.go b/pkg/internal/redact/redact.go
new file mode 100644
index 00000000..dc9c56b7
--- /dev/null
+++ b/pkg/internal/redact/redact.go
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package redact contains a simple context signal for redacting requests.
+package redact
+
+import (
+	"context"
+)
+
+type contextKey string
+
+var redactKey = contextKey("redact")
+
+// NewContext creates a new ctx with the reason for redaction.
+func NewContext(ctx context.Context, reason string) context.Context {
+	return context.WithValue(ctx, redactKey, reason)
+}
+
+// FromContext returns the redaction reason, if any.
+func FromContext(ctx context.Context) (bool, string) {
+	reason, ok := ctx.Value(redactKey).(string)
+	return ok, reason
+}
diff --git a/pkg/v1/google/auth.go b/pkg/v1/google/auth.go
index 58c9989b..15b74e98 100644
--- a/pkg/v1/google/auth.go
+++ b/pkg/v1/google/auth.go
@@ -120,13 +120,6 @@ func (tsa *tokenSourceAuth) Authorization() (*authn.AuthConfig, error) {
 		return nil, err
 	}
 
-	if logs.Enabled(logs.Debug) {
-		b, err := json.Marshal(token)
-		if err == nil {
-			logs.Debug.Printf("google.Keychain: %s", string(b))
-		}
-	}
-
 	return &authn.AuthConfig{
 		Username: "_token",
 		Password: token.AccessToken,
diff --git a/pkg/v1/remote/descriptor.go b/pkg/v1/remote/descriptor.go
index 7214c893..41da9f86 100644
--- a/pkg/v1/remote/descriptor.go
+++ b/pkg/v1/remote/descriptor.go
@@ -348,14 +348,14 @@ func (f *fetcher) headManifest(ref name.Reference, acceptable []types.MediaType)
 	}, nil
 }
 
-func (f *fetcher) fetchBlob(h v1.Hash) (io.ReadCloser, error) {
+func (f *fetcher) fetchBlob(ctx context.Context, h v1.Hash) (io.ReadCloser, error) {
 	u := f.url("blobs", h.String())
 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
 	if err != nil {
 		return nil, err
 	}
 
-	resp, err := f.Client.Do(req.WithContext(f.context))
+	resp, err := f.Client.Do(req.WithContext(ctx))
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/v1/remote/image.go b/pkg/v1/remote/image.go
index 30ee0125..84634f72 100644
--- a/pkg/v1/remote/image.go
+++ b/pkg/v1/remote/image.go
@@ -99,7 +99,7 @@ func (r *remoteImage) RawConfigFile() ([]byte, error) {
 		return nil, err
 	}
 
-	body, err := r.fetchBlob(m.Config.Digest)
+	body, err := r.fetchBlob(r.context, m.Config.Digest)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/v1/remote/layer.go b/pkg/v1/remote/layer.go
index 1e1e331c..6a941a1a 100644
--- a/pkg/v1/remote/layer.go
+++ b/pkg/v1/remote/layer.go
@@ -17,6 +17,7 @@ package remote
 import (
 	"io"
 
+	"github.com/google/go-containerregistry/pkg/internal/redact"
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/partial"
@@ -31,7 +32,9 @@ type remoteLayer struct {
 
 // Compressed implements partial.CompressedLayer
 func (rl *remoteLayer) Compressed() (io.ReadCloser, error) {
-	return rl.fetchBlob(rl.digest)
+	// We don't want to log binary layers -- this can break terminals.
+	ctx := redact.NewContext(rl.context, "omitting binary blobs from logs")
+	return rl.fetchBlob(ctx, rl.digest)
 }
 
 // Compressed implements partial.CompressedLayer
diff --git a/pkg/v1/remote/multi_write.go b/pkg/v1/remote/multi_write.go
index 259348d2..631ab23f 100644
--- a/pkg/v1/remote/multi_write.go
+++ b/pkg/v1/remote/multi_write.go
@@ -127,7 +127,7 @@ func MultiWrite(m map[name.Reference]Taggable, options ...Option) error {
 			// Start N workers consuming tasks to upload manifests.
 			g.Go(func() error {
 				for t := range taskChan {
-					if err := w.commitImage(t.i, t.ref); err != nil {
+					if err := w.commitManifest(t.i, t.ref); err != nil {
 						return err
 					}
 				}
diff --git a/pkg/v1/remote/transport/bearer.go b/pkg/v1/remote/transport/bearer.go
index 9a874d9a..e851ec59 100644
--- a/pkg/v1/remote/transport/bearer.go
+++ b/pkg/v1/remote/transport/bearer.go
@@ -24,6 +24,7 @@ import (
 	"strings"
 
 	"github.com/google/go-containerregistry/pkg/authn"
+	"github.com/google/go-containerregistry/pkg/internal/redact"
 	"github.com/google/go-containerregistry/pkg/name"
 )
 
@@ -212,7 +213,16 @@ func (bt *bearerTransport) refreshOauth() ([]byte, error) {
 	}
 
 	client := http.Client{Transport: bt.inner}
-	resp, err := client.PostForm(u.String(), v)
+	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(v.Encode()))
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	// We don't want to log credentials.
+	ctx := redact.NewContext(req.Context(), "oauth token response contains credentials")
+
+	resp, err := client.Do(req.WithContext(ctx))
 	if err != nil {
 		return nil, err
 	}
@@ -243,7 +253,15 @@ func (bt *bearerTransport) refreshBasic() ([]byte, error) {
 	v.Set("service", bt.service)
 	u.RawQuery = v.Encode()
 
-	resp, err := client.Get(u.String())
+	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// We don't want to log credentials.
+	ctx := redact.NewContext(req.Context(), "basic token response contains credentials")
+
+	resp, err := client.Do(req.WithContext(ctx))
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/v1/remote/transport/error.go b/pkg/v1/remote/transport/error.go
index 0d662496..7592590e 100644
--- a/pkg/v1/remote/transport/error.go
+++ b/pkg/v1/remote/transport/error.go
@@ -59,7 +59,7 @@ var _ error = (*Error)(nil)
 func (e *Error) Error() string {
 	prefix := ""
 	if e.request != nil {
-		prefix = fmt.Sprintf("%s %s: ", e.request.Method, redact(e.request.URL))
+		prefix = fmt.Sprintf("%s %s: ", e.request.Method, redactURL(e.request.URL))
 	}
 	return prefix + e.responseErr()
 }
@@ -96,7 +96,8 @@ func (e *Error) Temporary() bool {
 	return true
 }
 
-func redact(original *url.URL) *url.URL {
+// TODO(jonjohnsonjr): Consider moving to pkg/internal/redact.
+func redactURL(original *url.URL) *url.URL {
 	qs := original.Query()
 	for k, v := range qs {
 		for i := range v {
diff --git a/pkg/v1/remote/transport/logger.go b/pkg/v1/remote/transport/logger.go
index 8fcad6f2..3d0a3865 100644
--- a/pkg/v1/remote/transport/logger.go
+++ b/pkg/v1/remote/transport/logger.go
@@ -1,10 +1,26 @@
+// Copyright 2020 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package transport
 
 import (
 	"fmt"
 	"net/http"
 	"net/http/httputil"
+	"time"
 
+	"github.com/google/go-containerregistry/pkg/internal/redact"
 	"github.com/google/go-containerregistry/pkg/logs"
 )
 
@@ -20,24 +36,56 @@ func NewLogger(inner http.RoundTripper) http.RoundTripper {
 
 func (t *logTransport) RoundTrip(in *http.Request) (out *http.Response, err error) {
 	// Inspired by: github.com/motemen/go-loghttp
-	logs.Debug.Printf("--> %s %s", in.Method, in.URL)
-	b, err := httputil.DumpRequestOut(in, true)
+	msg := fmt.Sprintf("--> %s %s", in.Method, in.URL)
+
+	// We redact token responses and binary blobs in response/request.
+	omitBody, reason := redact.FromContext(in.Context())
+	if omitBody {
+		msg = fmt.Sprintf("%s [body redacted: %s]", msg, reason)
+	}
+
+	logs.Debug.Printf(msg)
+
+	// Save these headers so we can redact Authorization.
+	savedHeaders := in.Header.Clone()
+	if in.Header != nil {
+		in.Header.Set("authorization", "<redacted>")
+	}
+
+	b, err := httputil.DumpRequestOut(in, !omitBody)
 	if err == nil {
 		logs.Debug.Println(string(b))
+	} else {
+		logs.Debug.Printf("Failed to dump request %s %s: %v", in.Method, in.URL, err)
 	}
+
+	// Restore the non-redacted headers.
+	in.Header = savedHeaders
+
+	start := time.Now()
 	out, err = t.inner.RoundTrip(in)
+	duration := time.Since(start)
 	if err != nil {
-		logs.Debug.Printf("<-- %v %s", err, in.URL)
+		logs.Debug.Printf("<-- %v %s %s (%s)", err, in.Method, in.URL, duration)
 	}
 	if out != nil {
 		msg := fmt.Sprintf("<-- %d", out.StatusCode)
 		if out.Request != nil {
 			msg = fmt.Sprintf("%s %s", msg, out.Request.URL)
 		}
+		msg = fmt.Sprintf("%s (%s)", msg, duration)
+
+		if omitBody {
+			msg = fmt.Sprintf("%s [body redacted: %s]", msg, reason)
+		}
+
 		logs.Debug.Print(msg)
-		b, err := httputil.DumpResponse(out, true)
+
+		b, err := httputil.DumpResponse(out, !omitBody)
 		if err == nil {
 			logs.Debug.Println(string(b))
+		} else {
+			logs.Debug.Printf("Failed to dump response %s %s: %v", in.Method, in.URL, err)
 		}
 	}
 	return
diff --git a/pkg/v1/remote/write.go b/pkg/v1/remote/write.go
index 3115e550..0aa7a0e4 100644
--- a/pkg/v1/remote/write.go
+++ b/pkg/v1/remote/write.go
@@ -22,8 +22,10 @@ import (
 	"io"
 	"net/http"
 	"net/url"
+	"strings"
 	"time"
 
+	"github.com/google/go-containerregistry/pkg/internal/redact"
 	"github.com/google/go-containerregistry/pkg/internal/retry"
 	"github.com/google/go-containerregistry/pkg/logs"
 	"github.com/google/go-containerregistry/pkg/name"
@@ -128,7 +130,7 @@ func Write(ref name.Reference, img v1.Image, options ...Option) error {
 
 	// With all of the constituent elements uploaded, upload the manifest
 	// to commit the image.
-	return w.commitImage(img, ref)
+	return w.commitManifest(img, ref)
 }
 
 // writer writes the elements of an image to a remote image reference.
@@ -261,13 +263,13 @@ func (w *writer) initiateUpload(from, mount string) (location string, mounted bo
 // streamBlob streams the contents of the blob to the specified location.
 // On failure, this will return an error.  On success, this will return the location
 // header indicating how to commit the streamed blob.
-func (w *writer) streamBlob(blob io.ReadCloser, streamLocation string) (commitLocation string, err error) {
+func (w *writer) streamBlob(ctx context.Context, blob io.ReadCloser, streamLocation string) (commitLocation string, err error) {
 	req, err := http.NewRequest(http.MethodPatch, streamLocation, blob)
 	if err != nil {
 		return "", err
 	}
 
-	resp, err := w.client.Do(req.WithContext(w.context))
+	resp, err := w.client.Do(req.WithContext(ctx))
 	if err != nil {
 		return "", err
 	}
@@ -330,6 +332,8 @@ func (w *writer) uploadOne(l v1.Layer) error {
 		}
 	}
 
+	ctx := w.context
+
 	tryUpload := func() error {
 		location, mounted, err := w.initiateUpload(from, mount)
 		if err != nil {
@@ -343,11 +347,22 @@ func (w *writer) uploadOne(l v1.Layer) error {
 			return nil
 		}
 
+		// Only log layers with +json or +yaml. We can let through other stuff if it becomes popular.
+		// TODO(opencontainers/image-spec#791): Would be great to have an actual parser.
+		mt, err := l.MediaType()
+		if err != nil {
+			return err
+		}
+		smt := string(mt)
+		if !(strings.HasSuffix(smt, "+json") || strings.HasSuffix(smt, "+yaml")) {
+			ctx = redact.NewContext(ctx, "omitting binary blobs from logs")
+		}
+
 		blob, err := l.Compressed()
 		if err != nil {
 			return err
 		}
-		location, err = w.streamBlob(blob, location)
+		location, err = w.streamBlob(ctx, blob, location)
 		if err != nil {
 			return err
 		}
@@ -376,6 +391,53 @@ func (w *writer) uploadOne(l v1.Layer) error {
 	return retry.Retry(tryUpload, retry.IsTemporary, backoff)
 }
 
+func (w *writer) writeIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error {
+	index, err := ii.IndexManifest()
+	if err != nil {
+		return err
+	}
+
+	// TODO(#803): Pipe through remote.WithJobs and upload these in parallel.
+	for _, desc := range index.Manifests {
+		ref := ref.Context().Digest(desc.Digest.String())
+		exists, err := w.checkExistingManifest(desc.Digest, desc.MediaType)
+		if err != nil {
+			return err
+		}
+		if exists {
+			logs.Progress.Print("existing manifest: ", desc.Digest)
+			continue
+		}
+
+		switch desc.MediaType {
+		case types.OCIImageIndex, types.DockerManifestList:
+			ii, err := ii.ImageIndex(desc.Digest)
+			if err != nil {
+				return err
+			}
+
+			if err := w.writeIndex(ref, ii); err != nil {
+				return err
+			}
+		case types.OCIManifestSchema1, types.DockerManifestSchema2:
+			img, err := ii.Image(desc.Digest)
+			if err != nil {
+				return err
+			}
+			// TODO: Ideally we could reuse this writer, but we need to know
+			// scopes before we do the token exchange. To be lazy here, just
+			// re-do the token exchange. MultiWrite fixes this.
+			if err := Write(ref, img, options...); err != nil {
+				return err
+			}
+		}
+	}
+
+	// With all of the constituent elements uploaded, upload the manifest
+	// to commit the image.
+	return w.commitManifest(ii, ref)
+}
+
 type withMediaType interface {
 	MediaType() (types.MediaType, error)
 }
@@ -418,8 +480,8 @@ func unpackTaggable(t Taggable) (*v1.Descriptor, error) {
 	}, nil
 }
 
-// commitImage does a PUT of the image's manifest.
-func (w *writer) commitImage(t Taggable, ref name.Reference) error {
+// commitManifest does a PUT of the image's manifest.
+func (w *writer) commitManifest(t Taggable, ref name.Reference) error {
 	raw, err := t.RawManifest()
 	if err != nil {
 		return err
@@ -482,11 +544,6 @@ func scopesForUploadingImage(repo name.Repository, layers []v1.Layer) []string {
 // WriteIndex will attempt to push all of the referenced manifests before
 // attempting to push the ImageIndex, to retain referential integrity.
 func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error {
-	index, err := ii.IndexManifest()
-	if err != nil {
-		return err
-	}
-
 	o, err := makeOptions(ref.Context(), options...)
 	if err != nil {
 		return err
@@ -501,43 +558,7 @@ func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error {
 		client:  &http.Client{Transport: tr},
 		context: o.context,
 	}
-
-	// TODO(#803): Pipe through remote.WithJobs and upload these in parallel.
-	for _, desc := range index.Manifests {
-		ref := ref.Context().Digest(desc.Digest.String())
-		exists, err := w.checkExistingManifest(desc.Digest, desc.MediaType)
-		if err != nil {
-			return err
-		}
-		if exists {
-			logs.Progress.Printf("existing manifest: %v", desc.Digest)
-			continue
-		}
-
-		switch desc.MediaType {
-		case types.OCIImageIndex, types.DockerManifestList:
-			ii, err := ii.ImageIndex(desc.Digest)
-			if err != nil {
-				return err
-			}
-
-			if err := WriteIndex(ref, ii, WithAuth(o.auth), WithTransport(o.transport)); err != nil {
-				return err
-			}
-		case types.OCIManifestSchema1, types.DockerManifestSchema2:
-			img, err := ii.Image(desc.Digest)
-			if err != nil {
-				return err
-			}
-			if err := Write(ref, img, WithAuth(o.auth), WithTransport(o.transport)); err != nil {
-				return err
-			}
-		}
-	}
-
-	// With all of the constituent elements uploaded, upload the manifest
-	// to commit the image.
-	return w.commitImage(ii, ref)
+	return w.writeIndex(ref, ii, options...)
 }
 
 // WriteLayer uploads the provided Layer to the specified repo.
@@ -573,7 +594,7 @@ func Tag(tag name.Tag, t Taggable, options ...Option) error {
 	// * Tag could take a list of tags.
 	// * Allow callers to pass in a transport.Transport, typecheck
 	//   it to allow them to reuse the transport across multiple calls.
-	// * WithTag option to do multiple manifest PUTs in commitImage.
+	// * WithTag option to do multiple manifest PUTs in commitManifest.
 	tr, err := transport.New(tag.Context().Registry, o.auth, o.transport, scopes)
 	if err != nil {
 		return err
@@ -584,5 +605,5 @@ func Tag(tag name.Tag, t Taggable, options ...Option) error {
 		context: o.context,
 	}
 
-	return w.commitImage(t, tag)
+	return w.commitManifest(t, tag)
 }


Test patch:
diff --git a/pkg/authn/keychain_test.go b/pkg/authn/keychain_test.go
index 646c8ab5..a0329fd5 100644
--- a/pkg/authn/keychain_test.go
+++ b/pkg/authn/keychain_test.go
@@ -15,7 +15,6 @@
 package authn
 
 import (
-	"bytes"
 	"encoding/base64"
 	"fmt"
 	"io/ioutil"
@@ -24,7 +23,6 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/google/go-containerregistry/pkg/logs"
 	"github.com/google/go-containerregistry/pkg/name"
 )
 
@@ -114,22 +112,13 @@ func TestVariousPaths(t *testing.T) {
 		},
 	}}
 
-	var b bytes.Buffer
-	logs.Debug.SetOutput(&b)
-
 	for _, test := range tests {
 		cd := setupConfigFile(t, test.content)
 		// For some reason, these tempdirs don't get cleaned up.
 		defer os.RemoveAll(filepath.Dir(cd))
 
-		// Clear debug logs.
-		b.Reset()
-
 		auth, err := DefaultKeychain.Resolve(test.target)
 		if test.wantErr {
-			if b.Len() != 0 {
-				t.Errorf("didn't expect any logs, got: %v", b.String())
-			}
 			if err == nil {
 				t.Fatal("wanted err, got nil")
 			} else if err != nil {
@@ -148,8 +137,5 @@ func TestVariousPaths(t *testing.T) {
 		if !reflect.DeepEqual(cfg, test.cfg) {
 			t.Errorf("got %+v, want %+v", cfg, test.cfg)
 		}
-		if b.Len() == 0 {
-			t.Errorf("wanted logs, got nothing")
-		}
 	}
 }
diff --git a/pkg/v1/remote/transport/logger_test.go b/pkg/v1/remote/transport/logger_test.go
index e92930b9..98be15fd 100644
--- a/pkg/v1/remote/transport/logger_test.go
+++ b/pkg/v1/remote/transport/logger_test.go
@@ -16,29 +16,41 @@ package transport
 
 import (
 	"bytes"
+	"context"
 	"errors"
 	"io/ioutil"
 	"net/http"
 	"strings"
 	"testing"
 
+	"github.com/google/go-containerregistry/pkg/internal/redact"
 	"github.com/google/go-containerregistry/pkg/logs"
 )
 
 func TestLogger(t *testing.T) {
 	canary := "logs.Debug canary"
-	req, err := http.NewRequest("GET", "http://example.com", nil)
+	secret := "super secret do not log"
+	auth := "my token pls do not log"
+	reason := "should not log the secret"
+
+	ctx := redact.NewContext(context.Background(), reason)
+
+	req, err := http.NewRequestWithContext(ctx, "GET", "http://example.com", nil)
 	if err != nil {
 		t.Fatalf("Unexpected error during NewRequest: %v", err)
 	}
+	req.Header.Set("authorization", auth)
 
 	var b bytes.Buffer
 	logs.Debug.SetOutput(&b)
 	cannedResponse := http.Response{
 		Status:     http.StatusText(http.StatusOK),
 		StatusCode: http.StatusOK,
-		Body:       ioutil.NopCloser(strings.NewReader(canary)),
-		Request:    req,
+		Header: http.Header{
+			"Foo": []string{canary},
+		},
+		Body:    ioutil.NopCloser(strings.NewReader(secret)),
+		Request: req,
 	}
 	tr := NewLogger(newRecorder(&cannedResponse, nil))
 	if _, err := tr.RoundTrip(req); err != nil {
@@ -49,6 +61,15 @@ func TestLogger(t *testing.T) {
 	if !strings.Contains(logged, canary) {
 		t.Errorf("Expected logs to contain %s, got %s", canary, logged)
 	}
+	if !strings.Contains(logged, reason) {
+		t.Errorf("Expected logs to contain %s, got %s", canary, logged)
+	}
+	if strings.Contains(logged, secret) {
+		t.Errorf("Expected logs NOT to contain %s, got %s", secret, logged)
+	}
+	if strings.Contains(logged, auth) {
+		t.Errorf("Expected logs NOT to contain %s, got %s", auth, logged)
+	}
 }
 
 func TestLoggerError(t *testing.T) {
diff --git a/pkg/v1/remote/write_test.go b/pkg/v1/remote/write_test.go
index 12f0f8a9..a79d2ebb 100644
--- a/pkg/v1/remote/write_test.go
+++ b/pkg/v1/remote/write_test.go
@@ -546,7 +546,7 @@ func TestStreamBlob(t *testing.T) {
 		t.Fatalf("layer.Compressed: %v", err)
 	}
 
-	commitLocation, err := w.streamBlob(blob, streamLocation.String())
+	commitLocation, err := w.streamBlob(context.Background(), blob, streamLocation.String())
 	if err != nil {
 		t.Errorf("streamBlob() = %v", err)
 	}
@@ -598,7 +598,7 @@ func TestStreamLayer(t *testing.T) {
 		t.Fatalf("layer.Compressed: %v", err)
 	}
 
-	commitLocation, err := w.streamBlob(blob, streamLocation.String())
+	commitLocation, err := w.streamBlob(context.Background(), blob, streamLocation.String())
 	if err != nil {
 		t.Errorf("streamBlob: %v", err)
 	}
@@ -813,8 +813,8 @@ func TestCommitImage(t *testing.T) {
 	}
 	defer closer.Close()
 
-	if err := w.commitImage(img, w.repo.Tag("latest")); err != nil {
-		t.Errorf("commitImage() = %v", err)
+	if err := w.commitManifest(img, w.repo.Tag("latest")); err != nil {
+		t.Error("commitManifest() = ", err)
 	}
 }