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