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.
This commit is contained in:
Rene Nochebuena 2025-04-09 22:54:16 -06:00
parent c92275602f
commit 94c6e882fb
Signed by: Rene Nochebuena
GPG Key ID: A9FD83117EA538D8
4 changed files with 398 additions and 7 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
```
## ⚡ 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

264
log.go Normal file
View File

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

View File

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

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! Thats how you optimize!'",
" // Senku: 'This experiment... is a 10 billion percent success!'",
" // Chrome: 'SO BADASS! SENKU, YOURE A GENIUS!'",
" // Chrome: 'HOLY CRAP, SCIENCE JUST PUNCHED THE SKY!'",
" // Chrome: 'WERE OFFICIALLY WIZARDS NOW!'",
" // Kohaku: 'Heh. Even I couldve done that... maybe.'",
" // Kohaku: 'Science + fists = unstoppable!'",
" // Ryusui: 'NAVIGATION SUCCESSFUL! Time to monetize this!'",
" // Ryusui: 'Money cant buy this... BUT ILL TRY!'",
" // Kaseki: 'HOT BLOODED ENGINEERING... PERFECTED! (ᗒᗣᗕ)՞'",
" // Kaseki: 'ILL 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, were 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: 'Ill prepare a funeral tea for the deceased process.'",
}
)