internal/derrors,etc.: report errors lower in the stack

The data we get from the errorreporting API isn't too useful
because the call to Report happens high on the stack. Try
to call Report closer to where the error happens.

Change-Id: I101fb4de0892c5d9967f6eb46d7c9cbd72fb567e
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/278952
Trust: Jonathan Amsterdam <jba@google.com>
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/cmd/internal/cmdconfig/cmdconfig.go b/cmd/internal/cmdconfig/cmdconfig.go
index d1fe612..e9d915b 100644
--- a/cmd/internal/cmdconfig/cmdconfig.go
+++ b/cmd/internal/cmdconfig/cmdconfig.go
@@ -50,6 +50,7 @@
 	if err != nil {
 		log.Fatal(ctx, err)
 	}
+	derrors.SetReportingClient(reporter)
 	return reporter
 }
 
diff --git a/internal/database/logging.go b/internal/database/logging.go
index 5979dfa..a26c1b8 100644
--- a/internal/database/logging.go
+++ b/internal/database/logging.go
@@ -108,6 +108,8 @@
 				if errors.Is(ctx.Err(), context.Canceled) ||
 					strings.Contains(entry.Error, "pq: canceling statement due to user request") {
 					logf = log.Debug
+				} else {
+					derrors.Report(*errp)
 				}
 				logf(ctx, entry)
 			}
diff --git a/internal/derrors/derrors.go b/internal/derrors/derrors.go
index 4983381..bca742b 100644
--- a/internal/derrors/derrors.go
+++ b/internal/derrors/derrors.go
@@ -10,6 +10,8 @@
 	"errors"
 	"fmt"
 	"net/http"
+
+	"cloud.google.com/go/errorreporting"
 )
 
 //lint:file-ignore ST1012 prefixing error values with Err would stutter
@@ -223,3 +225,25 @@
 		*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
 	}
 }
+
+// WrapAndReport calls Wrap followed by Report.
+func WrapAndReport(errp *error, format string, args ...interface{}) {
+	Wrap(errp, format, args...)
+	if *errp != nil {
+		Report(*errp)
+	}
+}
+
+var repClient *errorreporting.Client
+
+// SetReportingClient sets an errorreporting client, for use by Report.
+func SetReportingClient(c *errorreporting.Client) {
+	repClient = c
+}
+
+// Report uses the errorreporting API to report an error.
+func Report(err error) {
+	if repClient != nil {
+		repClient.Report(errorreporting.Entry{Error: err})
+	}
+}
diff --git a/internal/frontend/readme.go b/internal/frontend/readme.go
index b2871e0..0338031 100644
--- a/internal/frontend/readme.go
+++ b/internal/frontend/readme.go
@@ -61,7 +61,7 @@
 //
 // This function is exported for use by external tools.
 func ProcessReadme(ctx context.Context, u *internal.Unit) (_ *Readme, err error) {
-	defer derrors.Wrap(&err, "ProcessReadme(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
+	defer derrors.WrapAndReport(&err, "ProcessReadme(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
 	return processReadme(u.Readme, u.SourceInfo)
 }
 
diff --git a/internal/frontend/unit.go b/internal/frontend/unit.go
index 9234caa..0728449 100644
--- a/internal/frontend/unit.go
+++ b/internal/frontend/unit.go
@@ -75,7 +75,7 @@
 // modules, documentation, readmes, licenses, and package_imports tables.
 func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *http.Request,
 	ds internal.DataSource, info *urlPathInfo) (err error) {
-	defer derrors.Wrap(&err, "serveUnitPage(ctx, w, r, ds, %v)", info)
+	defer derrors.WrapAndReport(&err, "serveUnitPage(ctx, w, r, ds, %v)", info)
 
 	tab := r.FormValue("tab")
 	if tab == "" {
diff --git a/internal/proxy/client.go b/internal/proxy/client.go
index 4c2e7a9..c9fea4b 100644
--- a/internal/proxy/client.go
+++ b/internal/proxy/client.go
@@ -70,7 +70,7 @@
 }
 
 func (c *Client) getInfo(ctx context.Context, modulePath, requestedVersion string, disableFetch bool) (_ *VersionInfo, err error) {
-	defer derrors.Wrap(&err, "proxy.Client.GetInfo(%q, %q)", modulePath, requestedVersion)
+	defer derrors.WrapAndReport(&err, "proxy.Client.GetInfo(%q, %q)", modulePath, requestedVersion)
 	data, err := c.readBody(ctx, modulePath, requestedVersion, "info", disableFetch)
 	if err != nil {
 		return nil, err
@@ -84,7 +84,7 @@
 
 // GetMod makes a request to $GOPROXY/<module>/@v/<resolvedVersion>.mod and returns the raw data.
 func (c *Client) GetMod(ctx context.Context, modulePath, resolvedVersion string) (_ []byte, err error) {
-	defer derrors.Wrap(&err, "proxy.Client.GetMod(%q, %q)", modulePath, resolvedVersion)
+	defer derrors.WrapAndReport(&err, "proxy.Client.GetMod(%q, %q)", modulePath, resolvedVersion)
 	return c.readBody(ctx, modulePath, resolvedVersion, "mod", false)
 }
 
@@ -94,7 +94,7 @@
 // $GOPROXY/<modulePath>/@v/<requestedVersion>.info to obtained the valid
 // semantic version.
 func (c *Client) GetZip(ctx context.Context, modulePath, resolvedVersion string) (_ *zip.Reader, err error) {
-	defer derrors.Wrap(&err, "proxy.Client.GetZip(ctx, %q, %q)", modulePath, resolvedVersion)
+	defer derrors.WrapAndReport(&err, "proxy.Client.GetZip(ctx, %q, %q)", modulePath, resolvedVersion)
 
 	bodyBytes, err := c.readBody(ctx, modulePath, resolvedVersion, "zip", false)
 	if err != nil {
@@ -110,7 +110,7 @@
 // GetZipSize gets the size in bytes of the zip from the proxy, without downloading it.
 // The version must be resolved, as by a call to Client.GetInfo.
 func (c *Client) GetZipSize(ctx context.Context, modulePath, resolvedVersion string) (_ int64, err error) {
-	defer derrors.Wrap(&err, "proxy.Client.GetZipSize(ctx, %q, %q)", modulePath, resolvedVersion)
+	defer derrors.WrapAndReport(&err, "proxy.Client.GetZipSize(ctx, %q, %q)", modulePath, resolvedVersion)
 
 	url, err := c.escapedURL(modulePath, resolvedVersion, "zip")
 	if err != nil {