• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

smallnest / goclaw / 22337106902

24 Feb 2026 04:49AM UTC coverage: 4.317% (-1.5%) from 5.772%
22337106902

push

github

smallnest
refactor agent

411 of 1648 new or added lines in 17 files covered. (24.94%)

2 existing lines in 2 files now uncovered.

1061 of 24577 relevant lines covered (4.32%)

0.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.33
/errors/logging.go
1
package errors
2

3
import (
4
        "errors"
5
        "fmt"
6
        "runtime/debug"
7
        "slices"
8
        "strings"
9

10
        "github.com/smallnest/goclaw/internal/logger"
11
        "go.uber.org/zap"
12
)
13

14
// ErrorHandler provides centralized error handling
15
type ErrorHandler struct {
16
        logger *zap.Logger
17
}
18

19
// NewErrorHandler creates a new error handler
NEW
20
func NewErrorHandler() *ErrorHandler {
×
NEW
21
        return &ErrorHandler{
×
NEW
22
                logger: logger.L(),
×
NEW
23
        }
×
NEW
24
}
×
25

26
// Handle handles an error with appropriate logging based on severity
NEW
27
func (h *ErrorHandler) Handle(err error) {
×
NEW
28
        if err == nil {
×
NEW
29
                return
×
NEW
30
        }
×
31

32
        // Get error details
NEW
33
        code := GetCode(err)
×
NEW
34
        msg := GetMessage(err)
×
NEW
35

×
NEW
36
        // Log based on error code severity
×
NEW
37
        switch code {
×
NEW
38
        case ErrCodeInvalidInput, ErrCodeNotFound, ErrCodeAlreadyExists:
×
NEW
39
                // User errors - log at debug level
×
NEW
40
                h.logger.Debug("User error", zap.String("code", string(code)), zap.String("message", msg))
×
NEW
41
        case ErrCodeTimeout, ErrCodeRateLimit:
×
NEW
42
                // Temporary errors - log at info level
×
NEW
43
                h.logger.Info("Temporary error", zap.String("code", string(code)), zap.String("message", msg))
×
NEW
44
        case ErrCodeAuth, ErrCodeBilling, ErrCodePermission:
×
NEW
45
                // Security/billing errors - log at warn level
×
NEW
46
                h.logger.Warn("Security error", zap.String("code", string(code)), zap.String("message", msg))
×
NEW
47
        case ErrCodeToolExecution, ErrCodeProviderError, ErrCodeMemorySearchFailed:
×
NEW
48
                // Operation errors - log at error level
×
NEW
49
                h.logWithError("Operation failed", err)
×
NEW
50
        case ErrCodeAgentStartFailed, ErrCodeSessionCorrupted:
×
NEW
51
                // Critical errors - log at error level with stack trace
×
NEW
52
                h.logWithStack("Critical error", err)
×
NEW
53
        default:
×
NEW
54
                // Unknown errors - log at error level
×
NEW
55
                h.logWithError("Unknown error", err)
×
56
        }
57
}
58

59
// Handlef handles an error with formatted message
NEW
60
func (h *ErrorHandler) Handlef(err error, format string, args ...any) {
×
NEW
61
        if err == nil {
×
NEW
62
                return
×
NEW
63
        }
×
NEW
64
        msg := fmt.Sprintf(format, args...)
×
NEW
65
        h.logger.Error(msg,
×
NEW
66
                zap.String("error_code", string(GetCode(err))),
×
NEW
67
                zap.String("error_message", GetMessage(err)),
×
NEW
68
                zap.Error(err))
×
69
}
70

71
// HandleWithFields handles an error with additional fields
NEW
72
func (h *ErrorHandler) HandleWithFields(err error, fields ...zap.Field) {
×
NEW
73
        if err == nil {
×
NEW
74
                return
×
NEW
75
        }
×
NEW
76
        allFields := append([]zap.Field{
×
NEW
77
                zap.String("error_code", string(GetCode(err))),
×
NEW
78
                zap.String("error_message", GetMessage(err)),
×
NEW
79
        }, fields...)
×
NEW
80
        h.logger.Error("Error occurred", allFields...)
×
81
}
82

83
// Recover handles panics and converts to errors
NEW
84
func (h *ErrorHandler) Recover(operation string) error {
×
NEW
85
        if r := recover(); r != nil {
×
NEW
86
                stack := debug.Stack()
×
NEW
87
                err := New(ErrCodeUnknown, fmt.Sprintf("panic in %s", operation))
×
NEW
88

×
NEW
89
                h.logger.Error("Panic recovered",
×
NEW
90
                        zap.String("operation", operation),
×
NEW
91
                        zap.Any("recover", r),
×
NEW
92
                        zap.String("stack", string(stack)))
×
NEW
93

×
NEW
94
                return err
×
NEW
95
        }
×
NEW
96
        return nil
×
97
}
98

99
// Validate checks if error is nil, returns wrapped error if not
NEW
100
func (h *ErrorHandler) Validate(err error, code ErrorCode, message string) error {
×
NEW
101
        if err == nil {
×
NEW
102
                return nil
×
NEW
103
        }
×
NEW
104
        return Wrap(err, code, message)
×
105
}
106

107
// logWithError logs error with full details
NEW
108
func (h *ErrorHandler) logWithError(message string, err error) {
×
NEW
109
        var appErr *AppError
×
NEW
110
        if errors.As(err, &appErr) && appErr.Err != nil {
×
NEW
111
                h.logger.Error(message,
×
NEW
112
                        zap.String("error_code", string(appErr.Code)),
×
NEW
113
                        zap.String("error_message", appErr.Message),
×
NEW
114
                        zap.Error(appErr.Err),
×
NEW
115
                        zap.Any("context", appErr.Context))
×
NEW
116
        } else {
×
NEW
117
                h.logger.Error(message,
×
NEW
118
                        zap.String("error_code", string(GetCode(err))),
×
NEW
119
                        zap.String("error_message", GetMessage(err)),
×
NEW
120
                        zap.Error(err))
×
NEW
121
        }
×
122
}
123

124
// logWithStack logs error with stack trace
NEW
125
func (h *ErrorHandler) logWithStack(message string, err error) {
×
NEW
126
        stack := debug.Stack()
×
NEW
127
        var appErr *AppError
×
NEW
128
        if errors.As(err, &appErr) && appErr.Err != nil {
×
NEW
129
                h.logger.Error(message,
×
NEW
130
                        zap.String("error_code", string(appErr.Code)),
×
NEW
131
                        zap.String("error_message", appErr.Message),
×
NEW
132
                        zap.Error(appErr.Err),
×
NEW
133
                        zap.String("stack", string(stack)),
×
NEW
134
                        zap.Any("context", appErr.Context))
×
NEW
135
        } else {
×
NEW
136
                h.logger.Error(message,
×
NEW
137
                        zap.String("error_code", string(GetCode(err))),
×
NEW
138
                        zap.String("error_message", GetMessage(err)),
×
NEW
139
                        zap.Error(err),
×
NEW
140
                        zap.String("stack", string(stack)))
×
NEW
141
        }
×
142
}
143

144
// IsRetryable checks if an error is retryable
145
func IsRetryable(err error) bool {
4✔
146
        if err == nil {
4✔
NEW
147
                return false
×
NEW
148
        }
×
149

150
        code := GetCode(err)
4✔
151
        retryableCodes := []ErrorCode{
4✔
152
                ErrCodeTimeout,
4✔
153
                ErrCodeRateLimit,
4✔
154
                ErrCodeProviderUnavailable,
4✔
155
                ErrCodeProviderTimeout,
4✔
156
        }
4✔
157

4✔
158
        if slices.Contains(retryableCodes, code) {
6✔
159
                return true
2✔
160
        }
2✔
161

162
        // Also check error message for network-related issues
163
        msg := strings.ToLower(err.Error())
2✔
164
        networkKeywords := []string{
2✔
165
                "connection refused",
2✔
166
                "connection reset",
2✔
167
                "timeout",
2✔
168
                "temporary failure",
2✔
169
                "network",
2✔
170
        }
2✔
171

2✔
172
        for _, keyword := range networkKeywords {
12✔
173
                if strings.Contains(msg, keyword) {
10✔
NEW
174
                        return true
×
NEW
175
                }
×
176
        }
177

178
        return false
2✔
179
}
180

181
// IsFatal checks if an error is fatal (should stop operation)
182
func IsFatal(err error) bool {
4✔
183
        if err == nil {
4✔
NEW
184
                return false
×
NEW
185
        }
×
186

187
        code := GetCode(err)
4✔
188
        fatalCodes := []ErrorCode{
4✔
189
                ErrCodeInvalidConfig,
4✔
190
                ErrCodePermission,
4✔
191
                ErrCodeBilling,
4✔
192
                ErrCodeAuth,
4✔
193
                ErrCodeSessionCorrupted,
4✔
194
        }
4✔
195

4✔
196
        if slices.Contains(fatalCodes, code) {
6✔
197
                return true
2✔
198
        }
2✔
199

200
        return false
2✔
201
}
202

203
// GetUserMessage returns a user-friendly error message
204
func GetUserMessage(err error) string {
3✔
205
        if err == nil {
3✔
NEW
206
                return ""
×
NEW
207
        }
×
208

209
        code := GetCode(err)
3✔
210
        msg := GetMessage(err)
3✔
211

3✔
212
        // Map error codes to user-friendly messages
3✔
213
        messages := map[ErrorCode]string{
3✔
214
                ErrCodeInvalidInput:        "The input provided is invalid. Please check and try again.",
3✔
215
                ErrCodeInvalidConfig:       "Configuration error. Please check your settings.",
3✔
216
                ErrCodeNotFound:            "The requested resource was not found.",
3✔
217
                ErrCodeAlreadyExists:       "The resource already exists.",
3✔
218
                ErrCodePermission:          "You don't have permission to perform this action.",
3✔
219
                ErrCodeTimeout:             "The operation timed out. Please try again.",
3✔
220
                ErrCodeRateLimit:           "Too many requests. Please wait and try again.",
3✔
221
                ErrCodeAuth:                "Authentication failed. Please check your credentials.",
3✔
222
                ErrCodeBilling:             "Billing error. Please check your account.",
3✔
223
                ErrCodeToolExecution:       "Failed to execute the requested operation.",
3✔
224
                ErrCodeToolNotFound:        "The requested tool is not available.",
3✔
225
                ErrCodeSkillNotFound:       "The requested skill is not available.",
3✔
226
                ErrCodeProviderUnavailable: "The AI service is currently unavailable.",
3✔
227
                ErrCodeSessionNotFound:     "Session not found. Please start a new conversation.",
3✔
228
                ErrCodeMemorySearchFailed:  "Failed to search memory. The operation will be retried.",
3✔
229
        }
3✔
230

3✔
231
        if userMsg, ok := messages[code]; ok {
5✔
232
                return userMsg
2✔
233
        }
2✔
234

235
        // Default message
236
        if msg != "" {
2✔
237
                return msg
1✔
238
        }
1✔
239

NEW
240
        return "An unexpected error occurred. Please try again."
×
241
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc