collector: preserve fatal causes in joined build errors (#2599)

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
Jan-Otto Kröpke
2026-10-09 11:14:57 +02:00
committed by GitHub
parent 1d413d4d4b
commit 9c9b6804a3
2 changed files with 33 additions and 8 deletions
+29 -8
View File
@@ -280,14 +280,7 @@ func (c *Collection) Build(ctx context.Context, logger *slog.Logger) error {
// errors.ErrUnsupported marks a collector whose subsystem is not
// available on this host, e.g. a Windows feature that is not installed.
if errors.Is(err, errors.ErrUnsupported) ||
errors.Is(err, pdh.ErrNoData) ||
errors.Is(err, winregistry.ErrNotExist) ||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)) ||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoCounter)) ||
errors.Is(err, mi.MI_RESULT_INVALID_OPERATION_TIMEOUT) ||
errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) ||
errors.Is(err, mi.MI_RESULT_INVALID_CLASS) {
if expectedBuildError(err) {
logger.LogAttrs(ctx, slog.LevelWarn, "couldn't initialize collector",
slog.Any("err", err),
)
@@ -421,3 +414,31 @@ func (c *Collection) WithCollectors(collectors []string) (*Collection, error) {
func (c *Collection) GetStartTime() gotime.Time {
return c.startTime
}
// expectedBuildError tolerates an error tree only if every independent cause is
// an expected initialization failure for an optional Windows subsystem.
func expectedBuildError(err error) bool {
if joined, ok := err.(interface{ Unwrap() []error }); ok {
causes := joined.Unwrap()
for _, cause := range causes {
if !expectedBuildError(cause) {
return false
}
}
return len(causes) != 0
}
if wrapped, ok := err.(interface{ Unwrap() error }); ok {
return expectedBuildError(wrapped.Unwrap())
}
return errors.Is(err, errors.ErrUnsupported) ||
errors.Is(err, pdh.ErrNoData) ||
errors.Is(err, winregistry.ErrNotExist) ||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)) ||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoCounter)) ||
errors.Is(err, mi.MI_RESULT_INVALID_OPERATION_TIMEOUT) ||
errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) ||
errors.Is(err, mi.MI_RESULT_INVALID_CLASS)
}
+4
View File
@@ -72,6 +72,10 @@ func TestCollectionBuildAndClose(t *testing.T) {
{name: "optional counter missing", buildErr: pdh.ErrNoData},
{name: "unsupported on this host", buildErr: fmt.Errorf("feature missing: %w", errors.ErrUnsupported)},
{name: "close failure", closeErr: closeFailure},
{name: "all expected joined errors", buildErr: errors.Join(errors.ErrUnsupported, pdh.ErrNoData)},
{name: "joined fatal error", buildErr: errors.Join(errors.ErrUnsupported, buildFailure), wantBuildErr: buildFailure},
{name: "nested fatal error", buildErr: errors.Join(pdh.ErrNoData, fmt.Errorf("nested: %w", errors.Join(errors.ErrUnsupported, buildFailure))), wantBuildErr: buildFailure},
{name: "multiple wrapped fatal error", buildErr: fmt.Errorf("%w and %w", errors.ErrUnsupported, buildFailure), wantBuildErr: buildFailure},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()