yoohoo
This commit is contained in:
210
pkg/types/types.go
Normal file
210
pkg/types/types.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemInfo represents comprehensive system information
|
||||
type SystemInfo struct {
|
||||
CPU CPUInfo `json:"cpu"`
|
||||
Memory MemoryInfo `json:"memory"`
|
||||
Battery *BatteryInfo `json:"battery,omitempty"`
|
||||
PowerSupply PowerSupplyInfo `json:"power_supply"`
|
||||
Kernel KernelInfo `json:"kernel"`
|
||||
Distribution DistributionInfo `json:"distribution"`
|
||||
Hardware HardwareInfo `json:"hardware"`
|
||||
TLPStatus TLPStatus `json:"tlp_status"`
|
||||
}
|
||||
|
||||
// CPUInfo contains CPU-related information
|
||||
type CPUInfo struct {
|
||||
Model string `json:"model"`
|
||||
Vendor string `json:"vendor"`
|
||||
Cores int `json:"cores"`
|
||||
Threads int `json:"threads"`
|
||||
BaseFrequency int64 `json:"base_frequency_mhz"`
|
||||
MaxFrequency int64 `json:"max_frequency_mhz"`
|
||||
MinFrequency int64 `json:"min_frequency_mhz"`
|
||||
Governor string `json:"governor"`
|
||||
Architecture string `json:"architecture"`
|
||||
}
|
||||
|
||||
// MemoryInfo contains memory information
|
||||
type MemoryInfo struct {
|
||||
Total int64 `json:"total_mb"`
|
||||
Available int64 `json:"available_mb"`
|
||||
Used int64 `json:"used_mb"`
|
||||
SwapTotal int64 `json:"swap_total_mb"`
|
||||
SwapUsed int64 `json:"swap_used_mb"`
|
||||
}
|
||||
|
||||
// BatteryInfo contains battery information
|
||||
type BatteryInfo struct {
|
||||
Present bool `json:"present"`
|
||||
Status string `json:"status"`
|
||||
Capacity int `json:"capacity_percent"`
|
||||
EnergyFull int64 `json:"energy_full_wh"`
|
||||
EnergyNow int64 `json:"energy_now_wh"`
|
||||
PowerNow int64 `json:"power_now_w"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
Model string `json:"model"`
|
||||
Technology string `json:"technology"`
|
||||
CycleCount int `json:"cycle_count"`
|
||||
DesignCapacity int64 `json:"design_capacity_wh"`
|
||||
}
|
||||
|
||||
// PowerSupplyInfo contains power supply information
|
||||
type PowerSupplyInfo struct {
|
||||
ACConnected bool `json:"ac_connected"`
|
||||
Type string `json:"type"`
|
||||
Online bool `json:"online"`
|
||||
}
|
||||
|
||||
// KernelInfo contains kernel information
|
||||
type KernelInfo struct {
|
||||
Version string `json:"version"`
|
||||
Release string `json:"release"`
|
||||
Parameters map[string]string `json:"parameters"`
|
||||
}
|
||||
|
||||
// DistributionInfo contains Linux distribution information
|
||||
type DistributionInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Codename string `json:"codename"`
|
||||
Family string `json:"family"`
|
||||
PackageManager string `json:"package_manager"`
|
||||
}
|
||||
|
||||
// HardwareInfo contains additional hardware information
|
||||
type HardwareInfo struct {
|
||||
Chassis string `json:"chassis"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
ProductName string `json:"product_name"`
|
||||
GPUs []GPUInfo `json:"gpus"`
|
||||
NetworkCards []string `json:"network_cards"`
|
||||
StorageDevices []StorageInfo `json:"storage_devices"`
|
||||
}
|
||||
|
||||
// GPUInfo contains GPU information
|
||||
type GPUInfo struct {
|
||||
Vendor string `json:"vendor"`
|
||||
Model string `json:"model"`
|
||||
Driver string `json:"driver"`
|
||||
Memory int64 `json:"memory_mb"`
|
||||
}
|
||||
|
||||
// StorageInfo contains storage device information
|
||||
type StorageInfo struct {
|
||||
Device string `json:"device"`
|
||||
Type string `json:"type"` // SSD, HDD, NVMe
|
||||
Size int64 `json:"size_gb"`
|
||||
Model string `json:"model"`
|
||||
Rotational bool `json:"rotational"`
|
||||
}
|
||||
|
||||
// TLPStatus contains TLP installation and configuration status
|
||||
type TLPStatus struct {
|
||||
Installed bool `json:"installed"`
|
||||
Version string `json:"version"`
|
||||
Active bool `json:"active"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
ConfigExists bool `json:"config_exists"`
|
||||
CurrentConfig map[string]string `json:"current_config"`
|
||||
LastModified *time.Time `json:"last_modified"`
|
||||
}
|
||||
|
||||
// UserPreferences represents user's power management preferences
|
||||
type UserPreferences struct {
|
||||
PowerProfile PowerProfile `json:"power_profile"`
|
||||
UseCase UseCase `json:"use_case"`
|
||||
BatteryPriority BatteryPriority `json:"battery_priority"`
|
||||
PerformanceMode PerformanceMode `json:"performance_mode"`
|
||||
CustomSettings map[string]interface{} `json:"custom_settings"`
|
||||
SpecialRequirements []string `json:"special_requirements"`
|
||||
}
|
||||
|
||||
// PowerProfile represents different power usage profiles
|
||||
type PowerProfile string
|
||||
|
||||
const (
|
||||
PowerProfileBalanced PowerProfile = "balanced"
|
||||
PowerProfilePerformance PowerProfile = "performance"
|
||||
PowerProfilePowerSaving PowerProfile = "power_saving"
|
||||
PowerProfileCustom PowerProfile = "custom"
|
||||
)
|
||||
|
||||
// UseCase represents different system use cases
|
||||
type UseCase string
|
||||
|
||||
const (
|
||||
UseCaseGeneral UseCase = "general"
|
||||
UseCaseDevelopment UseCase = "development"
|
||||
UseCaseGaming UseCase = "gaming"
|
||||
UseCaseServer UseCase = "server"
|
||||
UseCaseMultimedia UseCase = "multimedia"
|
||||
UseCaseOffice UseCase = "office"
|
||||
)
|
||||
|
||||
// BatteryPriority represents battery optimization priority
|
||||
type BatteryPriority string
|
||||
|
||||
const (
|
||||
BatteryPriorityLongevity BatteryPriority = "longevity"
|
||||
BatteryPriorityRuntime BatteryPriority = "runtime"
|
||||
BatteryPriorityBalanced BatteryPriority = "balanced"
|
||||
)
|
||||
|
||||
// PerformanceMode represents performance optimization mode
|
||||
type PerformanceMode string
|
||||
|
||||
const (
|
||||
PerformanceModeMaximum PerformanceMode = "maximum"
|
||||
PerformanceModeAdaptive PerformanceMode = "adaptive"
|
||||
PerformanceModeEfficient PerformanceMode = "efficient"
|
||||
)
|
||||
|
||||
// AIProvider represents different AI service providers
|
||||
type AIProvider string
|
||||
|
||||
const (
|
||||
AIProviderOpenRouter AIProvider = "openrouter"
|
||||
AIProviderGroq AIProvider = "groq"
|
||||
AIProviderGemini AIProvider = "gemini"
|
||||
AIProviderCustom AIProvider = "custom"
|
||||
)
|
||||
|
||||
// AIConfig represents AI service configuration
|
||||
type AIConfig struct {
|
||||
Provider AIProvider `json:"provider"`
|
||||
APIKey string `json:"-"` // Never serialize API keys
|
||||
Endpoint string `json:"endpoint"`
|
||||
Model string `json:"model"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
}
|
||||
|
||||
// TLPConfiguration represents a complete TLP configuration
|
||||
type TLPConfiguration struct {
|
||||
Settings map[string]string `json:"settings"`
|
||||
Description string `json:"description"`
|
||||
Rationale map[string]string `json:"rationale"`
|
||||
Warnings []string `json:"warnings"`
|
||||
Generated time.Time `json:"generated"`
|
||||
SystemInfo *SystemInfo `json:"system_info"`
|
||||
Preferences *UserPreferences `json:"preferences"`
|
||||
}
|
||||
|
||||
// InstallationResult represents the result of TLP installation
|
||||
type InstallationResult struct {
|
||||
Success bool `json:"success"`
|
||||
Version string `json:"version"`
|
||||
Message string `json:"message"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
}
|
||||
|
||||
// ValidationResult represents configuration validation result
|
||||
type ValidationResult struct {
|
||||
Valid bool `json:"valid"`
|
||||
Errors []string `json:"errors"`
|
||||
Warnings []string `json:"warnings"`
|
||||
}
|
233
pkg/utils/helpers.go
Normal file
233
pkg/utils/helpers.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// FileExists checks if a file exists
|
||||
func FileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// ReadFileLines reads a file and returns its lines
|
||||
func ReadFileLines(path string) ([]string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var lines []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
return lines, scanner.Err()
|
||||
}
|
||||
|
||||
// ReadFirstLine reads the first line of a file
|
||||
func ReadFirstLine(path string) (string, error) {
|
||||
lines, err := ReadFileLines(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
return "", fmt.Errorf("file is empty")
|
||||
}
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
// ParseKeyValue parses a key=value string
|
||||
func ParseKeyValue(line string) (string, string, bool) {
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", "", false
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
// Remove quotes if present
|
||||
value = strings.Trim(value, `"'`)
|
||||
return key, value, true
|
||||
}
|
||||
|
||||
// ParseInt64 safely parses a string to int64
|
||||
func ParseInt64(s string) int64 {
|
||||
val, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// ParseInt safely parses a string to int
|
||||
func ParseInt(s string) int {
|
||||
val, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// IsRoot checks if the current user is root
|
||||
func IsRoot() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
|
||||
// RunCommand executes a command and returns its output
|
||||
func RunCommand(name string, args ...string) (string, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
// RunCommandWithInput executes a command with stdin input
|
||||
func RunCommandWithInput(input, name string, args ...string) (string, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Stdin = strings.NewReader(input)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
// CommandExists checks if a command exists in PATH
|
||||
func CommandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// GetUserConfirmation prompts user for yes/no confirmation
|
||||
func GetUserConfirmation(prompt string) bool {
|
||||
fmt.Printf("%s (y/N): ", prompt)
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
return response == "y" || response == "yes"
|
||||
}
|
||||
|
||||
// GetUserInput prompts user for input with a default value
|
||||
func GetUserInput(prompt, defaultValue string) string {
|
||||
if defaultValue != "" {
|
||||
fmt.Printf("%s [%s]: ", prompt, defaultValue)
|
||||
} else {
|
||||
fmt.Printf("%s: ", prompt)
|
||||
}
|
||||
|
||||
var input string
|
||||
fmt.Scanln(&input)
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
if input == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
// EnsureRoot ensures the program is running with root privileges
|
||||
func EnsureRoot() error {
|
||||
if !IsRoot() {
|
||||
return fmt.Errorf("this operation requires root privileges")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestElevation requests privilege elevation using sudo
|
||||
func RequestElevation(args []string) error {
|
||||
if IsRoot() {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("This operation requires elevated privileges.")
|
||||
if !GetUserConfirmation("Continue with sudo?") {
|
||||
return fmt.Errorf("operation cancelled by user")
|
||||
}
|
||||
|
||||
// Prepare sudo command
|
||||
sudoArgs := append([]string{os.Args[0]}, args...)
|
||||
cmd := exec.Command("sudo", sudoArgs...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// Execute with sudo
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
|
||||
os.Exit(status.ExitStatus())
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("failed to execute with elevated privileges: %w", err)
|
||||
}
|
||||
|
||||
// Exit successfully since the elevated process completed
|
||||
os.Exit(0)
|
||||
return nil // This line will never be reached
|
||||
}
|
||||
|
||||
// SanitizeInput sanitizes user input by removing potentially dangerous characters
|
||||
func SanitizeInput(input string) string {
|
||||
// Remove null bytes and control characters
|
||||
sanitized := strings.ReplaceAll(input, "\x00", "")
|
||||
sanitized = strings.TrimSpace(sanitized)
|
||||
|
||||
// Basic validation - reject inputs with shell metacharacters
|
||||
dangerous := []string{";", "&", "|", "`", "$", "(", ")", "<", ">", "\"", "'"}
|
||||
for _, char := range dangerous {
|
||||
if strings.Contains(sanitized, char) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized
|
||||
}
|
||||
|
||||
// FormatBytes formats bytes into human-readable format
|
||||
func FormatBytes(bytes int64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
}
|
||||
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
|
||||
units := []string{"KB", "MB", "GB", "TB", "PB"}
|
||||
return fmt.Sprintf("%.1f %s", float64(bytes)/float64(div), units[exp])
|
||||
}
|
||||
|
||||
// Contains checks if a slice contains a string
|
||||
func Contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveEmpty removes empty strings from a slice
|
||||
func RemoveEmpty(slice []string) []string {
|
||||
var result []string
|
||||
for _, s := range slice {
|
||||
if strings.TrimSpace(s) != "" {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
174
pkg/utils/helpers_test.go
Normal file
174
pkg/utils/helpers_test.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
tempFile, err := os.CreateTemp("", "test_file")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer tempFile.Close()
|
||||
|
||||
if !FileExists(tempFile.Name()) {
|
||||
t.Errorf("FileExists should return true for existing file")
|
||||
}
|
||||
|
||||
if FileExists("/non/existent/file") {
|
||||
t.Errorf("FileExists should return false for non-existing file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKeyValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expectedKey string
|
||||
expectedVal string
|
||||
expectedOk bool
|
||||
}{
|
||||
{"key=value", "key", "value", true},
|
||||
{"key = value", "key", "value", true},
|
||||
{"key=\"quoted value\"", "key", "quoted value", true},
|
||||
{"key='single quoted'", "key", "single quoted", true},
|
||||
{"invalid_line", "", "", false},
|
||||
{"key=", "key", "", true},
|
||||
{"=value", "", "value", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
key, value, ok := ParseKeyValue(test.input)
|
||||
if key != test.expectedKey || value != test.expectedVal || ok != test.expectedOk {
|
||||
t.Errorf("ParseKeyValue(%q) = (%q, %q, %v), want (%q, %q, %v)",
|
||||
test.input, key, value, ok, test.expectedKey, test.expectedVal, test.expectedOk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInt64(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"123", 123},
|
||||
{"0", 0},
|
||||
{"-456", -456},
|
||||
{"invalid", 0},
|
||||
{"", 0},
|
||||
{"123.45", 0}, // Should fail for float
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := ParseInt64(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("ParseInt64(%q) = %d, want %d", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInt(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int
|
||||
}{
|
||||
{"123", 123},
|
||||
{"0", 0},
|
||||
{"-456", -456},
|
||||
{"invalid", 0},
|
||||
{"", 0},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := ParseInt(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("ParseInt(%q) = %d, want %d", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"clean_input", "clean_input"},
|
||||
{" spaced ", "spaced"},
|
||||
{"with;semicolon", ""},
|
||||
{"with&ersand", ""},
|
||||
{"with|pipe", ""},
|
||||
{"with`backtick", ""},
|
||||
{"with$dollar", ""},
|
||||
{"with(paren", ""},
|
||||
{"with\"quote", ""},
|
||||
{"with'quote", ""},
|
||||
{"normal_text_123", "normal_text_123"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := SanitizeInput(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("SanitizeInput(%q) = %q, want %q", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
input int64
|
||||
expected string
|
||||
}{
|
||||
{500, "500 B"},
|
||||
{1024, "1.0 KB"},
|
||||
{1536, "1.5 KB"},
|
||||
{1048576, "1.0 MB"},
|
||||
{1073741824, "1.0 GB"},
|
||||
{0, "0 B"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := FormatBytes(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("FormatBytes(%d) = %q, want %q", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
slice := []string{"apple", "banana", "cherry"}
|
||||
|
||||
tests := []struct {
|
||||
item string
|
||||
expected bool
|
||||
}{
|
||||
{"apple", true},
|
||||
{"banana", true},
|
||||
{"cherry", true},
|
||||
{"grape", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := Contains(slice, test.item)
|
||||
if result != test.expected {
|
||||
t.Errorf("Contains(slice, %q) = %v, want %v", test.item, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveEmpty(t *testing.T) {
|
||||
input := []string{"apple", "", "banana", " ", "cherry", "\t\n"}
|
||||
expected := []string{"apple", "banana", "cherry"}
|
||||
|
||||
result := RemoveEmpty(input)
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("RemoveEmpty() returned slice of length %d, want %d", len(result), len(expected))
|
||||
}
|
||||
|
||||
for i, item := range result {
|
||||
if item != expected[i] {
|
||||
t.Errorf("RemoveEmpty()[%d] = %q, want %q", i, item, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
83
pkg/utils/logger.go
Normal file
83
pkg/utils/logger.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Logger provides structured logging capabilities
|
||||
type Logger struct {
|
||||
*slog.Logger
|
||||
}
|
||||
|
||||
// NewLogger creates a new structured logger
|
||||
func NewLogger() *Logger {
|
||||
return NewLoggerWithOutput(os.Stdout)
|
||||
}
|
||||
|
||||
// NewDebugLogger creates a new logger with debug level enabled
|
||||
func NewDebugLogger() *Logger {
|
||||
opts := &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug,
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
// Customize timestamp format
|
||||
if a.Key == slog.TimeKey {
|
||||
return slog.Attr{
|
||||
Key: a.Key,
|
||||
Value: slog.StringValue(time.Now().Format("2006-01-02 15:04:05")),
|
||||
}
|
||||
}
|
||||
return a
|
||||
},
|
||||
}
|
||||
|
||||
handler := slog.NewTextHandler(os.Stdout, opts)
|
||||
logger := slog.New(handler)
|
||||
|
||||
return &Logger{Logger: logger}
|
||||
}
|
||||
|
||||
// NewLoggerWithOutput creates a new logger with custom output
|
||||
func NewLoggerWithOutput(w io.Writer) *Logger {
|
||||
opts := &slog.HandlerOptions{
|
||||
Level: slog.LevelInfo,
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
// Customize timestamp format
|
||||
if a.Key == slog.TimeKey {
|
||||
return slog.Attr{
|
||||
Key: a.Key,
|
||||
Value: slog.StringValue(time.Now().Format("2006-01-02 15:04:05")),
|
||||
}
|
||||
}
|
||||
return a
|
||||
},
|
||||
}
|
||||
|
||||
handler := slog.NewTextHandler(w, opts)
|
||||
logger := slog.New(handler)
|
||||
|
||||
return &Logger{Logger: logger}
|
||||
}
|
||||
|
||||
// NewSilentLogger creates a logger that discards all output
|
||||
func NewSilentLogger() *Logger {
|
||||
return NewLoggerWithOutput(io.Discard)
|
||||
}
|
||||
|
||||
// WithComponent adds a component context to the logger
|
||||
func (l *Logger) WithComponent(component string) *Logger {
|
||||
return &Logger{Logger: l.Logger.With("component", component)}
|
||||
}
|
||||
|
||||
// WithRequestID adds a request ID context to the logger
|
||||
func (l *Logger) WithRequestID(requestID string) *Logger {
|
||||
return &Logger{Logger: l.Logger.With("request_id", requestID)}
|
||||
}
|
||||
|
||||
// Fatal logs a fatal error and exits the program
|
||||
func (l *Logger) Fatal(msg string, args ...interface{}) {
|
||||
l.Error(msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
Reference in New Issue
Block a user