Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 88 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import (
var ErrUserNotFound errific.Err = "user not found"

func main() {
// Configure pretty JSON output for readability
errific.Configure(errific.OutputJSONPretty)

// Return an error with context
err := GetUser("user-123")
fmt.Println(err)
Expand All @@ -36,15 +39,23 @@ func GetUser(userID string) error {
```

**Output:**
```
user not found [main.go:20.GetUser]
```json
{
"error": "user not found",
"code": "USER_404",
"caller": "main.go:27.GetUser",
"context": {
"source": "database",
"user_id": "user-123"
}
}
```

The error includes:
- ✅ Automatic caller information (`main.go:20.GetUser`)
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation shows main.go:20.GetUser but the example output above (line 46) shows main.go:27.GetUser. These line numbers should be consistent. The actual call in the example is at line 32-37 in GetUser function, so the caller reference should reflect the actual location.

Suggested change
- ✅ Automatic caller information (`main.go:20.GetUser`)
- ✅ Automatic caller information (`main.go:27.GetUser`)

Copilot uses AI. Check for mistakes.
- ✅ Error code (`USER_404`)
- ✅ Structured context (user_id, source)
- ✅ JSON serializable for logging
- ✅ Error code (`USER_404`) visible in output
- ✅ Structured context (user_id, source) visible in output
- ✅ JSON output by default for structured logging

## ✨ Features

Expand Down Expand Up @@ -133,6 +144,78 @@ jsonBytes, _ := json.Marshal(err)
log.Info(string(jsonBytes))
```

### 🎨 Output Formats & Verbosity

Errific supports multiple output formats and verbosity levels. **By default, errors output as JSON with all metadata visible**.

```go
// Default: JSON format with full verbosity (shows all metadata)
errific.Configure() // or Configure(OutputJSON, VerbosityFull)
Comment on lines +152 to +153
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says Configure() defaults to OutputJSON with VerbosityFull, but looking at conf.go line 24-25, the code sets c.outputFormat = OutputJSON which matches. However, this contradicts the examples and tests which all use OutputPretty. The documentation should clarify whether calling Configure() with no arguments is the same as Configure(OutputJSON, VerbosityFull) or if it's different.

Copilot uses AI. Check for mistakes.

err := ErrUserNotFound.
WithCode("USER_404").
WithContext(errific.Context{"user_id": "user-123"})

fmt.Println(err)
// Output: {"error":"user not found","code":"USER_404","caller":"main.go:20","context":{"user_id":"user-123"}}

// JSON Pretty format (indented JSON for docs/debugging)
errific.Configure(OutputJSONPretty)
fmt.Println(err)
// Output:
// {
// "error": "user not found",
// "code": "USER_404",
// "caller": "main.go:20",
// "context": {
// "user_id": "user-123"
// }
// }

// Pretty format (multi-line, human-readable text)
errific.Configure(OutputPretty)
fmt.Println(err)
// Output:
// user not found [main.go:20.GetUser]
// code: USER_404
// context: map[user_id:user-123]

// Compact format (single-line key=value)
errific.Configure(OutputCompact)
fmt.Println(err)
// Output: user not found [main.go:20] code=USER_404 user_id=user-123

// Minimal verbosity (only message + caller, useful for simple logging)
errific.Configure(VerbosityMinimal)
fmt.Println(err)
// Output (JSON): {"error":"user not found","caller":"main.go:20"}

// Standard verbosity (message + caller + code + category + context)
errific.Configure(VerbosityStandard)
fmt.Println(err)
// Output (JSON): {"error":"user not found","code":"USER_404","caller":"main.go:20","context":{"user_id":"user-123"}}

// Custom verbosity (show only specific fields)
errific.Configure(VerbosityFull, HideContext, HideMCPData)
fmt.Println(err)
// Output (JSON): {"error":"user not found","code":"USER_404","caller":"main.go:20","http_status":404}
```

**Available output formats:**
- `OutputJSON` (default) - Compact JSON for structured logging
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation correctly identifies OutputJSON as the default, but this is inconsistent with all the test files and examples which use OutputPretty. This suggests the default may have been intended to change but the migration wasn't completed consistently across the codebase.

Copilot uses AI. Check for mistakes.
- `OutputJSONPretty` - Indented JSON for documentation and debugging
- `OutputPretty` - Multi-line, human-readable text
- `OutputCompact` - Single-line key=value pairs

**Available verbosity levels:**
- `VerbosityFull` (default) - Show all non-empty fields
- `VerbosityStandard` - Show code, category, context
- `VerbosityMinimal` - Show only message and caller
- `VerbosityCustom` - Use with `Show*`/`Hide*` flags for granular control

**Granular field control:**
`HideCode`, `HideCategory`, `HideContext`, `HideHTTPStatus`, `HideRetryMetadata`, `HideMCPData`, `HideTags`, `HideLabels`, `HideTimestamps`

### JSON Output

```json
Expand Down
235 changes: 235 additions & 0 deletions conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ func Configure(opts ...Option) {
c.withStack = false
c.trimPrefixes = nil
c.trimCWD = false
c.outputFormat = OutputJSON
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default output format is set to OutputJSON but the README documentation (line 149) claims the default is JSON, while tests and examples now explicitly use OutputPretty. This creates a breaking change where existing code without explicit configuration will suddenly get JSON output instead of pretty text. Consider setting the default to OutputPretty to maintain backward compatibility, or clearly document this as a breaking change.

Suggested change
c.outputFormat = OutputJSON
c.outputFormat = OutputPretty

Copilot uses AI. Check for mistakes.
c.verbosity = VerbosityFull

// Default field visibility (used when verbosity is VerbosityFull or VerbosityCustom)
c.showCode = true
c.showCategory = true
c.showContext = true
c.showHTTPStatus = true
c.showRetryMetadata = true
c.showMCPData = true
c.showTags = true
c.showLabels = true
c.showTimestamps = true

for _, opt := range opts {
switch o := opt.(type) {
Expand All @@ -38,6 +51,74 @@ func Configure(opts ...Option) {

case trimCWDOption:
c.trimCWD = o

case outputFormatOption:
c.outputFormat = o

case verbosityOption:
c.verbosity = o
// Set field visibility based on verbosity level
switch o {
case VerbosityMinimal:
c.showCode = false
c.showCategory = false
c.showContext = false
c.showHTTPStatus = false
c.showRetryMetadata = false
c.showMCPData = false
c.showTags = false
c.showLabels = false
c.showTimestamps = false

case VerbosityStandard:
c.showCode = true
c.showCategory = true
c.showContext = true
c.showHTTPStatus = false
c.showRetryMetadata = false
c.showMCPData = false
c.showTags = false
c.showLabels = false
c.showTimestamps = false

case VerbosityFull:
c.showCode = true
c.showCategory = true
c.showContext = true
c.showHTTPStatus = true
c.showRetryMetadata = true
c.showMCPData = true
c.showTags = true
c.showLabels = true
c.showTimestamps = true
}

case fieldVisibilityOption:
// When using field visibility options, automatically switch to VerbosityCustom
if c.verbosity != VerbosityCustom {
c.verbosity = VerbosityCustom
}
// Apply the specific field visibility setting
switch o.field {
case "code":
c.showCode = o.show
case "category":
c.showCategory = o.show
case "context":
c.showContext = o.show
case "http_status":
c.showHTTPStatus = o.show
case "retry_metadata":
c.showRetryMetadata = o.show
case "mcp_data":
c.showMCPData = o.show
case "tags":
c.showTags = o.show
case "labels":
c.showLabels = o.show
case "timestamps":
c.showTimestamps = o.show
}
}
}

Expand Down Expand Up @@ -70,6 +151,22 @@ var (
// TrimCWD will trim the current working directory from filenames.
// Default is false.
trimCWD trimCWDOption
// Output format: Pretty, JSON, or Compact.
// Default is Pretty.
outputFormat outputFormatOption
// Verbosity controls which fields are shown in Error() output.
// Default is VerbosityFull (show all non-empty fields).
verbosity verbosityOption
// Field visibility flags (used when verbosity is VerbosityCustom)
showCode bool
showCategory bool
showContext bool
showHTTPStatus bool
showRetryMetadata bool
showMCPData bool
showTags bool
showLabels bool
showTimestamps bool
}
cMu sync.RWMutex
)
Expand Down Expand Up @@ -139,6 +236,144 @@ type Option interface {
ErrificOption()
}

// outputFormatOption controls the format of error string output.
type outputFormatOption int

func (outputFormatOption) ErrificOption() {}

const (
// OutputPretty formats errors as human-readable multi-line text with all metadata.
//
// Example:
// user not found [main.go:20.GetUser]
// code: USER_404
// context: {user_id: user-123, source: database}
// http_status: 400
OutputPretty outputFormatOption = iota

// OutputJSON formats errors as compact JSON.
// This is the default.
// Useful for structured logging and machine processing.
//
// Example:
// {"error":"user not found","caller":"main.go:20","code":"USER_404",...}
OutputJSON

// OutputJSONPretty formats errors as indented JSON.
// Useful for documentation, debugging, and human-readable JSON output.
//
// Example:
// {
// "error": "user not found",
// "code": "USER_404",
// "caller": "main.go:20"
// }
OutputJSONPretty

// OutputCompact formats errors as single-line text with key=value pairs.
// Useful for log aggregation systems.
//
// Example:
// user not found [main.go:20] code=USER_404 user_id=user-123 http_status=400
OutputCompact
)

// verbosityOption controls which fields are included in Error() output.
type verbosityOption int

func (verbosityOption) ErrificOption() {}

const (
// VerbosityMinimal shows only the error message and caller.
//
// Example:
// user not found [main.go:20.GetUser]
VerbosityMinimal verbosityOption = iota

// VerbosityStandard shows message, caller, code, category, and context.
// Good balance for most applications.
//
// Example:
// user not found [main.go:20.GetUser]
// code: USER_404
// category: validation
// context: {user_id: user-123}
VerbosityStandard

// VerbosityFull shows all non-empty fields (default).
// Recommended for debugging and development.
//
// Example:
// user not found [main.go:20.GetUser]
// code: USER_404
// category: validation
// context: {user_id: user-123, source: database}
// http_status: 400
// retryable: true
// correlation_id: trace-123
// help: Check if user exists
VerbosityFull

// VerbosityCustom allows fine-grained control via individual field flags.
// Use with Show* and Hide* options.
VerbosityCustom
)

// Field visibility options for VerbosityCustom.
type fieldVisibilityOption struct {
field string
show bool
}

func (fieldVisibilityOption) ErrificOption() {}

var (
// ShowCode includes error code in output.
ShowCode = fieldVisibilityOption{field: "code", show: true}
// HideCode excludes error code from output.
HideCode = fieldVisibilityOption{field: "code", show: false}

// ShowCategory includes error category in output.
ShowCategory = fieldVisibilityOption{field: "category", show: true}
// HideCategory excludes error category from output.
HideCategory = fieldVisibilityOption{field: "category", show: false}

// ShowContext includes structured context in output.
ShowContext = fieldVisibilityOption{field: "context", show: true}
// HideContext excludes structured context from output.
HideContext = fieldVisibilityOption{field: "context", show: false}

// ShowHTTPStatus includes HTTP status code in output.
ShowHTTPStatus = fieldVisibilityOption{field: "http_status", show: true}
// HideHTTPStatus excludes HTTP status code from output.
HideHTTPStatus = fieldVisibilityOption{field: "http_status", show: false}

// ShowRetryMetadata includes retry information (retryable, retry_after, max_retries) in output.
ShowRetryMetadata = fieldVisibilityOption{field: "retry_metadata", show: true}
// HideRetryMetadata excludes retry information from output.
HideRetryMetadata = fieldVisibilityOption{field: "retry_metadata", show: false}

// ShowMCPData includes MCP-related fields (correlation_id, help, suggestion, etc.) in output.
ShowMCPData = fieldVisibilityOption{field: "mcp_data", show: true}
// HideMCPData excludes MCP-related fields from output.
HideMCPData = fieldVisibilityOption{field: "mcp_data", show: false}

// ShowTags includes semantic tags in output.
ShowTags = fieldVisibilityOption{field: "tags", show: true}
// HideTags excludes semantic tags from output.
HideTags = fieldVisibilityOption{field: "tags", show: false}

// ShowLabels includes key-value labels in output.
ShowLabels = fieldVisibilityOption{field: "labels", show: true}
// HideLabels excludes key-value labels from output.
HideLabels = fieldVisibilityOption{field: "labels", show: false}

// ShowTimestamps includes timestamp and duration in output.
ShowTimestamps = fieldVisibilityOption{field: "timestamps", show: true}
// HideTimestamps excludes timestamp and duration from output.
HideTimestamps = fieldVisibilityOption{field: "timestamps", show: false}
)

var root string
var goroot string

Expand Down
Loading