Skip to content

feat: log registry store operations - #2901

Open
charleswool wants to merge 1 commit into
notaryproject:mainfrom
charleswool:feat/registrystore-logging
Open

feat: log registry store operations#2901
charleswool wants to merge 1 commit into
notaryproject:mainfrom
charleswool:feat/registrystore-logging

Conversation

@charleswool

Copy link
Copy Markdown
Contributor

Description

Closes the last in-repo logging blind spot on the verification path: every registry round trip is currently invisible.

internal/store/registrystore only builds a ratify-go RegistryStore, and that library is deliberately dependency-free — [COMPUTED] it has zero log calls in the entire module, which is reasonable for a library. The consequence is that a slow ListReferrers, a manifest fetch that 404s, or a blob pull that times out leaves no trace on the server at all. The operator sees only the final "verification failed" with no idea which registry call was responsible.

Note

In #2877 I claimed this "cannot be added from this repo" and would need an upstream issue. That was wrong, and I've posted a correction there. ratify.Store is an interface, and our factory already returns it, so the seam is local. No upstream change is needed.

Change

ratify.Store has four methods, and internal/store/registrystore already returns that interface from its factory:

type Store interface {
	Resolve(ctx, ref) (ocispec.Descriptor, error)
	ListReferrers(ctx, ref, artifactTypes, fn) error
	FetchBlob(ctx, repo, desc) ([]byte, error)
	FetchManifest(ctx, repo, desc) ([]byte, error)
}

So the store is wrapped in a decorator rather than changing the dependency:

return &loggingStore{inner: ratify.NewRegistryStore(registryStoreOpts)}, nil

Each operation now logs:

  • debug on success — the resolved digest, the referrer count, the blob/manifest digest with media type and size, and the duration in ms.
  • error on failure — the reference or digest, the duration, and the error.

ListReferrers paginates, so the referrer count is accumulated across every callback rather than taken from the first page.

Logging goes through internal/logger, so entries carry the request trace ID and component-type=referrerStore. That correlation is the main reason this belongs here rather than upstream: ratify-go has no concept of the request context Ratify attaches.

Example output that previously did not exist at all:

level=debug msg="resolved registry.example/app:v1 to sha256:abc… in 42ms" component-type=referrerStore trace-id=0d9a6926-…
level=debug msg="listed 3 referrer(s) for registry.example/app:v1 in 88ms" component-type=referrerStore trace-id=0d9a6926-…
level=debug msg="fetched blob sha256:def… (application/vnd.cncf.notary.signature, 2048 bytes) from registry.example/app in 15ms" component-type=referrerStore trace-id=0d9a6926-…
level=error msg="failed to fetch manifest sha256:123… from registry.example/app after 5001ms: context deadline exceeded" component-type=referrerStore trace-id=0d9a6926-…

No credentials, tokens or blob contents are logged — only references, digests, media types, sizes and durations.

Testing

  • New logging_test.go drives every method through a stub ratify.Store on both the success and failure paths. internal/store/registrystore is at 100% statement coverage.
  • TestLoggingStore_ListReferrersCountsAllPages specifically covers the pagination accumulation, and asserts the wrapped callback still forwards every page to the caller.
  • Log assertions match on the test's own message via a findEntry helper rather than on log level alone, so a shared hook cannot make them pass spuriously.
  • go build ./..., go vet, package tests and golangci-lint pass. No go.mod change.

Additive logging only; the decorator returns the inner store's values and errors unchanged.

Enable with --set logger.level=debug (#2846).

internal/store/registrystore only builds a ratify-go RegistryStore, and that
library is deliberately dependency-free and does not log, so every registry
round trip the executor makes is invisible: a slow ListReferrers, a manifest
fetch that 404s or a blob pull that times out leaves no trace on the server.

ratify.Store is an interface and the factory already returns it, so wrap the
constructed store in a decorator rather than changing the dependency. Each
operation now logs at debug on success with the digest, size and duration,
and at error on failure. The referrer count is accumulated across pagination
callbacks so it reflects the whole listing.

Logging goes through internal/logger, so entries carry the request trace ID
and component-type=referrerStore. That correlation is the reason this belongs
here rather than upstream.

Signed-off-by: Charles Wu <yuewu2@microsoft.com>
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.31%. Comparing base (5586aba) to head (27a65e9).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2901      +/-   ##
==========================================
+ Coverage   77.10%   77.31%   +0.21%     
==========================================
  Files          90       91       +1     
  Lines        4315     4355      +40     
==========================================
+ Hits         3327     3367      +40     
  Misses        831      831              
  Partials      157      157              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a ratify.Store decorator in internal/store/registrystore so every registry round-trip (resolve, list referrers, fetch blob, fetch manifest) is logged through internal/logger, improving observability on the verification path without changing the upstream ratify-go dependency.

Changes:

  • Wrap the ratify-go RegistryStore with a loggingStore decorator returned from the registrystore factory.
  • Log success (debug) and failure (error) for Resolve, ListReferrers, FetchBlob, and FetchManifest, including durations and basic identifiers.
  • Add unit tests covering success/failure paths and pagination referrer counting.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
internal/store/registrystore/register.go Wraps the created RegistryStore with a logging decorator.
internal/store/registrystore/logging.go Introduces the loggingStore decorator that logs each store operation.
internal/store/registrystore/logging_test.go Adds tests validating emitted log messages and pagination referrer counting.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +50 to +60
start := time.Now()
// ListReferrers paginates, so the total is only known once fn stops being called.
count := 0
err := s.inner.ListReferrers(ctx, ref, artifactTypes, func(referrers []ocispec.Descriptor) error {
count += len(referrers)
return fn(referrers)
})
if err != nil {
log.Errorf("failed to list referrers for %s after %dms: %v", ref, time.Since(start).Milliseconds(), err)
return err
}
Comment on lines +69 to +72
if err != nil {
log.Errorf("failed to fetch blob %s from %s after %dms: %v", desc.Digest, repo, time.Since(start).Milliseconds(), err)
return nil, err
}
Comment on lines +81 to +84
if err != nil {
log.Errorf("failed to fetch manifest %s from %s after %dms: %v", desc.Digest, repo, time.Since(start).Milliseconds(), err)
return nil, err
}
Comment on lines +72 to +83
func newHook(t *testing.T) *test.Hook {
t.Helper()
base := logrus.StandardLogger()
previous := base.GetLevel()
base.SetLevel(logrus.DebugLevel)
hook := test.NewLocal(base)
t.Cleanup(func() {
base.SetLevel(previous)
hook.Reset()
})
return hook
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants