{"record":{"id":"8aefb3df785ca889","repo":"slackhq/nebula","slug":"ce-context","errorCode":null,"errorMessage":"${ce.Context}","messagePattern":"\\$\\{ce\\.Context\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/error.go","lineNumber":49,"sourceCode":"func LogWithContextIfNeeded(msg string, err error, l *slog.Logger) {\n\tswitch v := err.(type) {\n\tcase *ContextualError:\n\t\tv.Log(l)\n\tdefault:\n\t\tl.Error(msg, \"error\", err)\n\t}\n}\n\nfunc (ce *ContextualError) Error() string {\n\tif ce.RealError == nil {\n\t\treturn ce.Context\n\t}\n\treturn fmt.Errorf(\"%s (%v): %w\", ce.Context, ce.Fields, ce.RealError).Error()\n}\n\nfunc (ce *ContextualError) Unwrap() error {\n\tif ce.RealError == nil {\n\t\treturn errors.New(ce.Context)\n\t}\n\treturn ce.RealError\n}\n\n// Log emits ce as a single error-level log line with Fields and RealError\n// promoted to top-level attributes, producing a flat shape callers can grep\n// or parse without walking into a nested object.\nfunc (ce *ContextualError) Log(l *slog.Logger) {\n\tattrs := make([]slog.Attr, 0, len(ce.Fields)+1)\n\tfor k, v := range ce.Fields {\n\t\tattrs = append(attrs, slog.Any(k, v))\n\t}\n\tif ce.RealError != nil {\n\t\tattrs = append(attrs, slog.Any(\"error\", ce.RealError))\n\t}\n\t// LogAttrs is intentional: attrs is built from a map[string]any so it has\n\t// no pair-form equivalent.\n\t//nolint:sloglint","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/util/error.go#L31-L67","documentation":"ContextualError wraps a real error with a human-readable Context string and Fields map. Unwrap returns the wrapped RealError for errors.Is/As chains; however, if RealError is nil, it fabricates errors.New(ce.Context), so the message seen is just the context text (rendered by Error() as \"<Context> (<fields>): <real>\"). This keeps context-only errors (validation before any underlying failure) unwrap-safe.","triggerScenarios":"Any code path returning a ContextualError whose RealError field is nil, followed by errors.Is/errors.As/unwrap-based handling; the reported message `${ce.Context}` is the Context string, e.g. \"while loading config\".","commonSituations":"Nebula failing during config parsing or handshake setup where ContextualError is constructed with only Context and Fields; log scrapers/alerting matching on the context text while the real cause is absent.","solutions":["Log with ce.Log() which promotes Fields and RealError to top-level attributes to see the full context","Inspect the Context string to find the nebula subsystem that failed (config, handshake, lighthouse, etc.) and read the matching config docs","If you control the code, populate RealError with the underlying error instead of only Context","Use errors.As to extract *ContextualError and read ce.Fields for structured details"],"exampleFix":"// before\nreturn NewContextualError(\"failed to get host info\", m, nil)\n// after\nreturn NewContextualError(\"failed to get host info\", m, underlyingErr)","handlingStrategy":"try-catch","validationCode":"var ce *util.ContextualError\nif errors.As(err, &ce) && ce.RealError == nil {\n    // context-only error: no underlying cause will surface via Unwrap\n}","typeGuard":"func hasRealError(ce *ContextualError) bool { return ce != nil && ce.RealError != nil }","tryCatchPattern":"var ce *ContextualError\nif errors.As(err, &ce) {\n    ce.Log(l) // flat log line with Fields + RealError attributes\n    return ce.RealError\n}\nreturn err","preventionTips":["Always pass the underlying error as RealError, not nil","Use ce.Log() instead of err.Error() for full structured detail","Match on Context strings when grepping nebula logs","Avoid string-comparing Unwrapped errors when Context-only wrapping is possible"],"tags":["nebula","error-wrapping","contextual-error","go"],"backgroundTag":"wrapped-error-context","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}