Compare commits

...

13 Commits

Author SHA1 Message Date
83953b7d27
Merge pull request 'Update project name in sonar configuration' (#3) from feature/fix-sonar-project-name into develop
All checks were successful
Go CI/CD / go-ci (push) Successful in 10m4s
Go CI/CD / go-ci (pull_request) Successful in 10m6s
Reviewed-on: #3
Reviewed-by: Cloud Administrator <cloud-admin@noreply.gitstormr.dev>
2025-04-13 11:16:50 -06:00
8bf3fbfaa3
Update project name in sonar configuration
All checks were successful
Go CI/CD / go-ci (push) Successful in 10m35s
Go CI/CD / go-ci (pull_request) Successful in 10m33s
Renamed the project from "mother-utils/motherlog" to "stone-utils/stonelog" in the SonarQube configuration file. This ensures accurate project identification and alignment with the repository's purpose.
2025-04-13 10:58:32 -06:00
6df531d73b
Merge pull request 'Log System Implementation' (#1) from feature/log-system-implementation into develop
All checks were successful
Go CI/CD / go-ci (push) Successful in 10m5s
Go CI/CD / go-ci (pull_request) Successful in 10m6s
Reviewed-on: #1
Reviewed-by: Cloud Administrator <cloud-admin@noreply.gitstormr.dev>
2025-04-10 18:03:57 -06:00
a7c6a85084
Refactor test binary location and update sonar exclusions
All checks were successful
Go CI/CD / go-ci (push) Successful in 10m8s
Go CI/CD / go-ci (pull_request) Successful in 10m12s
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.
2025-04-10 10:57:51 -06:00
d126d4ef7a
Add tests for panic and fatal log handling
Some checks failed
Go CI/CD / go-ci (push) Has been cancelled
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.
2025-04-10 10:48:34 -06:00
b7202bc141
Add tests for handling invalid log levels in SetLogLevel
All checks were successful
Go CI/CD / go-ci (push) Successful in 10m4s
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.
2025-04-10 10:02:49 -06:00
e84f7bd810
Add test for muted plaintext log behavior
Some checks failed
Go CI/CD / go-ci (push) Has been cancelled
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.
2025-04-10 10:00:55 -06:00
00e6488185
Validate log level and improve test coverage
Some checks failed
Go CI/CD / go-ci (push) Has been cancelled
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.
2025-04-10 09:50:54 -06:00
b66a17197e
Add paths for Go test and coverage reports in Sonar config
Some checks failed
Go CI/CD / go-ci (push) Failing after 6m52s
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.
2025-04-10 08:30:19 -06:00
4557b8050c
Add tests for log level string and color representations
Some checks failed
Go CI/CD / go-ci (push) Failing after 6m59s
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.
2025-04-10 08:12:56 -06:00
8fad79b823
Set SonarQube to wait for quality gate results.
All checks were successful
Go CI/CD / go-ci (push) Successful in 10m8s
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.
2025-04-09 23:28:01 -06:00
4046c447cc
Refactor getRandomPrefix to simplify StoneLevel mapping.
All checks were successful
Go CI/CD / go-ci (push) Successful in 10m4s
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.
2025-04-09 23:04:35 -06:00
94c6e882fb
Add a structured logging utility with custom prefixes and suffixes
Some checks failed
Go CI/CD / go-ci (push) Has been cancelled
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.
2025-04-09 22:54:16 -06:00
7 changed files with 523 additions and 10 deletions

View File

@ -19,6 +19,70 @@ The most scientifically badass logging library from the Kingdom of Science!
go get gitstormr.dev/stone-utils/stonelog@latest 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!** **Join the Scientific Revolution!**
> "This isn't just logging - it's 10 billion percent scientific progress!" - Senku Ishigami > "This isn't just logging - it's 10 billion percent scientific progress!" - Senku Ishigami

255
log.go Normal file
View File

@ -0,0 +1,255 @@
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) {
if level < TRACE || level > PANIC {
return
}
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 string based on the given StoneLevel.
// It selects from predefined lists corresponding to the severity of the level.
func getRandomPrefix(level StoneLevel) string {
// Map StoneLevel to their respective prefix slices
prefixMap := map[StoneLevel][]string{
TRACE: stoneTracePrefixes,
DEBUG: stoneDebugPrefixes,
INFO: stoneInfoPrefixes,
WARN: stoneWarnPrefixes,
ERROR: stoneErrorPrefixes,
FATAL: stonePanicPrefixes,
PANIC: stonePanicPrefixes,
}
prefixes := prefixMap[level]
// Select and return a random prefix
return prefixes[rand.Intn(len(prefixes))]
}
// 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,
)
}

114
log_test.go Normal file
View File

@ -0,0 +1,114 @@
package stonelog
import (
"errors"
"os/exec"
"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())
}
}
}
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")
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_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")
}
func Test_PanicPlainTextLogs(t *testing.T) {
defer func() {
if r := recover(); r != nil {
Observation("Recovered from panic: %v", r)
}
}()
InitStoneLog(TRACE, false, true)
Panic("This is a panic log")
}
func Test_FatalPlainTextLogs(t *testing.T) {
cmd := exec.Command(
"go", "run", "./testbin/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())
}
}
}

View File

@ -1,10 +1,17 @@
package stonelog 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 ( var (
stoneTracePrefixes = []string{ stoneTracePrefixes = []string{
"Science traces", "Science traces:",
"Lab sensors detect", "Lab sensors detect:",
"Microscope focus on", "Microscope focus on:",
"Quantum scanner active:", "Quantum scanner active:",
"Data particles observed:", "Data particles observed:",
} }
@ -41,10 +48,19 @@ var (
"Hypothesis disproven:", "Hypothesis disproven:",
} }
stoneFatalPrefixes = []string{
"SCIENTIFIC APOCALYPSE:",
"FINAL EXPERIMENT FAILED:",
"LABORATORY SHUTDOWN:",
"CORE CRITICAL:",
"RADIOACTIVE TERMINATION:",
}
stonePanicPrefixes = []string{ stonePanicPrefixes = []string{
"‘‘‘CATASTROPHIC FAILURE!!!", "CHAIN REACTION DETECTED:",
"Emergency protocol FAILED:", "SYSTEMIC COLLAPSE:",
"LEAVE THE LAB!", "QUANTUM CATASTROPHE:",
"‘‘‘IMMINENT NUCLEAR FUSION!!!", "GALACTIC-LEVEL BUG DETECTED:",
"T-REX IN THE DATACENTER:",
} }
) )

View File

@ -1,7 +1,10 @@
sonar.projectKey=b97a45b2-c25d-4fd1-898d-136414896ce6 sonar.projectKey=b97a45b2-c25d-4fd1-898d-136414896ce6
sonar.projectName=mother-utils/motherlog sonar.projectName=stone-utils/stonelog
sonar.language=go sonar.language=go
sonar.sources=. sonar.sources=.
sonar.exclusions=**/*_test.go sonar.exclusions=**/*_test.go, testbin/**
sonar.tests=. sonar.tests=.
sonar.test.inclusions=**/*_test.go sonar.test.inclusions=**/*_test.go
sonar.go.tests.reportPaths=test-report.out
sonar.go.coverage.reportPaths=coverage.out
sonar.qualitygate.wait=true

47
suffixes.go Normal file
View File

@ -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.'",
}
)

14
testbin/main.go Normal file
View File

@ -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")
}
}