Files
log/utility.go

165 lines
4.3 KiB
Go

// FILE: utility.go
package log
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"unicode"
)
// getTrace returns a function call trace string.
func getTrace(depth int64, skip int) string {
if depth <= 0 || depth > 10 {
return ""
}
pc := make([]uintptr, int(depth)+skip)
n := runtime.Callers(skip+1, pc) // +1 because Callers includes its own frame
if n == 0 {
return "(unknown)"
}
frames := runtime.CallersFrames(pc[:n])
var trace []string
count := 0
for {
frame, more := frames.Next()
if !more || count >= int(depth) {
break
}
funcName := filepath.Base(frame.Function)
parts := strings.Split(funcName, ".")
lastPart := parts[len(parts)-1]
if strings.HasPrefix(lastPart, "func") {
isAnonymous := true
for _, r := range lastPart[4:] {
if !unicode.IsDigit(r) {
isAnonymous = false
break
}
}
if isAnonymous && len(lastPart) > 4 {
funcName = fmt.Sprintf("(anonymous in %s)", strings.Join(parts[:len(parts)-1], "."))
} else {
funcName = lastPart
}
} else {
funcName = lastPart
}
trace = append(trace, funcName)
count++
}
if len(trace) == 0 {
return "(unknown)"
}
// Reverse for caller -> callee order
for i, j := 0, len(trace)-1; i < j; i, j = i+1, j-1 {
trace[i], trace[j] = trace[j], trace[i]
}
return strings.Join(trace, " -> ")
}
// fmtErrorf wrapper
func fmtErrorf(format string, args ...any) error {
if !strings.HasPrefix(format, "log: ") {
format = "log: " + format
}
return fmt.Errorf(format, args...)
}
// fmtFprintf wrapper (used for internal errors)
func fmtFprintf(w *os.File, format string, args ...any) {
if !strings.HasPrefix(format, "log: ") {
format = "log: " + format
}
fmt.Fprintf(w, format, args...)
}
// combineErrors helper
func combineErrors(err1, err2 error) error {
if err1 == nil {
return err2
}
if err2 == nil {
return err1
}
return fmt.Errorf("%v; %w", err1, err2)
}
// parseKeyValue splits a "key=value" string.
func parseKeyValue(arg string) (string, string, error) {
parts := strings.SplitN(strings.TrimSpace(arg), "=", 2)
if len(parts) != 2 {
return "", "", fmtErrorf("invalid format in override string '%s', expected key=value", arg)
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
if key == "" {
return "", "", fmtErrorf("key cannot be empty in override string '%s'", arg)
}
return key, value, nil
}
// validateConfigValue checks ranges and specific constraints for parsed config values.
func validateConfigValue(key string, value interface{}) error {
keyLower := strings.ToLower(key)
switch keyLower {
case "name":
if v, ok := value.(string); ok && strings.TrimSpace(v) == "" {
return fmtErrorf("log name cannot be empty")
}
case "format":
if v, ok := value.(string); ok && v != "txt" && v != "json" {
return fmtErrorf("invalid format: '%s' (use txt or json)", v)
}
case "extension":
if v, ok := value.(string); ok && strings.HasPrefix(v, ".") {
return fmtErrorf("extension should not start with dot: %s", v)
}
case "buffer_size":
if v, ok := value.(int64); ok && v <= 0 {
return fmtErrorf("buffer_size must be positive: %d", v)
}
case "max_size_mb", "max_total_size_mb", "min_disk_free_mb":
if v, ok := value.(int64); ok && v < 0 {
return fmtErrorf("%s cannot be negative: %d", key, v)
}
case "flush_timer", "disk_check_interval_ms", "min_check_interval_ms", "max_check_interval_ms":
if v, ok := value.(int64); ok && v <= 0 {
return fmtErrorf("%s must be positive milliseconds: %d", key, v)
}
case "trace_depth":
if v, ok := value.(int64); ok && (v < 0 || v > 10) {
return fmtErrorf("trace_depth must be between 0 and 10: %d", v)
}
case "retention_period", "retention_check_interval":
if v, ok := value.(float64); ok && v < 0 {
return fmtErrorf("%s cannot be negative: %f", key, v)
}
}
return nil
}
// Level converts level string to numeric constant.
func Level(levelStr string) (int64, error) {
switch strings.ToLower(strings.TrimSpace(levelStr)) {
case "debug":
return LevelDebug, nil
case "info":
return LevelInfo, nil
case "warn":
return LevelWarn, nil
case "error":
return LevelError, nil
case "proc":
return LevelProc, nil
case "disk":
return LevelDisk, nil
case "sys":
return LevelSys, nil
default:
return 0, fmtErrorf("invalid level string: '%s' (use debug, info, warn, error, proc, disk, sys)", levelStr)
}
}