Notable changes to this project will be documented in this file. To keep it lightweight, releases 2+ minor versions back will be churned regularly.
The format is based on Keep a Changelog.
Previously, when DNS resolution failed but a cached address was available for fallback, the error was silently swallowed and WatcherResponse.Err would be nil. This made it impossible to detect DNS degradation in monitoring systems.
Now, DNS errors are always reported in WatcherResponse.Err, even when fallback succeeds. This provides visibility into DNS issues while maintaining resilience.
Before:
// DNS fails but fallback uses cached address → resp.Err == nil
resp := <-wadjit.Responses()
if resp.Err != nil {
// Only triggered for hard failures
log.Error("Request failed", resp.Err)
}After:
// DNS fails with fallback → resp.Err != nil (contains DNS error)
resp := <-wadjit.Responses()
if resp.Err != nil {
// This now triggers for DNS failures too!
// But request may have completed using cached address
log.Error("Request failed", resp.Err)
}Two new fields added to DNSMetadata and DNSDecision:
FallbackUsed bool: Indicates whether a cached address was used due to DNS lookup failureErr error: Contains the DNS resolution error, if any occurred
Access DNS error information:
resp := <-wadjit.Responses()
md := resp.Metadata()
if md.DNS != nil {
if md.DNS.FallbackUsed {
log.Warn("Using cached DNS entry", "error", md.DNS.Err)
}
if md.DNS.Err != nil {
log.Warn("DNS error occurred", "error", md.DNS.Err)
}
}DNS decision hooks now receive fallback and error information:
policy := wadjit.DNSPolicy{
Mode: wadjit.DNSRefreshTTL,
TTLMin: time.Minute,
DecisionCallback: func(ctx context.Context, decision wadjit.DNSDecision) {
if decision.FallbackUsed {
log.Warn("DNS fallback triggered", "error", decision.Err)
}
if decision.Err != nil {
metrics.RecordDNSError(decision.Host, decision.Err)
}
},
}Update error handling to check if fallback was used:
resp := <-wadjit.Responses()
if resp.Err != nil {
md := resp.Metadata()
if md.DNS != nil && md.DNS.FallbackUsed {
// DNS failed but request completed with cached address
log.Warn("DNS fallback used",
"error", md.DNS.Err,
"cached_addr", md.DNS.ResolvedAddrs)
// Maybe alert but don't fail completely
} else {
// Hard failure - request did not complete
log.Error("Request failed completely", resp.Err)
// Definitely alert/retry
}
}Expect increased error counts in dashboards - this is correct behavior. DNS failures were always happening, just not visible before.
Distinguish between error types in metrics:
resp := <-wadjit.Responses()
if resp.Err != nil {
md := resp.Metadata()
if md.DNS != nil {
if md.DNS.FallbackUsed {
metrics.IncrCounter("dns.fallback.used") // Degraded but working
metrics.IncrCounter("dns.errors.soft")
} else if md.DNS.Err != nil {
metrics.IncrCounter("dns.errors.hard") // Complete failure
}
}
metrics.IncrCounter("requests.errors.total")
}Consider different retry strategies for fallback vs hard failures:
func shouldRetry(resp WatcherResponse) bool {
if resp.Err == nil {
return false // Success
}
md := resp.Metadata()
if md.DNS != nil && md.DNS.FallbackUsed {
// DNS failed but got cached data - maybe don't retry immediately
// as the DNS issue might be temporary
return false
}
// Hard failure or non-DNS error - retry
return true
}If code assumed Err == nil means complete success:
// OLD - May need updating
if resp.Err != nil {
return fmt.Errorf("health check failed: %w", resp.Err)
}
// NEW - More nuanced
if resp.Err != nil {
md := resp.Metadata()
if md.DNS != nil && md.DNS.FallbackUsed {
// Degrade gracefully - log warning but continue
metrics.IncrDNSFallback(resp.URL.Host)
} else {
// Hard failure - propagate error
return fmt.Errorf("health check failed: %w", resp.Err)
}
}- Update error handling to check for
DNS.FallbackUsedbefore treating errors as hard failures - Update monitoring to distinguish soft (fallback) vs hard DNS failures
- Review retry logic - consider not retrying immediately on fallback errors
- Test in staging - watch for increased error counts (expected and correct)
- Update runbooks - DNS fallback errors may now trigger alerts that need different responses
- Modified
dnsResolveOutcometo includefallbackErrfield for error propagation - Updated
resolveTTL(),resolveCadence(), andresolveSingle()to return DNS decisions even on error - Added
lastDecisionfield todnsPolicyManagerfor error reporting in HTTP responses - Created
httpTaskResponseErrortype to attach DNS metadata to error responses - Enhanced decision context propagation to include errors and fallback state