Compare commits
	
		
			11 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a6805f8990 | |||
| 02fed6e55f | |||
| 1712042f64 | |||
| f8a72d3579 | |||
| 65cbfe78c3 | |||
| 6d60f7f813 | |||
| d9564c2b98 | |||
| 04f9b0db6e | |||
| 93557356e9 | |||
| 13a7d8db91 | |||
| bdad721b3d | 
							
								
								
									
										146
									
								
								colors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								colors.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | // Package cylogger provides ANSI color constants and utilities for terminal output. | ||||||
|  | // This file contains all the color constants used by the logger for styling log messages. | ||||||
|  | // It includes regular colors, bold colors, underlined colors, background colors, | ||||||
|  | // high intensity colors, and utility functions for generating random colors. | ||||||
|  | package cylogger | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"math/rand/v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Reset | ||||||
|  | 	Reset = "\033[0m" // Text Reset | ||||||
|  |  | ||||||
|  | 	// Regular Colors | ||||||
|  | 	Black   = "\033[0;30m"     // Black | ||||||
|  | 	Red     = "\033[0;31m"     // Red | ||||||
|  | 	Green   = "\033[0;32m"     // Green | ||||||
|  | 	Yellow  = "\033[0;33m"     // Yellow | ||||||
|  | 	Blue    = "\033[0;34m"     // Blue | ||||||
|  | 	Purple  = "\033[0;35m"     // Purple | ||||||
|  | 	Cyan    = "\033[0;36m"     // Cyan | ||||||
|  | 	White   = "\033[0;37m"     // White | ||||||
|  | 	Orange  = "\033[38;5;208m" // Orange | ||||||
|  | 	Pink    = "\033[38;5;205m" // Pink | ||||||
|  | 	Brown   = "\033[38;5;130m" // Brown | ||||||
|  | 	Gray    = "\033[38;5;240m" // Gray | ||||||
|  | 	Magenta = "\033[38;5;201m" // Magenta | ||||||
|  |  | ||||||
|  | 	// Bold | ||||||
|  | 	BBlack   = "\033[1;30m"       // Black | ||||||
|  | 	BRed     = "\033[1;31m"       // Red | ||||||
|  | 	BGreen   = "\033[1;32m"       // Green | ||||||
|  | 	BYellow  = "\033[1;33m"       // Yellow | ||||||
|  | 	BBlue    = "\033[1;34m"       // Blue | ||||||
|  | 	BPurple  = "\033[1;35m"       // Purple | ||||||
|  | 	BCyan    = "\033[1;36m"       // Cyan | ||||||
|  | 	BWhite   = "\033[1;37m"       // White | ||||||
|  | 	BOrange  = "\033[1;38;5;208m" // Bold Orange | ||||||
|  | 	BPink    = "\033[1;38;5;205m" // Bold Pink | ||||||
|  | 	BBrown   = "\033[1;38;5;130m" // Bold Brown | ||||||
|  | 	BGray    = "\033[1;38;5;240m" // Bold Gray | ||||||
|  | 	BMagenta = "\033[1;38;5;201m" // Bold Magenta | ||||||
|  |  | ||||||
|  | 	// Underline | ||||||
|  | 	UBlack   = "\033[4;30m"       // Black | ||||||
|  | 	URed     = "\033[4;31m"       // Red | ||||||
|  | 	UGreen   = "\033[4;32m"       // Green | ||||||
|  | 	UYellow  = "\033[4;33m"       // Yellow | ||||||
|  | 	UBlue    = "\033[4;34m"       // Blue | ||||||
|  | 	UPurple  = "\033[4;35m"       // Purple | ||||||
|  | 	UCyan    = "\033[4;36m"       // Cyan | ||||||
|  | 	UWhite   = "\033[4;37m"       // White | ||||||
|  | 	UOrange  = "\033[4;38;5;208m" // Underline Orange | ||||||
|  | 	UPink    = "\033[4;38;5;205m" // Underline Pink | ||||||
|  | 	UBrown   = "\033[4;38;5;130m" // Underline Brown | ||||||
|  | 	UGray    = "\033[4;38;5;240m" // Underline Gray | ||||||
|  | 	UMagenta = "\033[4;38;5;201m" // Underline Magenta | ||||||
|  |  | ||||||
|  | 	// Background | ||||||
|  | 	On_Black   = "\033[40m"       // Black | ||||||
|  | 	On_Red     = "\033[41m"       // Red | ||||||
|  | 	On_Green   = "\033[42m"       // Green | ||||||
|  | 	On_Yellow  = "\033[43m"       // Yellow | ||||||
|  | 	On_Blue    = "\033[44m"       // Blue | ||||||
|  | 	On_Purple  = "\033[45m"       // Purple | ||||||
|  | 	On_Cyan    = "\033[46m"       // Cyan | ||||||
|  | 	On_White   = "\033[47m"       // White | ||||||
|  | 	On_Orange  = "\033[48;5;208m" // Orange Background | ||||||
|  | 	On_Pink    = "\033[48;5;205m" // Pink Background | ||||||
|  | 	On_Brown   = "\033[48;5;130m" // Brown Background | ||||||
|  | 	On_Gray    = "\033[48;5;240m" // Gray Background | ||||||
|  | 	On_Magenta = "\033[48;5;201m" // Magenta Background | ||||||
|  |  | ||||||
|  | 	// High Intensty | ||||||
|  | 	IBlack   = "\033[0;90m"       // Black | ||||||
|  | 	IRed     = "\033[0;91m"       // Red | ||||||
|  | 	IGreen   = "\033[0;92m"       // Green | ||||||
|  | 	IYellow  = "\033[0;93m"       // Yellow | ||||||
|  | 	IBlue    = "\033[0;94m"       // Blue | ||||||
|  | 	IPurple  = "\033[0;95m"       // Purple | ||||||
|  | 	ICyan    = "\033[0;96m"       // Cyan | ||||||
|  | 	IWhite   = "\033[0;97m"       // White | ||||||
|  | 	IOrange  = "\033[0;38;5;208m" // Intense Orange | ||||||
|  | 	IPink    = "\033[0;38;5;205m" // Intense Pink | ||||||
|  | 	IBrown   = "\033[0;38;5;130m" // Intense Brown | ||||||
|  | 	IGray    = "\033[0;38;5;240m" // Intense Gray | ||||||
|  | 	IMagenta = "\033[0;38;5;201m" // Intense Magenta | ||||||
|  |  | ||||||
|  | 	// Bold High Intensty | ||||||
|  | 	BIBlack   = "\033[1;90m"       // Black | ||||||
|  | 	BIRed     = "\033[1;91m"       // Red | ||||||
|  | 	BIGreen   = "\033[1;92m"       // Green | ||||||
|  | 	BIYellow  = "\033[1;93m"       // Yellow | ||||||
|  | 	BIBlue    = "\033[1;94m"       // Blue | ||||||
|  | 	BIPurple  = "\033[1;95m"       // Purple | ||||||
|  | 	BICyan    = "\033[1;96m"       // Cyan | ||||||
|  | 	BIWhite   = "\033[1;97m"       // White | ||||||
|  | 	BIOrange  = "\033[1;38;5;208m" // Bold Intense Orange | ||||||
|  | 	BIPink    = "\033[1;38;5;205m" // Bold Intense Pink | ||||||
|  | 	BIBrown   = "\033[1;38;5;130m" // Bold Intense Brown | ||||||
|  | 	BIGray    = "\033[1;38;5;240m" // Bold Intense Gray | ||||||
|  | 	BIMagenta = "\033[1;38;5;201m" // Bold Intense Magenta | ||||||
|  |  | ||||||
|  | 	// High Intensty backgrounds | ||||||
|  | 	On_IBlack   = "\033[0;100m"      // Black | ||||||
|  | 	On_IRed     = "\033[0;101m"      // Red | ||||||
|  | 	On_IGreen   = "\033[0;102m"      // Green | ||||||
|  | 	On_IYellow  = "\033[0;103m"      // Yellow | ||||||
|  | 	On_IBlue    = "\033[0;104m"      // Blue | ||||||
|  | 	On_IPurple  = "\033[10;95m"      // Purple | ||||||
|  | 	On_ICyan    = "\033[0;106m"      // Cyan | ||||||
|  | 	On_IWhite   = "\033[0;107m"      // White | ||||||
|  | 	On_IOrange  = "\033[0;48;5;208m" // Intense Orange Background | ||||||
|  | 	On_IPink    = "\033[0;48;5;205m" // Intense Pink Background | ||||||
|  | 	On_IBrown   = "\033[0;48;5;130m" // Intense Brown Background | ||||||
|  | 	On_IGray    = "\033[0;48;5;240m" // Intense Gray Background | ||||||
|  | 	On_IMagenta = "\033[0;48;5;201m" // Intense Magenta Background | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // The acceptable range is [16, 231] but here we remove some very dark colors | ||||||
|  | // That make text unreadable on a dark terminal | ||||||
|  | // See https://www.hackitu.de/termcolor256/ | ||||||
|  | var colors = []int{22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 57, 62, 63, 64, 65, 67, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 184, 185, 186, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 226, 227, 228, 229, 230} | ||||||
|  | var colorsIndex int = -1 | ||||||
|  | var shuffled bool | ||||||
|  |  | ||||||
|  | // GenerateRandomAnsiColor generates a random ANSI color code from a curated set of colors. | ||||||
|  | // The colors are shuffled once and then cycled through in order. | ||||||
|  | // This is useful for generating unique colors for different log sources or components. | ||||||
|  | // The function returns a formatted ANSI color code that can be used for text styling. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	color := cylogger.GenerateRandomAnsiColor() | ||||||
|  | //	fmt.Printf("%sColored text%s", color, cylogger.Reset) | ||||||
|  | func GenerateRandomAnsiColor() string { | ||||||
|  | 	if !shuffled { | ||||||
|  | 		rand.Shuffle(len(colors), func(i int, j int) { | ||||||
|  | 			colors[i], colors[j] = colors[j], colors[i] | ||||||
|  | 		}) | ||||||
|  | 		shuffled = true | ||||||
|  | 	} | ||||||
|  | 	colorsIndex++ | ||||||
|  | 	return fmt.Sprintf("\033[1;4;38;5;%dm", colors[colorsIndex%len(colors)]) | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,3 +1,13 @@ | |||||||
| module git.site.quack-lab.dev/dave/cylogger | module git.site.quack-lab.dev/dave/cylogger | ||||||
|  |  | ||||||
| go 1.24.2 | go 1.23 | ||||||
|  |  | ||||||
|  | require github.com/hexops/valast v1.5.0 | ||||||
|  |  | ||||||
|  | require ( | ||||||
|  | 	github.com/google/go-cmp v0.5.9 // indirect | ||||||
|  | 	golang.org/x/mod v0.7.0 // indirect | ||||||
|  | 	golang.org/x/sys v0.3.0 // indirect | ||||||
|  | 	golang.org/x/tools v0.4.0 // indirect | ||||||
|  | 	mvdan.cc/gofumpt v0.4.0 // indirect | ||||||
|  | ) | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= | ||||||
|  | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= | ||||||
|  | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||||||
|  | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/hexops/autogold v0.8.1 h1:wvyd/bAJ+Dy+DcE09BoLk6r4Fa5R5W+O+GUzmR985WM= | ||||||
|  | github.com/hexops/autogold v0.8.1/go.mod h1:97HLDXyG23akzAoRYJh/2OBs3kd80eHyKPvZw0S5ZBY= | ||||||
|  | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | ||||||
|  | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||||||
|  | github.com/hexops/valast v1.5.0 h1:FBTuvVi0wjTngtXJRZXMbkN/Dn6DgsUsBwch2DUJU8Y= | ||||||
|  | github.com/hexops/valast v1.5.0/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4= | ||||||
|  | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= | ||||||
|  | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= | ||||||
|  | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||||
|  | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
|  | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= | ||||||
|  | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||||||
|  | golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= | ||||||
|  | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||||
|  | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= | ||||||
|  | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= | ||||||
|  | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= | ||||||
|  | golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= | ||||||
|  | mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM= | ||||||
|  | mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= | ||||||
							
								
								
									
										633
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										633
									
								
								main.go
									
									
									
									
									
								
							| @@ -1,4 +1,29 @@ | |||||||
| package logger | // Package cylogger provides a feature-rich, colored logging library for Go applications. | ||||||
|  | // It offers structured logging with multiple levels, colored output, goroutine tracking, | ||||||
|  | // panic recovery, and object dumping capabilities. | ||||||
|  | // | ||||||
|  | // Key Features: | ||||||
|  | //   - Multiple log levels (Error, Warning, Info, Debug, Trace, Dump, Lua) | ||||||
|  | //   - Colored terminal output with customizable styling | ||||||
|  | //   - Structured logging with fields and context | ||||||
|  | //   - Goroutine ID tracking | ||||||
|  | //   - Panic recovery and safe goroutine execution | ||||||
|  | //   - Object dumping for debugging and testing | ||||||
|  | //   - Multiple output destinations (stdout, files) | ||||||
|  | //   - Thread-safe operations | ||||||
|  | // | ||||||
|  | // Basic Usage: | ||||||
|  | // | ||||||
|  | //	cylogger.Init(cylogger.LevelInfo) | ||||||
|  | //	cylogger.Info("Application started") | ||||||
|  | //	cylogger.WithField("user", "john").Info("User logged in") | ||||||
|  | // | ||||||
|  | // Advanced Usage: | ||||||
|  | // | ||||||
|  | //	logger := cylogger.New(os.Stdout, "", log.LstdFlags) | ||||||
|  | //	logger.SetLevel(cylogger.LevelDebug) | ||||||
|  | //	logger.WithFields(map[string]interface{}{"service": "api", "version": "1.0"}).Info("Service initialized") | ||||||
|  | package cylogger | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| @@ -13,87 +38,166 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/hexops/valast" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // TODO: Enable turning colors on and off maybe even per stream? | // TODO: Enable turning colors on and off maybe even per stream? | ||||||
|  |  | ||||||
| var loglevel = flag.String("loglevel", "info", "log level") | var loglevel = flag.String("loglevel", "info", "log level") | ||||||
|  |  | ||||||
| // LogLevel defines the severity of log messages | // LogLevel defines the severity and type of log messages. | ||||||
|  | // Lower values indicate higher priority. Messages are only logged if their level | ||||||
|  | // is less than or equal to the current logger level. | ||||||
| type LogLevel int | type LogLevel int | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	// LevelError is for critical errors that should always be displayed | 	// LevelError is for critical errors that should always be displayed. | ||||||
|  | 	// These represent serious problems that may cause the application to fail. | ||||||
| 	LevelError LogLevel = iota | 	LevelError LogLevel = iota | ||||||
| 	// LevelWarning is for important warnings |  | ||||||
|  | 	// LevelWarning is for important warnings that indicate potential issues. | ||||||
|  | 	// These represent situations that are not errors but may require attention. | ||||||
| 	LevelWarning | 	LevelWarning | ||||||
| 	// LevelInfo is for informational messages |  | ||||||
|  | 	// LevelInfo is for general informational messages about application flow. | ||||||
|  | 	// These provide context about what the application is doing. | ||||||
| 	LevelInfo | 	LevelInfo | ||||||
| 	// LevelDebug is for detailed debugging information |  | ||||||
|  | 	// LevelDebug is for detailed debugging information. | ||||||
|  | 	// These are useful for diagnosing problems during development. | ||||||
| 	LevelDebug | 	LevelDebug | ||||||
| 	// LevelTrace is for very detailed tracing information |  | ||||||
|  | 	// LevelTrace is for very detailed tracing information. | ||||||
|  | 	// These provide the most verbose output for deep debugging. | ||||||
| 	LevelTrace | 	LevelTrace | ||||||
| 	// LevelLua is specifically for output from Lua scripts |  | ||||||
|  | 	// LevelDump is for dumping objects to console for regressive tests. | ||||||
|  | 	// This level is specifically designed for object inspection and testing. | ||||||
|  | 	LevelDump | ||||||
|  |  | ||||||
|  | 	// LevelLua is specifically for output from Lua scripts. | ||||||
|  | 	// This level bypasses normal level filtering and is always shown. | ||||||
| 	LevelLua | 	LevelLua | ||||||
|  |  | ||||||
|  | 	// LevelPrefix is used internally for styling user prefixes. | ||||||
|  | 	// This is not a logging level but a style identifier. | ||||||
| 	LevelPrefix | 	LevelPrefix | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var levelNames = map[LogLevel]string{ | // LevelStyle defines the visual styling configuration for a log level. | ||||||
| 	LevelError:   "ERROR", | // It controls how log messages appear in the terminal with colors and formatting. | ||||||
| 	LevelWarning: "WARNING", | type LevelStyle struct { | ||||||
| 	LevelInfo:    "INFO", | 	// Tag is the text label displayed for this log level (e.g., "ERROR", "INFO") | ||||||
| 	LevelDebug:   "DEBUG", | 	Tag string | ||||||
| 	LevelTrace:   "TRACE", |  | ||||||
| 	LevelLua:     "LUA", | 	// TagColor is the ANSI color code for the tag text | ||||||
| 	LevelPrefix:  "PREFIX", | 	TagColor string | ||||||
|  |  | ||||||
|  | 	// TagBackgroundColor is the ANSI color code for the tag background | ||||||
|  | 	TagBackgroundColor string | ||||||
|  |  | ||||||
|  | 	// MessageColor is the ANSI color code for the message text | ||||||
|  | 	MessageColor string | ||||||
|  |  | ||||||
|  | 	// MessageBackgroundColor is the ANSI color code for the message background | ||||||
|  | 	MessageBackgroundColor string | ||||||
| } | } | ||||||
|  |  | ||||||
| var levelColors = map[LogLevel]string{ | // levelStyles maps LogLevel to its display style | ||||||
| 	LevelError:   "\033[1;31m", // Bold Red | var levelStyles = map[LogLevel]LevelStyle{ | ||||||
| 	LevelWarning: "\033[1;33m", // Bold Yellow | 	LevelError: { | ||||||
| 	LevelInfo:    "\033[1;32m", // Bold Green | 		Tag:                    "ERROR", | ||||||
| 	LevelDebug:   "\033[1;36m", // Bold Cyan | 		TagColor:               BIRed,    // Bold Intense Red | ||||||
| 	LevelTrace:   "\033[1;35m", // Bold Magenta | 		TagBackgroundColor:     On_White, // White background | ||||||
| 	LevelLua:     "\033[1;34m", // Bold Blue | 		MessageColor:           White,    // Bold White text | ||||||
| 	LevelPrefix:  "\033[0;90m", // Regular Dark Grey | 		MessageBackgroundColor: On_IRed,  // Intense Red background | ||||||
|  | 	}, | ||||||
|  | 	LevelWarning: { | ||||||
|  | 		Tag:                    "WARNING", | ||||||
|  | 		TagColor:               BIOrange,   // Bold Intense Orange | ||||||
|  | 		TagBackgroundColor:     On_White,   // White background | ||||||
|  | 		MessageColor:           White,      // Bold White text | ||||||
|  | 		MessageBackgroundColor: On_IOrange, // Intense Orange background | ||||||
|  | 	}, | ||||||
|  | 	LevelInfo: { | ||||||
|  | 		Tag:      "INFO", | ||||||
|  | 		TagColor: BGreen, // Bold Green | ||||||
|  | 	}, | ||||||
|  | 	LevelDebug: { | ||||||
|  | 		Tag:      "DEBUG", | ||||||
|  | 		TagColor: BCyan, // Bold Cyan | ||||||
|  | 	}, | ||||||
|  | 	LevelTrace: { | ||||||
|  | 		Tag:      "TRACE", | ||||||
|  | 		TagColor: BPurple, // Bold Purple | ||||||
|  | 	}, | ||||||
|  | 	LevelDump: { | ||||||
|  | 		Tag:                "DUMP", | ||||||
|  | 		TagColor:           BIMagenta, // Bold Intense Magenta | ||||||
|  | 		TagBackgroundColor: On_White,  // White background | ||||||
|  | 		MessageColor:       IMagenta,  // White text | ||||||
|  | 	}, | ||||||
|  | 	LevelLua: { | ||||||
|  | 		Tag:      "LUA", | ||||||
|  | 		TagColor: BBlue, // Bold Blue | ||||||
|  | 	}, | ||||||
|  | 	LevelPrefix: { | ||||||
|  | 		Tag:      "PREFIX", // Used for coloring the user prefix | ||||||
|  | 		TagColor: BIGray,   // Bold Gray | ||||||
|  | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| // ANSI Background Colors | // Logger is a thread-safe, feature-rich logger that supports multiple output destinations, | ||||||
| var levelBackgroundColors = map[LogLevel]string{ | // structured logging, colored output, and various log levels. It can be used both as a | ||||||
| 	LevelError:   "\033[41m", // Red Background | // standalone logger or as part of a structured logging system with context fields. | ||||||
| 	LevelWarning: "\033[43m", // Yellow Background |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ANSI Foreground Colors (adjusting for readability on backgrounds) |  | ||||||
| const FgWhiteBold = "\033[1;37m" |  | ||||||
|  |  | ||||||
| // ResetColor is the ANSI code to reset text color |  | ||||||
| const ResetColor = "\033[0m" |  | ||||||
|  |  | ||||||
| // Logger is our custom logger with level support |  | ||||||
| type Logger struct { | type Logger struct { | ||||||
| 	mu            sync.Mutex | 	// mu protects all fields from concurrent access | ||||||
| 	out           io.Writer | 	mu sync.Mutex | ||||||
| 	currentLevel  LogLevel |  | ||||||
| 	prefix        string | 	// out is the list of output destinations for log messages | ||||||
| 	userPrefix    string | 	out []io.Writer | ||||||
| 	flag          int |  | ||||||
| 	useColors     bool | 	// currentLevel determines which log messages are actually written | ||||||
| 	callerOffset  int | 	currentLevel LogLevel | ||||||
|  |  | ||||||
|  | 	// prefix is the standard Go log prefix (timestamp, file, etc.) | ||||||
|  | 	prefix string | ||||||
|  |  | ||||||
|  | 	// userPrefix is a custom user-defined prefix for log messages | ||||||
|  | 	userPrefix string | ||||||
|  |  | ||||||
|  | 	// flag controls which standard log information is included (timestamp, file, line, etc.) | ||||||
|  | 	flag int | ||||||
|  |  | ||||||
|  | 	// useColors determines whether ANSI color codes are included in output | ||||||
|  | 	useColors bool | ||||||
|  |  | ||||||
|  | 	// callerOffset adjusts the stack depth for caller information | ||||||
|  | 	callerOffset int | ||||||
|  |  | ||||||
|  | 	// defaultFields are key-value pairs included in all log messages from this logger | ||||||
| 	defaultFields map[string]interface{} | 	defaultFields map[string]interface{} | ||||||
|  |  | ||||||
|  | 	// showGoroutine determines whether goroutine ID is included in log messages | ||||||
| 	showGoroutine bool | 	showGoroutine bool | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// Default is the global logger instance | 	// Default is the global logger instance used by package-level functions. | ||||||
|  | 	// It is automatically initialized when first used if not explicitly set. | ||||||
| 	Default *Logger | 	Default *Logger | ||||||
| 	// defaultLogLevel is the default log level if not specified |  | ||||||
|  | 	// defaultLogLevel is the default log level used when initializing the logger | ||||||
| 	defaultLogLevel = LevelInfo | 	defaultLogLevel = LevelInfo | ||||||
| 	// Global mutex for DefaultLogger initialization |  | ||||||
|  | 	// initMutex protects the initialization of the Default logger from race conditions | ||||||
| 	initMutex sync.Mutex | 	initMutex sync.Mutex | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ParseLevel converts a string log level to LogLevel | // ParseLevel converts a string representation of a log level to the corresponding LogLevel. | ||||||
|  | // It accepts case-insensitive strings like "error", "warning", "info", "debug", "trace", "dump", and "lua". | ||||||
|  | // If the string is not recognized, it returns the default log level (LevelInfo). | ||||||
| func ParseLevel(levelStr string) LogLevel { | func ParseLevel(levelStr string) LogLevel { | ||||||
| 	switch strings.ToUpper(levelStr) { | 	switch strings.ToUpper(levelStr) { | ||||||
| 	case "ERROR": | 	case "ERROR": | ||||||
| @@ -106,6 +210,8 @@ func ParseLevel(levelStr string) LogLevel { | |||||||
| 		return LevelDebug | 		return LevelDebug | ||||||
| 	case "TRACE": | 	case "TRACE": | ||||||
| 		return LevelTrace | 		return LevelTrace | ||||||
|  | 	case "DUMP": | ||||||
|  | 		return LevelDump | ||||||
| 	case "LUA": | 	case "LUA": | ||||||
| 		return LevelLua | 		return LevelLua | ||||||
| 	default: | 	default: | ||||||
| @@ -113,18 +219,21 @@ func ParseLevel(levelStr string) LogLevel { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // String returns the string representation of the log level | // String returns the string representation of the log level. | ||||||
|  | // It returns the tag name (e.g., "ERROR", "INFO") or a formatted representation for unknown levels. | ||||||
| func (l LogLevel) String() string { | func (l LogLevel) String() string { | ||||||
| 	if name, ok := levelNames[l]; ok { | 	if name, ok := levelStyles[l]; ok { | ||||||
| 		return name | 		return name.Tag | ||||||
| 	} | 	} | ||||||
| 	return fmt.Sprintf("Level(%d)", l) | 	return fmt.Sprintf("Level(%d)", l) | ||||||
| } | } | ||||||
|  |  | ||||||
| // New creates a new Logger instance | // New creates a new Logger instance with the specified output destination, prefix, and flags. | ||||||
|  | // The logger is initialized with default settings: LevelInfo log level, colors enabled, | ||||||
|  | // goroutine tracking enabled, and no default fields. | ||||||
| func New(out io.Writer, prefix string, flag int) *Logger { | func New(out io.Writer, prefix string, flag int) *Logger { | ||||||
| 	return &Logger{ | 	return &Logger{ | ||||||
| 		out:           out, | 		out:           []io.Writer{out}, | ||||||
| 		currentLevel:  defaultLogLevel, | 		currentLevel:  defaultLogLevel, | ||||||
| 		prefix:        prefix, | 		prefix:        prefix, | ||||||
| 		userPrefix:    "", | 		userPrefix:    "", | ||||||
| @@ -136,12 +245,22 @@ func New(out io.Writer, prefix string, flag int) *Logger { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // InitFlag initializes the default logger using the loglevel flag value. | ||||||
|  | // This library defines a "loglevel" command-line flag that accepts values like "error", "warning", "info", "debug", "trace", "dump", or "lua". | ||||||
|  | // This function should be called after flag.Parse() to use command-line log level configuration. | ||||||
|  | // Example usage: | ||||||
|  | // | ||||||
|  | //	flag.Parse() | ||||||
|  | //	cylogger.InitFlag() | ||||||
|  | //	// Now the logger level is set based on the -loglevel flag | ||||||
| func InitFlag() { | func InitFlag() { | ||||||
| 	level := ParseLevel(*loglevel) | 	level := ParseLevel(*loglevel) | ||||||
| 	Init(level) | 	Init(level) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Init initializes the DefaultLogger | // Init initializes the Default logger with the specified log level. | ||||||
|  | // If the Default logger is already initialized, it only updates the log level. | ||||||
|  | // This function is thread-safe and can be called multiple times. | ||||||
| func Init(level LogLevel) { | func Init(level LogLevel) { | ||||||
| 	initMutex.Lock() | 	initMutex.Lock() | ||||||
| 	defer initMutex.Unlock() | 	defer initMutex.Unlock() | ||||||
| @@ -152,45 +271,63 @@ func Init(level LogLevel) { | |||||||
| 	Default.SetLevel(level) | 	Default.SetLevel(level) | ||||||
| } | } | ||||||
|  |  | ||||||
| // SetLevel sets the current log level | // SetLevel sets the current log level for the logger. | ||||||
|  | // Only messages with a level less than or equal to this level will be logged. | ||||||
|  | // This method is thread-safe. | ||||||
| func (l *Logger) SetLevel(level LogLevel) { | func (l *Logger) SetLevel(level LogLevel) { | ||||||
| 	l.mu.Lock() | 	l.mu.Lock() | ||||||
| 	defer l.mu.Unlock() | 	defer l.mu.Unlock() | ||||||
| 	l.currentLevel = level | 	l.currentLevel = level | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetLevel returns the current log level | // GetLevel returns the current log level of the logger. | ||||||
|  | // This method is thread-safe. | ||||||
| func (l *Logger) GetLevel() LogLevel { | func (l *Logger) GetLevel() LogLevel { | ||||||
| 	l.mu.Lock() | 	l.mu.Lock() | ||||||
| 	defer l.mu.Unlock() | 	defer l.mu.Unlock() | ||||||
| 	return l.currentLevel | 	return l.currentLevel | ||||||
| } | } | ||||||
|  |  | ||||||
| // SetCallerOffset sets the caller offset for correct file and line reporting | // SetCallerOffset sets the caller offset for correct file and line reporting. | ||||||
|  | // This is useful when the logger is wrapped in helper functions to ensure | ||||||
|  | // the correct caller information is displayed. | ||||||
| func (l *Logger) SetCallerOffset(offset int) { | func (l *Logger) SetCallerOffset(offset int) { | ||||||
| 	l.mu.Lock() | 	l.mu.Lock() | ||||||
| 	defer l.mu.Unlock() | 	defer l.mu.Unlock() | ||||||
| 	l.callerOffset = offset | 	l.callerOffset = offset | ||||||
| } | } | ||||||
|  |  | ||||||
| // SetShowGoroutine sets whether to include goroutine ID in log messages | // SetShowGoroutine sets whether to include goroutine ID in log messages. | ||||||
|  | // This is useful for debugging concurrent applications where you need to track | ||||||
|  | // which goroutine generated each log message. | ||||||
| func (l *Logger) SetShowGoroutine(show bool) { | func (l *Logger) SetShowGoroutine(show bool) { | ||||||
| 	l.mu.Lock() | 	l.mu.Lock() | ||||||
| 	defer l.mu.Unlock() | 	defer l.mu.Unlock() | ||||||
| 	l.showGoroutine = show | 	l.showGoroutine = show | ||||||
| } | } | ||||||
|  |  | ||||||
| // ShowGoroutine returns whether goroutine ID is included in log messages | // ShowGoroutine returns whether goroutine ID is included in log messages. | ||||||
|  | // This method is thread-safe. | ||||||
| func (l *Logger) ShowGoroutine() bool { | func (l *Logger) ShowGoroutine() bool { | ||||||
| 	l.mu.Lock() | 	l.mu.Lock() | ||||||
| 	defer l.mu.Unlock() | 	defer l.mu.Unlock() | ||||||
| 	return l.showGoroutine | 	return l.showGoroutine | ||||||
| } | } | ||||||
|  |  | ||||||
| // WithField adds a field to the logger's context | // WithField creates a new logger instance with an additional field in its context. | ||||||
|  | // The field will be included in all log messages from the returned logger, appearing as key=value pairs appended to the message. | ||||||
|  | // This is useful for structured logging where you want to add context to a group of log messages. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	logger := cylogger.WithField("user", "john") | ||||||
|  | //	logger.Info("User logged in")  // Output: "User logged in user=john" | ||||||
|  | //	logger.Error("Login failed")    // Output: "Login failed user=john" | ||||||
|  | // | ||||||
|  | // This is particularly useful for scoped logging in functions or loops where you want to track context | ||||||
|  | // without continuously logging an ID or identifier. | ||||||
| func (l *Logger) WithField(key string, value interface{}) *Logger { | func (l *Logger) WithField(key string, value interface{}) *Logger { | ||||||
| 	newLogger := &Logger{ | 	newLogger := &Logger{ | ||||||
| 		out:           l.out, | 		out:           append([]io.Writer(nil), l.out...), | ||||||
| 		currentLevel:  l.currentLevel, | 		currentLevel:  l.currentLevel, | ||||||
| 		prefix:        l.prefix, | 		prefix:        l.prefix, | ||||||
| 		userPrefix:    l.userPrefix, | 		userPrefix:    l.userPrefix, | ||||||
| @@ -211,10 +348,20 @@ func (l *Logger) WithField(key string, value interface{}) *Logger { | |||||||
| 	return newLogger | 	return newLogger | ||||||
| } | } | ||||||
|  |  | ||||||
| // WithFields adds multiple fields to the logger's context | // WithFields creates a new logger instance with multiple additional fields in its context. | ||||||
|  | // All fields will be included in all log messages from the returned logger, appearing as key=value pairs appended to the message. | ||||||
|  | // This is useful for structured logging where you want to add multiple context fields. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	fields := map[string]interface{}{"user": "john", "role": "admin", "session": "abc123"} | ||||||
|  | //	logger := cylogger.WithFields(fields) | ||||||
|  | //	logger.Info("Processing request")  // Output: "Processing request user=john role=admin session=abc123" | ||||||
|  | // | ||||||
|  | // This is particularly useful for scoped logging where you need to track multiple pieces of context | ||||||
|  | // without repeatedly logging them in each message. | ||||||
| func (l *Logger) WithFields(fields map[string]interface{}) *Logger { | func (l *Logger) WithFields(fields map[string]interface{}) *Logger { | ||||||
| 	newLogger := &Logger{ | 	newLogger := &Logger{ | ||||||
| 		out:           l.out, | 		out:           append([]io.Writer(nil), l.out...), | ||||||
| 		currentLevel:  l.currentLevel, | 		currentLevel:  l.currentLevel, | ||||||
| 		prefix:        l.prefix, | 		prefix:        l.prefix, | ||||||
| 		userPrefix:    l.userPrefix, | 		userPrefix:    l.userPrefix, | ||||||
| @@ -237,6 +384,18 @@ func (l *Logger) WithFields(fields map[string]interface{}) *Logger { | |||||||
| 	return newLogger | 	return newLogger | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // WithPrefix creates a new logger instance with an additional user prefix. | ||||||
|  | // The prefix will be displayed in all log messages from the returned logger, appearing as [prefix] before the message. | ||||||
|  | // This is useful for adding context or module identification to log messages. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	logger := cylogger.WithPrefix("AUTH") | ||||||
|  | //	logger.Info("User authenticated")  // Output: "[AUTH] User authenticated" | ||||||
|  | //	logger.Error("Login failed")     // Output: "[AUTH] Login failed" | ||||||
|  | // | ||||||
|  | // This is particularly useful for scoped logging in functions, loops, or modules where you want to know | ||||||
|  | // what the log line refers to without continuously logging an ID or identifier. | ||||||
|  | // Unlike WithField which appends fields to the end, WithPrefix prepends the prefix to the beginning. | ||||||
| func (l *Logger) WithPrefix(prefix string) *Logger { | func (l *Logger) WithPrefix(prefix string) *Logger { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -245,7 +404,7 @@ func (l *Logger) WithPrefix(prefix string) *Logger { | |||||||
| 		l = Default | 		l = Default | ||||||
| 	} | 	} | ||||||
| 	newLogger := &Logger{ | 	newLogger := &Logger{ | ||||||
| 		out:           l.out, | 		out:           append([]io.Writer(nil), l.out...), | ||||||
| 		currentLevel:  l.currentLevel, | 		currentLevel:  l.currentLevel, | ||||||
| 		prefix:        l.prefix, | 		prefix:        l.prefix, | ||||||
| 		userPrefix:    strings.TrimSpace(l.userPrefix + " " + prefix), | 		userPrefix:    strings.TrimSpace(l.userPrefix + " " + prefix), | ||||||
| @@ -263,6 +422,17 @@ func (l *Logger) WithPrefix(prefix string) *Logger { | |||||||
| 	return newLogger | 	return newLogger | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ToFile creates a new logger instance that writes to both the original outputs and a file. | ||||||
|  | // The file is opened in append mode and will be created if it doesn't exist. | ||||||
|  | // This is useful for logging to both console and file simultaneously. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	logger := cylogger.New(os.Stdout, "", log.LstdFlags) | ||||||
|  | //	fileLogger := logger.ToFile("app.log") | ||||||
|  | //	fileLogger.Info("This will appear in both console and app.log") | ||||||
|  | // | ||||||
|  | // The file is opened with os.O_CREATE|os.O_WRONLY|os.O_APPEND flags, so it will be created if it doesn't exist | ||||||
|  | // and new log entries will be appended to the end of the file. | ||||||
| func (l *Logger) ToFile(filename string) *Logger { | func (l *Logger) ToFile(filename string) *Logger { | ||||||
| 	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) | 	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -275,7 +445,7 @@ func (l *Logger) ToFile(filename string) *Logger { | |||||||
| 		l = Default | 		l = Default | ||||||
| 	} | 	} | ||||||
| 	newLogger := &Logger{ | 	newLogger := &Logger{ | ||||||
| 		out:           io.MultiWriter(l.out, file), | 		out:           append([]io.Writer(nil), l.out...), | ||||||
| 		currentLevel:  l.currentLevel, | 		currentLevel:  l.currentLevel, | ||||||
| 		prefix:        l.prefix, | 		prefix:        l.prefix, | ||||||
| 		userPrefix:    l.userPrefix, | 		userPrefix:    l.userPrefix, | ||||||
| @@ -285,10 +455,17 @@ func (l *Logger) ToFile(filename string) *Logger { | |||||||
| 		defaultFields: make(map[string]interface{}), | 		defaultFields: make(map[string]interface{}), | ||||||
| 		showGoroutine: l.showGoroutine, | 		showGoroutine: l.showGoroutine, | ||||||
| 	} | 	} | ||||||
|  | 	// Copy existing fields | ||||||
|  | 	for k, v := range l.defaultFields { | ||||||
|  | 		newLogger.defaultFields[k] = v | ||||||
|  | 	} | ||||||
|  | 	// Append the new file writer | ||||||
|  | 	newLogger.out = append(newLogger.out, file) | ||||||
| 	return newLogger | 	return newLogger | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetGoroutineID extracts the goroutine ID from the runtime stack | // GetGoroutineID extracts the current goroutine ID from the runtime stack. | ||||||
|  | // This is used internally by the logger to include goroutine information in log messages. | ||||||
| func GetGoroutineID() string { | func GetGoroutineID() string { | ||||||
| 	buf := make([]byte, 64) | 	buf := make([]byte, 64) | ||||||
| 	n := runtime.Stack(buf, false) | 	n := runtime.Stack(buf, false) | ||||||
| @@ -318,56 +495,52 @@ func (l *Logger) formatMessage(level LogLevel, format string, args ...interface{ | |||||||
| 		fields = " " + strings.Join(pairs, " ") | 		fields = " " + strings.Join(pairs, " ") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var levelColor, bgColor, msgFgColor, resetColor string | 	var tagFgColor, tagBgColor, messageBgColor string | ||||||
| 	useBgColor := false // Flag to indicate if background color should be used | 	useSpecialFormatting := false // Flag for levels with custom message background/foreground | ||||||
| 	if l.useColors { | 	if l.useColors { | ||||||
| 		resetColor = ResetColor | 		// Check if a message background color is defined for this level style | ||||||
| 		levelColor = levelColors[level] // Color for the level tag text ONLY | 		if levelStyles[level].MessageBackgroundColor != "" { | ||||||
|  | 			useSpecialFormatting = true | ||||||
| 		if bg, ok := levelBackgroundColors[level]; ok { // Check if this level has a background color defined (ERROR/WARNING) | 			// Retrieve all style components from the map | ||||||
| 			bgColor = bg | 			tagFgColor = levelStyles[level].TagColor // Assign directly | ||||||
| 			msgFgColor = FgWhiteBold // Use bold white for the message part on colored background | 			tagBgColor = levelStyles[level].TagBackgroundColor | ||||||
| 			useBgColor = true | 			// messageFgColor = levelStyles[level].MessageColor | ||||||
|  | 			messageBgColor = levelStyles[level].MessageBackgroundColor | ||||||
| 		} else { | 		} else { | ||||||
| 			// For other levels, message part uses default terminal color. | 			// For other levels (INFO, DEBUG, etc.), only TagColor is guaranteed | ||||||
| 			// msgFgColor remains empty, bgColor remains empty. | 			tagFgColor = levelStyles[level].TagColor // Use the defined tag color | ||||||
| 			// levelColor is still needed for the tag below. | 			// tagBgColor, messageFgColor, messageBgColor remain empty (use terminal defaults) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var caller string | 	var caller string | ||||||
| 	if l.flag&log.Lshortfile != 0 || l.flag&log.Llongfile != 0 { | 	if l.flag&log.Lshortfile != 0 || l.flag&log.Llongfile != 0 { | ||||||
| 		// Find the actual caller by scanning up the stack |  | ||||||
| 		// until we find a function outside the logger package |  | ||||||
| 		var file string | 		var file string | ||||||
| 		var line int | 		var line int | ||||||
| 		var ok bool | 		var ok bool | ||||||
|  |  | ||||||
| 		// Start at a reasonable depth and scan up to 10 frames | 		// Start at a reasonable depth and scan up to 10 frames | ||||||
| 		for depth := 4; depth < 15; depth++ { | 		for depth := 4; depth < 15; depth++ { | ||||||
| 			_, file, line, ok = runtime.Caller(depth) | 			_, file, line, ok = runtime.Caller(depth) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
|  | 			// Check if the caller is within this logger package itself | ||||||
| 			// If the caller is not in the logger package, we found our caller | 			if !strings.Contains(file, "main.go") && !strings.Contains(file, "colors.go") { | ||||||
| 			if !strings.Contains(file, "logger/logger.go") { |  | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			file = "???" | 			file = "???" | ||||||
| 			line = 0 | 			line = 0 | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if l.flag&log.Lshortfile != 0 { | 		if l.flag&log.Lshortfile != 0 { | ||||||
| 			file = filepath.Base(file) | 			file = filepath.Base(file) | ||||||
| 		} | 		} | ||||||
|  | 		// Caller string - no background color applied here | ||||||
| 		caller = fmt.Sprintf("%-25s ", file+":"+strconv.Itoa(line)) | 		caller = fmt.Sprintf("%-25s ", file+":"+strconv.Itoa(line)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Format the timestamp with fixed width | 	// Format the timestamp with fixed width - no background color applied here | ||||||
| 	var timeStr string | 	var timeStr string | ||||||
| 	if l.flag&(log.Ldate|log.Ltime|log.Lmicroseconds) != 0 { | 	if l.flag&(log.Ldate|log.Ltime|log.Lmicroseconds) != 0 { | ||||||
| 		t := time.Now() | 		t := time.Now() | ||||||
| @@ -383,41 +556,75 @@ func (l *Logger) formatMessage(level LogLevel, format string, args ...interface{ | |||||||
| 		timeStr = fmt.Sprintf("%-15s ", timeStr) | 		timeStr = fmt.Sprintf("%-15s ", timeStr) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Add goroutine ID if enabled, with fixed width | 	// Add goroutine ID if enabled, with fixed width - no background color applied here | ||||||
| 	var goroutineStr string | 	var goroutineStr string | ||||||
| 	if l.showGoroutine { | 	if l.showGoroutine { | ||||||
| 		goroutineID := GetGoroutineID() | 		goroutineID := GetGoroutineID() | ||||||
| 		goroutineStr = fmt.Sprintf("[g:%-4s] ", goroutineID) | 		goroutineStr = fmt.Sprintf("[g:%-4s] ", goroutineID) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Create a colored level indicator - color only the level name text, reset locally | 	// --- Level Tag Formatting and Padding --- | ||||||
| 	levelStr := fmt.Sprintf("%s%s%s", levelColor, levelNames[level], resetColor) | 	levelStr := levelStyles[level].Tag | ||||||
| 	levelColumn := fmt.Sprintf("%-20s", levelStr) // Pad the tag | 	visibleTagContent := fmt.Sprintf("[%s]", levelStr) | ||||||
|  | 	visibleTagLen := len(visibleTagContent) | ||||||
|  | 	paddingWidth := 10 // Target width for the level column (tag + padding) | ||||||
|  | 	numSpaces := paddingWidth - visibleTagLen | ||||||
|  | 	if numSpaces < 0 { | ||||||
|  | 		numSpaces = 1 // Ensure at least one space | ||||||
|  | 	} | ||||||
|  | 	padding := strings.Repeat(" ", numSpaces) | ||||||
|  |  | ||||||
|  | 	var levelTagFormatted string | ||||||
|  | 	if useSpecialFormatting { | ||||||
|  | 		// ERROR/WARNING: Tag has specific background and foreground | ||||||
|  | 		levelTagFormatted = fmt.Sprintf("%s%s%s%s", tagBgColor, tagFgColor, visibleTagContent, Reset) | ||||||
|  | 	} else { | ||||||
|  | 		// Other levels: Tag has standard foreground color only | ||||||
|  | 		levelTagFormatted = fmt.Sprintf("%s%s%s", tagFgColor, visibleTagContent, Reset) | ||||||
|  | 	} | ||||||
|  | 	levelColumn := levelTagFormatted + padding // Combine formatted tag and padding | ||||||
|  |  | ||||||
|  | 	// --- User Prefix Formatting (part of message content for coloring purposes) --- | ||||||
| 	userPrefixStr := "" | 	userPrefixStr := "" | ||||||
| 	if l.userPrefix != "" { | 	if l.userPrefix != "" { | ||||||
| 		// Use default foreground color for prefix (dark grey), reset locally | 		// Format the string part here, colors applied later if needed | ||||||
| 		prefixColor := levelColors[LevelPrefix] | 		userPrefixStr = fmt.Sprintf("[%s] ", l.userPrefix) | ||||||
| 		userPrefixStr = fmt.Sprintf("%s[%s]%s ", prefixColor, l.userPrefix, resetColor) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Build the prefix part (timestamp, caller, goroutine, level tag, user prefix) | 	// --- Message Content --- | ||||||
| 	// These parts will use default terminal colors or their specifically set colors (like level tag) | 	messageContent := fmt.Sprintf("%s%s", msg, fields) | ||||||
| 	prefixPart := fmt.Sprintf("%s%s%s%s%s%s", |  | ||||||
| 		l.prefix, timeStr, caller, goroutineStr, levelColumn, userPrefixStr) |  | ||||||
|  |  | ||||||
| 	// Build the message part | 	// --- Assemble Final String --- | ||||||
| 	messagePart := fmt.Sprintf("%s%s", msg, fields) | 	var finalMsg strings.Builder | ||||||
|  |  | ||||||
| 	// Combine prefix and message, applying background/foreground ONLY to the message part | 	// Part 1: Timestamp, Caller, Goroutine ID (always default colors) | ||||||
| 	if useBgColor { | 	finalMsg.WriteString(l.prefix) | ||||||
| 		// Apply background and specific foreground ONLY to the message part | 	finalMsg.WriteString(timeStr) | ||||||
| 		return fmt.Sprintf("%s%s%s%s%s", prefixPart, bgColor, msgFgColor, messagePart, resetColor) | 	finalMsg.WriteString(caller) | ||||||
|  | 	finalMsg.WriteString(goroutineStr) | ||||||
|  |  | ||||||
|  | 	// Part 2: Level Column (already formatted with tag colors and padding) | ||||||
|  | 	finalMsg.WriteString(levelColumn) | ||||||
|  |  | ||||||
|  | 	// Part 3: User Prefix + Message Content (apply special formatting if needed) | ||||||
|  | 	if useSpecialFormatting { | ||||||
|  | 		// ERROR/WARNING: Apply message background and foreground to User Prefix + Message | ||||||
|  | 		finalMsg.WriteString(messageBgColor) | ||||||
|  | 		// finalMsg.WriteString(messageFgColor) // This doesn't work...? For some reason? | ||||||
|  | 		finalMsg.WriteString(userPrefixStr) // Write user prefix inside the colored block | ||||||
|  | 		finalMsg.WriteString(messageContent) | ||||||
|  | 		finalMsg.WriteString(Reset) | ||||||
| 	} else { | 	} else { | ||||||
| 		// For non-ERROR/WARNING, just combine prefix (with its colored tag) and the uncolored message part. | 		// Other levels: User Prefix and Message content use default colors | ||||||
| 		// No additional color codes needed here for the message itself. | 		// Apply specific color to user prefix if it exists | ||||||
| 		return fmt.Sprintf("%s%s", prefixPart, messagePart) | 		if l.userPrefix != "" { | ||||||
|  | 			prefixColor := levelStyles[LevelPrefix].TagColor | ||||||
|  | 			finalMsg.WriteString(fmt.Sprintf("%s%s%s", prefixColor, userPrefixStr, Reset)) | ||||||
|  | 		} // No else needed, if userPrefix is empty, userPrefixStr is "" anyway | ||||||
|  | 		finalMsg.WriteString(messageContent) // Append message with default colors | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return finalMsg.String() | ||||||
| } | } | ||||||
|  |  | ||||||
| // log logs a message at the specified level | // log logs a message at the specified level | ||||||
| @@ -432,42 +639,84 @@ func (l *Logger) log(level LogLevel, format string, args ...interface{}) { | |||||||
|  |  | ||||||
| 	// Get formatted message with potential background color | 	// Get formatted message with potential background color | ||||||
| 	msg := l.formatMessage(level, format, args...) | 	msg := l.formatMessage(level, format, args...) | ||||||
| 	fmt.Fprintln(l.out, msg) | 	for _, w := range l.out { | ||||||
|  | 		fmt.Fprintln(w, msg) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Error logs an error message | // Error logs an error message at LevelError. | ||||||
|  | // Error messages are always displayed regardless of the current log level. | ||||||
| func (l *Logger) Error(format string, args ...interface{}) { | func (l *Logger) Error(format string, args ...interface{}) { | ||||||
| 	l.log(LevelError, format, args...) | 	l.log(LevelError, format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Warning logs a warning message | // Warning logs a warning message at LevelWarning. | ||||||
|  | // Warning messages indicate potential issues that should be noted. | ||||||
| func (l *Logger) Warning(format string, args ...interface{}) { | func (l *Logger) Warning(format string, args ...interface{}) { | ||||||
| 	l.log(LevelWarning, format, args...) | 	l.log(LevelWarning, format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Info logs an informational message | // Info logs an informational message at LevelInfo. | ||||||
|  | // Info messages provide general information about application flow. | ||||||
| func (l *Logger) Info(format string, args ...interface{}) { | func (l *Logger) Info(format string, args ...interface{}) { | ||||||
| 	l.log(LevelInfo, format, args...) | 	l.log(LevelInfo, format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Debug logs a debug message | // Debug logs a debug message at LevelDebug. | ||||||
|  | // Debug messages provide detailed information useful for diagnosing problems. | ||||||
| func (l *Logger) Debug(format string, args ...interface{}) { | func (l *Logger) Debug(format string, args ...interface{}) { | ||||||
| 	l.log(LevelDebug, format, args...) | 	l.log(LevelDebug, format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Trace logs a trace message | // Trace logs a trace message at LevelTrace. | ||||||
|  | // Trace messages provide the most detailed information for deep debugging. | ||||||
| func (l *Logger) Trace(format string, args ...interface{}) { | func (l *Logger) Trace(format string, args ...interface{}) { | ||||||
| 	l.log(LevelTrace, format, args...) | 	l.log(LevelTrace, format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Lua logs a Lua message | // Dump logs objects using valast for regressive tests at LevelDump. | ||||||
|  | // This is useful for debugging and testing where you need to inspect object state. | ||||||
|  | // The objects are formatted using valast for readable output. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	type User struct { Name string; Age int } | ||||||
|  | //	user := User{Name: "John", Age: 30} | ||||||
|  | //	logger.Dump("User data", user) | ||||||
|  | //	// Output: "User data:\nUser{Name: \"John\", Age: 30}" | ||||||
|  | // | ||||||
|  | // This is particularly useful for regressive testing where you need to capture | ||||||
|  | // the exact state of objects for comparison or debugging purposes. | ||||||
|  | func (l *Logger) Dump(message string, objects ...interface{}) { | ||||||
|  | 	if len(objects) == 0 { | ||||||
|  | 		l.log(LevelDump, message) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var dumpContent strings.Builder | ||||||
|  | 	dumpContent.WriteString(message) | ||||||
|  | 	dumpContent.WriteString(":\n") | ||||||
|  |  | ||||||
|  | 	for i, obj := range objects { | ||||||
|  | 		if i > 0 { | ||||||
|  | 			dumpContent.WriteString("\n") | ||||||
|  | 		} | ||||||
|  | 		dumpContent.WriteString(valast.String(obj)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l.log(LevelDump, dumpContent.String()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Lua logs a Lua message at LevelLua. | ||||||
|  | // Lua messages are always displayed regardless of the current log level. | ||||||
|  | // This is specifically designed for output from Lua scripts. | ||||||
| func (l *Logger) Lua(format string, args ...interface{}) { | func (l *Logger) Lua(format string, args ...interface{}) { | ||||||
| 	l.log(LevelLua, format, args...) | 	l.log(LevelLua, format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Global log functions that use DefaultLogger | // Global log functions that use DefaultLogger | ||||||
|  |  | ||||||
| // Error logs an error message using the default logger | // Error logs an error message using the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func Error(format string, args ...interface{}) { | func Error(format string, args ...interface{}) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -475,7 +724,8 @@ func Error(format string, args ...interface{}) { | |||||||
| 	Default.Error(format, args...) | 	Default.Error(format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Warning logs a warning message using the default logger | // Warning logs a warning message using the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func Warning(format string, args ...interface{}) { | func Warning(format string, args ...interface{}) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -483,7 +733,8 @@ func Warning(format string, args ...interface{}) { | |||||||
| 	Default.Warning(format, args...) | 	Default.Warning(format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Info logs an informational message using the default logger | // Info logs an informational message using the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func Info(format string, args ...interface{}) { | func Info(format string, args ...interface{}) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -491,7 +742,8 @@ func Info(format string, args ...interface{}) { | |||||||
| 	Default.Info(format, args...) | 	Default.Info(format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Debug logs a debug message using the default logger | // Debug logs a debug message using the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func Debug(format string, args ...interface{}) { | func Debug(format string, args ...interface{}) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -499,7 +751,8 @@ func Debug(format string, args ...interface{}) { | |||||||
| 	Default.Debug(format, args...) | 	Default.Debug(format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Trace logs a trace message using the default logger | // Trace logs a trace message using the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func Trace(format string, args ...interface{}) { | func Trace(format string, args ...interface{}) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -507,7 +760,17 @@ func Trace(format string, args ...interface{}) { | |||||||
| 	Default.Trace(format, args...) | 	Default.Trace(format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Lua logs a Lua message using the default logger | // Dump logs objects using valast for regressive tests using the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
|  | func Dump(message string, objects ...interface{}) { | ||||||
|  | 	if Default == nil { | ||||||
|  | 		Init(defaultLogLevel) | ||||||
|  | 	} | ||||||
|  | 	Default.Dump(message, objects...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Lua logs a Lua message using the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func Lua(format string, args ...interface{}) { | func Lua(format string, args ...interface{}) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -515,7 +778,9 @@ func Lua(format string, args ...interface{}) { | |||||||
| 	Default.Lua(format, args...) | 	Default.Lua(format, args...) | ||||||
| } | } | ||||||
|  |  | ||||||
| // LogPanic logs a panic error and its stack trace | // LogPanic logs a panic error and its stack trace using the default logger. | ||||||
|  | // This is useful for logging panics that have been recovered elsewhere. | ||||||
|  | // The function logs the panic value and full stack trace. | ||||||
| func LogPanic(r interface{}) { | func LogPanic(r interface{}) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -525,7 +790,8 @@ func LogPanic(r interface{}) { | |||||||
| 	Default.Error("PANIC: %v\n%s", r, stack[:n]) | 	Default.Error("PANIC: %v\n%s", r, stack[:n]) | ||||||
| } | } | ||||||
|  |  | ||||||
| // SetLevel sets the log level for the default logger | // SetLevel sets the log level for the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func SetLevel(level LogLevel) { | func SetLevel(level LogLevel) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(level) | 		Init(level) | ||||||
| @@ -534,7 +800,8 @@ func SetLevel(level LogLevel) { | |||||||
| 	Default.SetLevel(level) | 	Default.SetLevel(level) | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetLevel gets the log level for the default logger | // GetLevel gets the log level for the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func GetLevel() LogLevel { | func GetLevel() LogLevel { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -542,7 +809,8 @@ func GetLevel() LogLevel { | |||||||
| 	return Default.GetLevel() | 	return Default.GetLevel() | ||||||
| } | } | ||||||
|  |  | ||||||
| // WithField returns a new logger with the field added to the default logger's context | // WithField returns a new logger with the field added to the default logger's context. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func WithField(key string, value interface{}) *Logger { | func WithField(key string, value interface{}) *Logger { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -550,7 +818,8 @@ func WithField(key string, value interface{}) *Logger { | |||||||
| 	return Default.WithField(key, value) | 	return Default.WithField(key, value) | ||||||
| } | } | ||||||
|  |  | ||||||
| // WithFields returns a new logger with the fields added to the default logger's context | // WithFields returns a new logger with the fields added to the default logger's context. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func WithFields(fields map[string]interface{}) *Logger { | func WithFields(fields map[string]interface{}) *Logger { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -558,7 +827,8 @@ func WithFields(fields map[string]interface{}) *Logger { | |||||||
| 	return Default.WithFields(fields) | 	return Default.WithFields(fields) | ||||||
| } | } | ||||||
|  |  | ||||||
| // SetShowGoroutine enables or disables goroutine ID display in the default logger | // SetShowGoroutine enables or disables goroutine ID display in the default logger. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func SetShowGoroutine(show bool) { | func SetShowGoroutine(show bool) { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| @@ -566,10 +836,117 @@ func SetShowGoroutine(show bool) { | |||||||
| 	Default.SetShowGoroutine(show) | 	Default.SetShowGoroutine(show) | ||||||
| } | } | ||||||
|  |  | ||||||
| // ShowGoroutine returns whether goroutine ID is included in default logger's messages | // ShowGoroutine returns whether goroutine ID is included in default logger's messages. | ||||||
|  | // This is a convenience function that uses the global Default logger instance. | ||||||
| func ShowGoroutine() bool { | func ShowGoroutine() bool { | ||||||
| 	if Default == nil { | 	if Default == nil { | ||||||
| 		Init(defaultLogLevel) | 		Init(defaultLogLevel) | ||||||
| 	} | 	} | ||||||
| 	return Default.ShowGoroutine() | 	return Default.ShowGoroutine() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NoStdout creates a new logger instance that excludes stdout from its output destinations. | ||||||
|  | // This is useful when you want to log only to files or other destinations, not to the console. | ||||||
|  | // The original logger's stdout output is filtered out while keeping all other output destinations. | ||||||
|  | func (l *Logger) NoStdout() *Logger { | ||||||
|  | 	if Default == nil { | ||||||
|  | 		Init(defaultLogLevel) | ||||||
|  | 	} | ||||||
|  | 	if l == nil { | ||||||
|  | 		l = Default | ||||||
|  | 	} | ||||||
|  | 	newLogger := &Logger{ | ||||||
|  | 		out:           nil, | ||||||
|  | 		currentLevel:  l.currentLevel, | ||||||
|  | 		prefix:        l.prefix, | ||||||
|  | 		userPrefix:    l.userPrefix, | ||||||
|  | 		flag:          l.flag, | ||||||
|  | 		useColors:     l.useColors, | ||||||
|  | 		callerOffset:  l.callerOffset, | ||||||
|  | 		defaultFields: make(map[string]interface{}), | ||||||
|  | 		showGoroutine: l.showGoroutine, | ||||||
|  | 	} | ||||||
|  | 	// Copy existing fields | ||||||
|  | 	for k, v := range l.defaultFields { | ||||||
|  | 		newLogger.defaultFields[k] = v | ||||||
|  | 	} | ||||||
|  | 	// Keep all writers except stdout | ||||||
|  | 	for _, w := range l.out { | ||||||
|  | 		if w == os.Stdout { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		newLogger.out = append(newLogger.out, w) | ||||||
|  | 	} | ||||||
|  | 	return newLogger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	Init(LevelDebug) | ||||||
|  |  | ||||||
|  | 	// Test basic logging | ||||||
|  | 	Debug("This is a debug message") | ||||||
|  | 	Info("This is an info message") | ||||||
|  | 	Warning("This is a warning message") | ||||||
|  | 	Error("This is an error message") | ||||||
|  |  | ||||||
|  | 	// Test logging with fields | ||||||
|  | 	logger := WithField("user", "testuser") | ||||||
|  | 	logger.Info("User logged in") | ||||||
|  |  | ||||||
|  | 	// Test logging with multiple fields | ||||||
|  | 	fields := map[string]interface{}{ | ||||||
|  | 		"user": "testuser", | ||||||
|  | 		"role": "admin", | ||||||
|  | 		"id":   12345, | ||||||
|  | 	} | ||||||
|  | 	WithFields(fields).Info("User details") | ||||||
|  |  | ||||||
|  | 	// Test error logging with fields | ||||||
|  | 	WithField("error", "connection failed").Error("Database error") | ||||||
|  |  | ||||||
|  | 	logger.WithPrefix("CUSTOM").Info("This is a message with a custom prefix") | ||||||
|  |  | ||||||
|  | 	// Test goroutine ID display | ||||||
|  | 	SetShowGoroutine(true) | ||||||
|  | 	Info("This message should show goroutine ID") | ||||||
|  |  | ||||||
|  | 	// Test different log levels | ||||||
|  | 	SetLevel(LevelInfo) | ||||||
|  | 	Debug("This debug message should not appear") | ||||||
|  | 	Info("This info message should appear") | ||||||
|  |  | ||||||
|  | 	// Test with custom prefix | ||||||
|  | 	WithField("prefix", "custom").Info("Message with custom prefix") | ||||||
|  | 	Lua("This is a Lua message") | ||||||
|  |  | ||||||
|  | 	// Test Dump functionality | ||||||
|  | 	SetLevel(LevelDump) // Set level to show dump messages | ||||||
|  |  | ||||||
|  | 	type ProjectData struct { | ||||||
|  | 		Title   string | ||||||
|  | 		Name    string | ||||||
|  | 		Data    string | ||||||
|  | 		Commits string | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	type Project struct { | ||||||
|  | 		Id   int64 | ||||||
|  | 		Data *ProjectData | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p := Project{ | ||||||
|  | 		Id: 1, | ||||||
|  | 		Data: &ProjectData{ | ||||||
|  | 			Title:   "Test", | ||||||
|  | 			Name:    "Mihai", | ||||||
|  | 			Data:    "Some data", | ||||||
|  | 			Commits: "Test Message", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Test single object dump | ||||||
|  | 	Dump("FooBar called", p) | ||||||
|  |  | ||||||
|  | 	// Test multiple object dump | ||||||
|  | 	Dump("Multiple objects", p, fields, "string value", 42) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										84
									
								
								safe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								safe.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | // Package cylogger provides panic recovery and safe goroutine execution utilities. | ||||||
|  | // These functions help prevent panics from crashing your application by catching | ||||||
|  | // them and logging them appropriately. | ||||||
|  | package cylogger | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"runtime/debug" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // PanicHandler handles a panic and logs it with goroutine ID and stack trace. | ||||||
|  | // This function should be used with defer to catch panics in goroutines or functions. | ||||||
|  | // When a panic occurs, it logs the panic value, goroutine ID, and full stack trace. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	defer cylogger.PanicHandler() | ||||||
|  | //	// Your code that might panic | ||||||
|  | func PanicHandler() { | ||||||
|  | 	if r := recover(); r != nil { | ||||||
|  | 		goroutineID := GetGoroutineID() | ||||||
|  | 		stackTrace := debug.Stack() | ||||||
|  | 		Error("PANIC in goroutine %s: %v\n%s", goroutineID, r, stackTrace) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SafeGo launches a goroutine with panic recovery. | ||||||
|  | // If the goroutine panics, the panic will be caught and logged instead of crashing the program. | ||||||
|  | // This is useful for running potentially unstable code in goroutines. | ||||||
|  | // Usage: cylogger.SafeGo(func() { ... your code ... }) | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	cylogger.SafeGo(func() { | ||||||
|  | //	    // Code that might panic | ||||||
|  | //	    riskyOperation() | ||||||
|  | //	}) | ||||||
|  | func SafeGo(f func()) { | ||||||
|  | 	go func() { | ||||||
|  | 		defer PanicHandler() | ||||||
|  | 		f() | ||||||
|  | 	}() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SafeGoWithArgs launches a goroutine with panic recovery and passes arguments. | ||||||
|  | // If the goroutine panics, the panic will be caught and logged instead of crashing the program. | ||||||
|  | // This is useful for running potentially unstable code in goroutines with specific arguments. | ||||||
|  | // Usage: cylogger.SafeGoWithArgs(func(arg1, arg2 interface{}) { ... }, "value1", 42) | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	cylogger.SafeGoWithArgs(func(args ...interface{}) { | ||||||
|  | //	    // Code that might panic with arguments | ||||||
|  | //	    processData(args[0], args[1]) | ||||||
|  | //	}, "data1", 42) | ||||||
|  | func SafeGoWithArgs(f func(...interface{}), args ...interface{}) { | ||||||
|  | 	go func() { | ||||||
|  | 		defer PanicHandler() | ||||||
|  | 		f(args...) | ||||||
|  | 	}() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SafeExec executes a function with panic recovery and returns an error if a panic occurs. | ||||||
|  | // This is useful for code that should not panic but might, allowing you to handle panics gracefully. | ||||||
|  | // If the function panics, it returns an error describing the panic instead of crashing the program. | ||||||
|  | // Example: | ||||||
|  | // | ||||||
|  | //	err := cylogger.SafeExec(func() { | ||||||
|  | //	    // Code that might panic | ||||||
|  | //	    riskyOperation() | ||||||
|  | //	}) | ||||||
|  | //	if err != nil { | ||||||
|  | //	    // Handle the panic as an error | ||||||
|  | //	} | ||||||
|  | func SafeExec(f func()) (err error) { | ||||||
|  | 	defer func() { | ||||||
|  | 		if r := recover(); r != nil { | ||||||
|  | 			goroutineID := GetGoroutineID() | ||||||
|  | 			stackTrace := debug.Stack() | ||||||
|  | 			Error("PANIC in goroutine %s: %v\n%s", goroutineID, r, stackTrace) | ||||||
|  | 			err = fmt.Errorf("panic recovered: %v", r) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	f() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user