From 94c6e882fb30ceb47e7918533bfa7661867e6772 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Wed, 9 Apr 2025 22:54:16 -0600 Subject: [PATCH 01/10] Add a structured logging utility with custom prefixes and suffixes Introduce a logging system supporting multiple severity levels, JSON formatting, and customizable prefixes and suffixes inspired by Dr. Stone. Includes random success and failure quotes to make logging more dynamic and engaging. --- README.md | 64 +++++++++++++ log.go | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++++ prefixes.go | 30 ++++-- suffixes.go | 47 ++++++++++ 4 files changed, 398 insertions(+), 7 deletions(-) create mode 100644 log.go create mode 100644 suffixes.go diff --git a/README.md b/README.md index 4d5c3ba..57b29ae 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,70 @@ The most scientifically badass logging library from the Kingdom of Science! go get gitstormr.dev/stone-utils/stonelog@latest ``` +## ⚡ Initialization + +```go +package main + +import ( + "gitstormr.dev/stone-utils/stonelog" +) + +func main() { + // Basic configuration (INFO level by default) + stonelog.InitStoneLog(stonelog.INFO, false, false) // false = text mode (true = JSON), false = keep suffixes (true = don't add character quotes) + + // Example logs + stonelog.Observation("System initialized successfully!") + stonelog.Failure("Reactor core temperature critical!") +} +``` + +## 📜 Log Levels (Scientific Style!) + +| Function | Level Equivalent | Example Output | +|---------------|--------------------|--------------------------------------------------| +| Trace() | TRACE | 🔍 Science traces: Entering function X | +| Debug() | DEBUG | 🤔 Hypothesis forming: Variable X = 42 | +| Observation() | INFO | ✅ Experiment successful: User logged in | +| Hypothesis() | WARN | ⚠️ Anomaly detected: High memory usage | +| Failure() | ERROR | 💥 Critical malfunction: DB disconnected | +| Meltdown() | FATAL | ☠️ FINAL EXPERIMENT FAILED: Server exploded | +| Panic() | PANIC | 🚨 CHAIN REACTION DETECTED: Unrecoverable error | + +## 🎛️ Changing Log Level + +```go +// Dynamic change! (e.g., enable DEBUG in production) +stonelog.SetLogLevel(stonelog.DEBUG) +``` + +## 🔬 JSON Mode (For Production) + +```go +stonelog.InitStoneLab(stonelog.STONE_DEBUG, true, false) // true = JSON, false = Keep suffixes +// Output: {"time":"2023-07-15T12:00:00Z","level":"OBSERVATION","message":"✅ Experiment successful: System ready","caller":"main.go:15"} +``` + +## **🚨 WINDOWS COLOR SUPPORT NOTICE 🚨** + +*"HEY WINDOWS USERS! YOUR TERMINALS NEED SOME SCIENCE!" - Chrome* + +🔬 **Current Limitations:** +- Colors don't work in old Windows Command Prompt (CMD) +- Looks boring in black & white (like stone tablets!) +- Works perfectly in modern terminals + +💡 **Senku-Approved Solutions:** +1. Install **Windows Terminal** (Microsoft Store) +2. Use **WSL** (Windows Subsystem for Linux) +3. Try **VS Code** or **Git Bash** + +*"This temporary setback represents just 0.0000001% of our scientific progress!"* +- **Dr. Senku Ishigami** + +*(Chrome whispers: "Pssst... Linux terminals are SO BADASS!")* 🐧🔥 + **Join the Scientific Revolution!** > "This isn't just logging - it's 10 billion percent scientific progress!" - Senku Ishigami diff --git a/log.go b/log.go new file mode 100644 index 0000000..81c5d7a --- /dev/null +++ b/log.go @@ -0,0 +1,264 @@ +package stonelog + +import ( + "encoding/json" + "fmt" + "log" + "math/rand" + "os" + "path/filepath" + "runtime" + "strconv" + "time" +) + +// logEntry represents the structure for a single log entry. +// Time is the timestamp of the log entry in RFC3339 format. +// Level specifies the severity level of the log message. +// Caller describes the source file and line generating the log. +// Message contains the actual log details or description. +type logEntry struct { + Time string `json:"time"` + Level string `json:"level"` + Caller string `json:"caller"` + Message string `json:"message"` +} + +// StoneLevel represents the severity level of logging categories. +type StoneLevel int + +// String returns the string representation of the StoneLevel. +func (l StoneLevel) String() string { + return stoneLevelNames[l] +} + +// Color returns the color code associated with the StoneLevel. +func (l StoneLevel) Color() string { + return levelColors[l] +} + +// TRACE represents the trace log level. +// DEBUG represents the debug log level. +// INFO represents the info log level. +// WARN represents the warning log level. +// ERROR represents the error log level. +// FATAL represents the fatal log level. +// PANIC represents the panic log level. +const ( + TRACE StoneLevel = iota + DEBUG + INFO + WARN + ERROR + FATAL + PANIC +) + +// stoneLevelNames maps StoneLevel constants to their string representations. +var stoneLevelNames = map[StoneLevel]string{ + TRACE: "TRACE", + DEBUG: "DEBUG", + INFO: "INFO", + WARN: "WARN", + ERROR: "ERROR", + FATAL: "FATAL", + PANIC: "PANIC", +} + +// ANSI color codes for terminal text formatting. +const ( + colorReset = "\033[0m" + colorRed = "\033[31m" + colorGreen = "\033[32m" + colorYellow = "\033[33m" + colorMagenta = "\033[35m" + colorCyan = "\033[36m" + colorWhite = "\033[37m" +) + +// levelColors maps StoneLevel constants to their corresponding color codes. +var levelColors = map[StoneLevel]string{ + TRACE: colorWhite, + DEBUG: colorCyan, + INFO: colorGreen, + WARN: colorYellow, + ERROR: colorRed, + FATAL: colorMagenta, + PANIC: colorRed + "\033[1m", +} + +// stoneLogger is a logger instance for logging stone-related operations. +// currentLogLevel indicates the active logging level for stoneLogger. +// disableCharacterSuffixes disables suffixes in log outputs when true. +var ( + stoneLogger *log.Logger + currentLogLevel StoneLevel + useJSON bool + callerFrameDepth = 3 + disableCharacterSuffixes bool +) + +// InitStoneLog initializes the logging system with the specified parameters. +// level sets the logging severity level threshold. +// jsonMode determines whether logs should be output in JSON format. +// disableSuffixes disables character suffixes in log messages. +func InitStoneLog(level StoneLevel, jsonMode, disableSuffixes bool) { + currentLogLevel = level + stoneLogger = log.New(os.Stdout, "", 0) + useJSON = jsonMode + disableCharacterSuffixes = disableSuffixes +} + +// SetLogLevel sets the current log level to the specified StoneLevel. +func SetLogLevel(level StoneLevel) { + currentLogLevel = level + Observation("Log level set to %v", level) +} + +// Trace logs a message at the TRACE using the specified format and arguments. +func Trace(format string, args ...interface{}) { + logMessage(TRACE, format, args...) +} + +// Debug logs a message at the DEBUG level using the provided format and arguments. +func Debug(format string, args ...interface{}) { + logMessage(DEBUG, format, args...) +} + +// Observation logs a message at the INFO level with optional formatting arguments. +func Observation(format string, args ...interface{}) { + logMessage(INFO, format, args...) +} + +// Hypothesis logs a message with WARN level using the provided format and arguments. +func Hypothesis(format string, args ...interface{}) { + logMessage(WARN, format, args...) +} + +// Failure logs a message at the ERROR level using the provided format and arguments. +func Failure(format string, args ...interface{}) { + logMessage(ERROR, format, args...) +} + +// Meltdown logs a message at the FATAL level and terminates the program. +func Meltdown(format string, args ...interface{}) { + logMessage(FATAL, format, args...) +} + +// Panic logs a message with PANIC severity and terminates the application. +func Panic(format string, args ...interface{}) { + logMessage(PANIC, format, args...) +} + +// getCallerInfo retrieves the file name and line number of the caller. +// It returns the information in the format "file:line" or "unknown:0" on failure. +func getCallerInfo() string { + _, file, line, ok := runtime.Caller(callerFrameDepth) + if !ok { + return "unknown:0" + } + return filepath.Base(file) + ":" + strconv.Itoa(line) +} + +// logMessage handles the logging of messages with varying severity levels. +// If the log level is below the configured threshold, it is ignored. +// Formats the message, adds a random prefix, and appends optional suffixes. +// Converts the output to JSON format if enabled or applies a custom formatter. +// Executes fatal or panic behaviors when specified severity levels are met. +func logMessage(level StoneLevel, format string, args ...interface{}) { + if level < FATAL && currentLogLevel > level { + return + } + + msg := fmt.Sprintf(format, args...) + msg = getRandomPrefix(level) + " " + msg + + // Determine if success or failure quote is needed + isSuccess := level <= WARN + + if !disableCharacterSuffixes { + msg += getRandomQuote(isSuccess) + } + + var formatted string + + if useJSON { + caller := getCallerInfo() + + entry := logEntry{ + Time: time.Now().UTC().Format(time.RFC3339), + Level: level.String(), + Caller: caller, + Message: msg, + } + jsonData, _ := json.Marshal(entry) + formatted = string(jsonData) + } else { + formatted = stoneFormat(level, msg) + } + + // Handle special logging behaviors for FATAL and PANIC + switch level { + case FATAL: + stoneLogger.Fatalln(formatted) + case PANIC: + stoneLogger.Panicln(formatted) + default: + stoneLogger.Println(formatted) + } +} + +// getRandomPrefix returns a random prefix based on the provided StoneLevel. +// It selects a prefix from predefined lists corresponding to the log level. +func getRandomPrefix(level StoneLevel) string { + switch level { + case TRACE: + idx := rand.Intn(len(stoneTracePrefixes)) + return stoneTracePrefixes[idx] + + case DEBUG: + idx := rand.Intn(len(stoneDebugPrefixes)) + return stoneDebugPrefixes[idx] + + case INFO: + idx := rand.Intn(len(stoneInfoPrefixes)) + return stoneInfoPrefixes[idx] + + case WARN: + idx := rand.Intn(len(stoneWarnPrefixes)) + return stoneWarnPrefixes[idx] + case ERROR: + idx := rand.Intn(len(stoneErrorPrefixes)) + return stoneErrorPrefixes[idx] + + case FATAL: + idx := rand.Intn(len(stonePanicPrefixes)) + return stonePanicPrefixes[idx] + + default: + idx := rand.Intn(len(stoneInfoPrefixes)) + return stoneInfoPrefixes[idx] + } +} + +// getRandomQuote returns a random character quote based on success or failure. +// The isSuccess parameter indicates if the quote is for a success (true) or failure. +func getRandomQuote(isSuccess bool) string { + if isSuccess { + idx := rand.Intn(len(characterSuccessQuoteSuffixes)) + return characterSuccessQuoteSuffixes[idx] + } else { + idx := rand.Intn(len(characterFailQuoteSuffixes)) + return characterFailQuoteSuffixes[idx] + } +} + +// stoneFormat formats a log message with level, timestamp, caller, and color. +func stoneFormat(level StoneLevel, msg string) string { + now := time.Now().UTC().Format(time.RFC3339) + caller := getCallerInfo() + return fmt.Sprintf( + "%s%s [%s] [%s] %s%s", level.Color(), now, level.String(), caller, msg, + colorReset, + ) +} diff --git a/prefixes.go b/prefixes.go index b81c19c..0bb548c 100644 --- a/prefixes.go +++ b/prefixes.go @@ -1,10 +1,17 @@ package stonelog +// stoneTracePrefixes contains prefixes for trace-level scientific messages. +// stoneDebugPrefixes contains prefixes for debug-level diagnostics and processes. +// stoneInfoPrefixes contains prefixes for informational scientific outcomes. +// stoneWarnPrefixes contains prefixes for warning-level messages of potential risks. +// stoneErrorPrefixes contains prefixes for error-level critical issues detected. +// stoneFatalPrefixes contains prefixes for fatal-level critical failures. +// stonePanicPrefixes contains prefixes for panic-level catastrophic failures. var ( stoneTracePrefixes = []string{ - "Science traces", - "Lab sensors detect", - "Microscope focus on", + "Science traces:", + "Lab sensors detect:", + "Microscope focus on:", "Quantum scanner active:", "Data particles observed:", } @@ -41,10 +48,19 @@ var ( "Hypothesis disproven:", } + stoneFatalPrefixes = []string{ + "SCIENTIFIC APOCALYPSE:", + "FINAL EXPERIMENT FAILED:", + "LABORATORY SHUTDOWN:", + "CORE CRITICAL:", + "RADIOACTIVE TERMINATION:", + } + stonePanicPrefixes = []string{ - "¡¡¡CATASTROPHIC FAILURE!!!", - "Emergency protocol FAILED:", - "LEAVE THE LAB!", - "¡¡¡IMMINENT NUCLEAR FUSION!!!", + "CHAIN REACTION DETECTED:", + "SYSTEMIC COLLAPSE:", + "QUANTUM CATASTROPHE:", + "GALACTIC-LEVEL BUG DETECTED:", + "T-REX IN THE DATACENTER:", } ) diff --git a/suffixes.go b/suffixes.go new file mode 100644 index 0000000..23a44e6 --- /dev/null +++ b/suffixes.go @@ -0,0 +1,47 @@ +package stonelog + +// characterSuccessQuoteSuffixes contains success quote suffixes by characters. +// characterFailQuoteSuffixes contains failure quote suffixes by characters. +var ( + characterSuccessQuoteSuffixes = []string{ + " // Senku: '10 BILLION POINTS FOR SCIENCE! REVIVAL SUCCESSFUL!'", + " // Senku: 'Mwa-ha-ha! Another victory for the Kingdom of Science!'", + " // Senku: 'E=mc², baby! That’s how you optimize!'", + " // Senku: 'This experiment... is a 10 billion percent success!'", + + " // Chrome: 'SO BADASS! SENKU, YOU’RE A GENIUS!'", + " // Chrome: 'HOLY CRAP, SCIENCE JUST PUNCHED THE SKY!'", + " // Chrome: 'WE’RE OFFICIALLY WIZARDS NOW!'", + + " // Kohaku: 'Heh. Even I could’ve done that... maybe.'", + " // Kohaku: 'Science + fists = unstoppable!'", + + " // Ryusui: 'NAVIGATION SUCCESSFUL! Time to monetize this!'", + " // Ryusui: 'Money can’t buy this... BUT I’LL TRY!'", + + " // Kaseki: 'HOT BLOODED ENGINEERING... PERFECTED! (ᗒᗣᗕ)՞'", + " // Kaseki: 'I’LL CARVE A STATUE TO COMMEMORATE THIS MOMENT!'", + } + + characterFailQuoteSuffixes = []string{ + " // Senku: '10 BILLION REASONS TO FIX THIS... NOW.'", + " // Senku: 'This failure is... statistically impressive.'", + " // Senku: 'Mwa-ha-ha... *nervous laugh*... reboot everything.'", + + " // Chrome: 'NOT BADASS! NOT BADASS AT ALL! (╥﹏╥)'", + " // Chrome: 'MY BRAIN HURTS! IS THIS HOW SCIENCE WORKS?!'", + + " // Kohaku: 'Ugh. Can I just smash the server with a rock?'", + " // Kohaku: 'Senku, your science is broken. FIX IT.'", + + " // Gen: 'Ah~... so this is how the world ends~?'", + " // Gen: 'Mentally calculating... yep, we’re doomed~.'", + + " // Tsukasa: '...This is why I opposed technology.'", + + " // Kaseki: 'BACK IN MY DAY, ERRORS WERE FIXED WITH A HAMMER! 🔨'", + " // Kaseki: 'MY SOUL... IT BURNS WITH DEBUGGING RAGE!!!'", + + " // Francois: 'I’ll prepare a funeral tea for the deceased process.'", + } +) -- 2.39.5 From 4046c447cc58235cb1792aa86d2c7b36022e720b Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Wed, 9 Apr 2025 23:04:35 -0600 Subject: [PATCH 02/10] Refactor `getRandomPrefix` to simplify StoneLevel mapping. Replaced the switch-case with a map for StoneLevel to prefix mapping. This reduces redundancy, improves readability, and maintains default handling for unsupported levels. Functionality remains unchanged. --- log.go | 48 +++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/log.go b/log.go index 81c5d7a..95efac2 100644 --- a/log.go +++ b/log.go @@ -208,37 +208,27 @@ func logMessage(level StoneLevel, format string, args ...interface{}) { } } -// getRandomPrefix returns a random prefix based on the provided StoneLevel. -// It selects a prefix from predefined lists corresponding to the log level. +// getRandomPrefix returns a random prefix string based on the given StoneLevel. +// It selects from predefined lists corresponding to the severity of the level. func getRandomPrefix(level StoneLevel) string { - switch level { - case TRACE: - idx := rand.Intn(len(stoneTracePrefixes)) - return stoneTracePrefixes[idx] - - case DEBUG: - idx := rand.Intn(len(stoneDebugPrefixes)) - return stoneDebugPrefixes[idx] - - case INFO: - idx := rand.Intn(len(stoneInfoPrefixes)) - return stoneInfoPrefixes[idx] - - case WARN: - idx := rand.Intn(len(stoneWarnPrefixes)) - return stoneWarnPrefixes[idx] - case ERROR: - idx := rand.Intn(len(stoneErrorPrefixes)) - return stoneErrorPrefixes[idx] - - case FATAL: - idx := rand.Intn(len(stonePanicPrefixes)) - return stonePanicPrefixes[idx] - - default: - idx := rand.Intn(len(stoneInfoPrefixes)) - return stoneInfoPrefixes[idx] + // Map StoneLevel to their respective prefix slices + prefixMap := map[StoneLevel][]string{ + TRACE: stoneTracePrefixes, + DEBUG: stoneDebugPrefixes, + INFO: stoneInfoPrefixes, + WARN: stoneWarnPrefixes, + ERROR: stoneErrorPrefixes, + FATAL: stonePanicPrefixes, } + + // Default to INFO prefixes if level is not in the map + prefixes, exists := prefixMap[level] + if !exists { + prefixes = stoneInfoPrefixes + } + + // Select and return a random prefix + return prefixes[rand.Intn(len(prefixes))] } // getRandomQuote returns a random character quote based on success or failure. -- 2.39.5 From 8fad79b823ce9ef1d763abe16b918f94f0706a86 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Wed, 9 Apr 2025 23:28:01 -0600 Subject: [PATCH 03/10] Set SonarQube to wait for quality gate results. Added `sonar.qualitygate.wait=true` to enforce the build process waits for SonarQube's quality gate results. This ensures code quality checks are completed before proceeding. --- sonar-project.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 31737c3..61d58a7 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,4 +4,5 @@ sonar.language=go sonar.sources=. sonar.exclusions=**/*_test.go sonar.tests=. -sonar.test.inclusions=**/*_test.go \ No newline at end of file +sonar.test.inclusions=**/*_test.go +sonar.qualitygate.wait=true \ No newline at end of file -- 2.39.5 From 4557b8050c593698a47bc3a89a5653eb7067eec5 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Thu, 10 Apr 2025 08:12:56 -0600 Subject: [PATCH 04/10] Add tests for log level string and color representations Introduce unit tests to verify the string and color outputs of StoneLevel. These tests ensure the correct mapping of log levels to their respective string values and color codes. --- log_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 log_test.go diff --git a/log_test.go b/log_test.go new file mode 100644 index 0000000..51c6a2d --- /dev/null +++ b/log_test.go @@ -0,0 +1,47 @@ +package stonelog + +import ( + "testing" +) + +func Test_LogLevelString(t *testing.T) { + levelTests := []struct { + level StoneLevel + expected string + }{ + {TRACE, "TRACE"}, + {DEBUG, "DEBUG"}, + {INFO, "INFO"}, + {WARN, "WARN"}, + {ERROR, "ERROR"}, + {FATAL, "FATAL"}, + {PANIC, "PANIC"}, + } + + for _, tt := range levelTests { + if tt.level.String() != tt.expected { + t.Errorf("Expected %s, got %s", tt.expected, tt.level.String()) + } + } +} + +func Test_LogLevelColor(t *testing.T) { + levelTests := []struct { + level StoneLevel + expected string + }{ + {TRACE, colorWhite}, + {DEBUG, colorCyan}, + {INFO, colorGreen}, + {WARN, colorYellow}, + {ERROR, colorRed}, + {FATAL, colorMagenta}, + {PANIC, colorRed + "\033[1m"}, + } + + for _, tt := range levelTests { + if tt.level.Color() != tt.expected { + t.Errorf("Expected %s, got %s", tt.expected, tt.level.Color()) + } + } +} -- 2.39.5 From b66a17197ee522ecff45a25170f40a375d8f088c Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Thu, 10 Apr 2025 08:30:19 -0600 Subject: [PATCH 05/10] Add paths for Go test and coverage reports in Sonar config This update includes `test-report.out` and `coverage.out` for improved test and coverage integration with SonarQube analysis. It ensures better reporting and alignment with project quality standards. --- sonar-project.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sonar-project.properties b/sonar-project.properties index 61d58a7..3e4456d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,4 +5,6 @@ sonar.sources=. sonar.exclusions=**/*_test.go sonar.tests=. sonar.test.inclusions=**/*_test.go +sonar.go.tests.reportPaths=test-report.out +sonar.go.coverage.reportPaths=coverage.out sonar.qualitygate.wait=true \ No newline at end of file -- 2.39.5 From 00e64881853ea72b6a9de22790ffcd9240f102f7 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Thu, 10 Apr 2025 09:50:54 -0600 Subject: [PATCH 06/10] Validate log level and improve test coverage Added validation to ensure log levels are within valid bounds in `SetLogLevel`. Simplified prefix mapping logic by removing the fallback to INFO level. Expanded test coverage with new test cases for plain text and JSON log outputs. --- log.go | 10 +++++----- log_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/log.go b/log.go index 95efac2..c4516e2 100644 --- a/log.go +++ b/log.go @@ -111,6 +111,10 @@ func InitStoneLog(level StoneLevel, jsonMode, disableSuffixes bool) { // SetLogLevel sets the current log level to the specified StoneLevel. func SetLogLevel(level StoneLevel) { + if level < TRACE || level > PANIC { + return + } + currentLogLevel = level Observation("Log level set to %v", level) } @@ -221,11 +225,7 @@ func getRandomPrefix(level StoneLevel) string { FATAL: stonePanicPrefixes, } - // Default to INFO prefixes if level is not in the map - prefixes, exists := prefixMap[level] - if !exists { - prefixes = stoneInfoPrefixes - } + prefixes := prefixMap[level] // Select and return a random prefix return prefixes[rand.Intn(len(prefixes))] diff --git a/log_test.go b/log_test.go index 51c6a2d..64442fa 100644 --- a/log_test.go +++ b/log_test.go @@ -45,3 +45,25 @@ func Test_LogLevelColor(t *testing.T) { } } } + +func Test_PlainTextLogs(t *testing.T) { + InitStoneLog(TRACE, false, false) + Trace("This is a trace log") + Debug("This is a debug log") + Observation("This is an observation log") + Hypothesis("This is a hypothesis log") + Failure("This is a failure log") + + SetLogLevel(INFO) +} + +func Test_JSONLogs(t *testing.T) { + InitStoneLog(TRACE, true, false) + Trace("This is a trace log") + Debug("This is a debug log") + Observation("This is an observation log") + Hypothesis("This is a hypothesis log") + Failure("This is a failure log") + + SetLogLevel(INFO) +} -- 2.39.5 From e84f7bd810922e9bf15f52d675114e65616a3cf1 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Thu, 10 Apr 2025 10:00:55 -0600 Subject: [PATCH 07/10] Add test for muted plaintext log behavior This test verifies that logs below the ERROR level are properly muted when plaintext logging is enabled without JSON formatting. It ensures that log output adheres to the configured log level settings. --- log_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/log_test.go b/log_test.go index 64442fa..fb83b29 100644 --- a/log_test.go +++ b/log_test.go @@ -57,6 +57,15 @@ func Test_PlainTextLogs(t *testing.T) { SetLogLevel(INFO) } +func Test_MutedPlainTextLogs(t *testing.T) { + InitStoneLog(ERROR, false, true) + Trace("This is a trace log") + Debug("This is a debug log") + Observation("This is an observation log") + Hypothesis("This is a hypothesis log") + Failure("This is a failure log") +} + func Test_JSONLogs(t *testing.T) { InitStoneLog(TRACE, true, false) Trace("This is a trace log") -- 2.39.5 From b7202bc141d462e71bc832faeb68d2cd85ccf274 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Thu, 10 Apr 2025 10:02:49 -0600 Subject: [PATCH 08/10] Add tests for handling invalid log levels in SetLogLevel Ensure that setting an invalid log level with SetLogLevel does not affect existing log behavior. Additional test cases verify that only appropriate log messages are displayed based on the current log level. --- log_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/log_test.go b/log_test.go index fb83b29..026ee90 100644 --- a/log_test.go +++ b/log_test.go @@ -59,10 +59,17 @@ func Test_PlainTextLogs(t *testing.T) { func Test_MutedPlainTextLogs(t *testing.T) { InitStoneLog(ERROR, false, true) + + // This won't change the log level + SetLogLevel(-1) + + // Following won't appear Trace("This is a trace log") Debug("This is a debug log") Observation("This is an observation log") Hypothesis("This is a hypothesis log") + + // Until this one Failure("This is a failure log") } -- 2.39.5 From d126d4ef7ab3f3f3957a0aabcbe2d7d2f8660868 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Thu, 10 Apr 2025 10:48:34 -0600 Subject: [PATCH 09/10] Add tests for panic and fatal log handling Introduce tests for handling panic and fatal log scenarios, including recovery from panics and execution of external processes to test fatal behavior. Adjusted logging levels and prefixes to ensure consistency for PANIC logs. --- log.go | 1 + log_test.go | 45 +++++++++++++++++++++++++++++++++++++-------- tests/main.go | 14 ++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/main.go diff --git a/log.go b/log.go index c4516e2..dbbe934 100644 --- a/log.go +++ b/log.go @@ -223,6 +223,7 @@ func getRandomPrefix(level StoneLevel) string { WARN: stoneWarnPrefixes, ERROR: stoneErrorPrefixes, FATAL: stonePanicPrefixes, + PANIC: stonePanicPrefixes, } prefixes := prefixMap[level] diff --git a/log_test.go b/log_test.go index 026ee90..a744ad9 100644 --- a/log_test.go +++ b/log_test.go @@ -1,6 +1,8 @@ package stonelog import ( + "errors" + "os/exec" "testing" ) @@ -46,6 +48,17 @@ func Test_LogLevelColor(t *testing.T) { } } +func Test_JSONLogs(t *testing.T) { + InitStoneLog(TRACE, true, false) + Trace("This is a trace log") + Debug("This is a debug log") + Observation("This is an observation log") + Hypothesis("This is a hypothesis log") + Failure("This is a failure log") + + SetLogLevel(INFO) +} + func Test_PlainTextLogs(t *testing.T) { InitStoneLog(TRACE, false, false) Trace("This is a trace log") @@ -73,13 +86,29 @@ func Test_MutedPlainTextLogs(t *testing.T) { Failure("This is a failure log") } -func Test_JSONLogs(t *testing.T) { - InitStoneLog(TRACE, true, false) - Trace("This is a trace log") - Debug("This is a debug log") - Observation("This is an observation log") - Hypothesis("This is a hypothesis log") - Failure("This is a failure log") +func Test_PanicPlainTextLogs(t *testing.T) { + defer func() { + if r := recover(); r != nil { + Observation("Recovered from panic: %v", r) + } + }() - SetLogLevel(INFO) + InitStoneLog(TRACE, false, true) + Panic("This is a panic log") +} + +func Test_FatalPlainTextLogs(t *testing.T) { + cmd := exec.Command( + "go", "run", "./test/main.go", + ) + cmd.Env = append(cmd.Env, "TEST_FATAL=1") + + err := cmd.Run() + + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + if exitErr.ExitCode() != 1 { + t.Errorf("Expected exit code 1, got %d", exitErr.ExitCode()) + } + } } diff --git a/tests/main.go b/tests/main.go new file mode 100644 index 0000000..d51eccb --- /dev/null +++ b/tests/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "gitstormr.dev/stone-utils/stonelog" +) + +func main() { + if os.Getenv("TEST_FATAL") == "1" { + stonelog.InitStoneLog(stonelog.TRACE, false, false) + stonelog.Meltdown("A fatal error occurred") + } +} -- 2.39.5 From a7c6a850846edba6691cd3448ae721034cfd8f15 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Thu, 10 Apr 2025 10:57:51 -0600 Subject: [PATCH 10/10] Refactor test binary location and update sonar exclusions Moved the test binary from "tests" to "testbin" for better organization. Updated `sonar-project.properties` to exclude the new directory from analysis and adjusted test references accordingly. --- log_test.go | 2 +- sonar-project.properties | 2 +- {tests => testbin}/main.go | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename {tests => testbin}/main.go (100%) diff --git a/log_test.go b/log_test.go index a744ad9..f7fcbb1 100644 --- a/log_test.go +++ b/log_test.go @@ -99,7 +99,7 @@ func Test_PanicPlainTextLogs(t *testing.T) { func Test_FatalPlainTextLogs(t *testing.T) { cmd := exec.Command( - "go", "run", "./test/main.go", + "go", "run", "./testbin/main.go", ) cmd.Env = append(cmd.Env, "TEST_FATAL=1") diff --git a/sonar-project.properties b/sonar-project.properties index 3e4456d..e3c95ed 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=b97a45b2-c25d-4fd1-898d-136414896ce6 sonar.projectName=mother-utils/motherlog sonar.language=go sonar.sources=. -sonar.exclusions=**/*_test.go +sonar.exclusions=**/*_test.go, testbin/** sonar.tests=. sonar.test.inclusions=**/*_test.go sonar.go.tests.reportPaths=test-report.out diff --git a/tests/main.go b/testbin/main.go similarity index 100% rename from tests/main.go rename to testbin/main.go -- 2.39.5