Logging: structured slog messages throughout, and WithLogger options so callers choose the logger

Log messages are lower-case event names with their details as key/value
attributes ("error" for errors), in place of formatted strings.

Loggable package take a WithLogger option as part of their "New*" call,
defaulting to slog.Default(). Each tags its lines with its `component`.

Some debug loggers print directly rather than using slog when in
terminal logging mode.
This commit is contained in:
bwilliams committed 2026-10-15 16:10:52 +01:00
1 parent 0b5fde9563
commit 043e307663
49 files changed
+598 -248

No files matched your search

+38 -27
View File
@@ -226,7 +226,9 @@ func missingIDsError(haveOCID, haveID bool) error {
// releases, etc).
//
// This method takes ownership of the response body and closes it when done.
func safeUnpackReleases(response *resty.Response) (*ocds.ReleasePackage, error) {
func safeUnpackReleases(
logger *slog.Logger, response *resty.Response,
) (*ocds.ReleasePackage, error) {
var pkg safeReleasePackage
// Take ownership of the response body.
@@ -263,9 +265,12 @@ func safeUnpackReleases(response *resty.Response) (*ocds.ReleasePackage, error)
err := unmarshalLenientDates(rawRel, &release)
if err != nil {
ocid, release_id, _ := releaseIDs(rawRel)
slog.Warn(fmt.Sprintf(
"Skipping release (%s/%s) due to error: %s", ocid, release_id, err,
))
logger.Warn(
"skipping unreadable release",
"ocid", ocid,
"release_id", release_id,
"error", err,
)
continue
}
out.Releases = append(out.Releases, release)
@@ -303,8 +308,9 @@ func closeBody(res *resty.Response) {
// response status.
func fetchURI(ft *FindATender, uri string) (*resty.Response, error) {
withTrace := ft.config.Logging.LogLevel() <= slog.LevelDebug
logger := ft.logger()
slog.Info("findTender.resty", "request_uri", uri)
logger.Info("fetching", "uri", uri)
ctx := ft.ctx
if ctx == nil {
@@ -316,23 +322,31 @@ func fetchURI(ft *FindATender, uri string) (*resty.Response, error) {
res, err := req.SetTrace(withTrace).Get(uri)
if withTrace {
if logging.IsJSONHandler(slog.Default().Handler()) {
slog.Debug(
"Resty",
"trace", res.Request.TraceInfo().JSON(),
"status", res.Status(),
"response", res,
)
} else {
slog.Debug("Resty " + res.Request.TraceInfo().String())
slog.Debug("Resty status: " + res.Status())
slog.Debug("Resty response: " + res.String())
}
logTrace(logger, res)
}
return res, err
}
// logTrace logs res's trace, status and body at debug level. On a terminal
// the trace and body are printed as they are, as a field would escape them
// into one line; elsewhere they are fields, the trace as JSON for the JSON
// (file) logger.
func logTrace(logger *slog.Logger, res *resty.Response) {
if logging.WritesToTerminal(logger.Handler()) {
logger.Debug("fetched", "status", res.Status())
logging.PrintToTerminal(res.Request.TraceInfo().String())
logging.PrintToTerminal(res.String())
return
}
trace := res.Request.TraceInfo().String()
if logging.IsJSONHandler(logger.Handler()) {
trace = res.Request.TraceInfo().JSON()
}
logger.Debug("fetched", "trace", trace, "status", res.Status(), "response", res.String())
}
// fetchURI performs a fetch against the API. If the API returns with a rate limit
// reached response and allowRateLimit is true, then wait and retry like a good
// client. If allowRateLimit is false, return to the caller informing them of
@@ -365,10 +379,9 @@ func (ft *FindATender) fetchURI(
return nil, fmt.Errorf("unknown retry-after value: %w", err)
}
slog.Warn(fmt.Sprintf(
"API rate limit hit, waiting as requested for %d seconds", retrySec,
))
if rateSleep(ctx, time.Duration(retrySec)*time.Second) != nil {
retryAfter := time.Duration(retrySec) * time.Second
ft.logger().Warn("api rate limit hit, waiting as asked", "retry_after", retryAfter)
if rateSleep(ctx, retryAfter) != nil {
return nil, ErrFetchCancelled
}
@@ -432,7 +445,7 @@ func (ft *FindATender) fetchOnce(opts RequestOptions) ([]ocds.Release, error) {
return nil, err
}
pkg, err := safeUnpackReleases(res)
pkg, err := safeUnpackReleases(ft.logger(), res)
if err != nil {
return nil, err
}
@@ -454,7 +467,7 @@ func (ft *FindATender) fetchAll(opts RequestOptions) ([]ocds.Release, error) {
return nil, err
}
pkg, err := safeUnpackReleases(res)
pkg, err := safeUnpackReleases(ft.logger(), res)
if err != nil {
return nil, err
}
@@ -466,9 +479,7 @@ func (ft *FindATender) fetchAll(opts RequestOptions) ([]ocds.Release, error) {
for {
if pkg.Links != nil && len(pkg.Links.Next) > 0 {
if seen[pkg.Links.Next] {
slog.Warn(fmt.Sprintf(
"Stopping at repeated next link: %s", pkg.Links.Next,
))
ft.logger().Warn("stopping at repeated next link", "link", pkg.Links.Next)
break
}
seen[pkg.Links.Next] = true
@@ -482,7 +493,7 @@ func (ft *FindATender) fetchAll(opts RequestOptions) ([]ocds.Release, error) {
return nil, err
}
pkg, err = safeUnpackReleases(res)
pkg, err = safeUnpackReleases(ft.logger(), res)
if err != nil {
return nil, err
}