e1.0.0 Initial commit, restructured and refactored logger package, used config package for configuration management.
This commit is contained in:
158
utility.go
Normal file
158
utility.go
Normal file
@ -0,0 +1,158 @@
|
||||
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
|
||||
}
|
||||
|
||||
// parseLevel converts level string to numeric constant.
|
||||
func parseLevel(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
|
||||
default:
|
||||
return 0, fmtErrorf("invalid level string: '%s' (use debug, info, warn, error)", levelStr)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user