nip/src/nimpak/logger.nim

155 lines
4.1 KiB
Nim

# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## logger.nim
## Logging system for NIP MVP
import std/[times, strformat, os, strutils]
type
LogLevel* = enum
Debug, Info, Warning, Error, Fatal
Logger* = ref object
logFile*: string
minLevel*: LogLevel
enabled*: bool
verbose*: bool
var globalLogger*: Logger = nil
proc newLogger*(logFile: string = "/var/log/nip.log",
minLevel: LogLevel = Info,
verbose: bool = false): Logger =
## Create a new logger
result = Logger(
logFile: logFile,
minLevel: minLevel,
enabled: true,
verbose: verbose
)
proc initGlobalLogger*(logFile: string = "/var/log/nip.log",
minLevel: LogLevel = Info,
verbose: bool = false) =
## Initialize the global logger
globalLogger = newLogger(logFile, minLevel, verbose)
proc levelToString(level: LogLevel): string =
case level
of Debug: "DEBUG"
of Info: "INFO"
of Warning: "WARN"
of Error: "ERROR"
of Fatal: "FATAL"
proc log*(logger: Logger, level: LogLevel, message: string) =
## Log a message
if not logger.enabled:
return
if level < logger.minLevel:
return
let timestamp = now().format("yyyy-MM-dd HH:mm:ss")
let levelStr = levelToString(level)
let logLine = fmt"[{timestamp}] [{levelStr}] {message}"
# Print to console if verbose or error/fatal
if logger.verbose or level >= Error:
echo logLine
# Write to log file
try:
let logDir = parentDir(logger.logFile)
if not dirExists(logDir):
try:
createDir(logDir)
except:
# Can't create log directory, skip file logging
return
let file = open(logger.logFile, fmAppend)
file.writeLine(logLine)
file.close()
except IOError, OSError:
# Can't write to log file, just skip it
discard
# Convenience functions for global logger
proc logDebug*(message: string) =
if globalLogger != nil:
globalLogger.log(Debug, message)
proc logInfo*(message: string) =
if globalLogger != nil:
globalLogger.log(Info, message)
proc logWarning*(message: string) =
if globalLogger != nil:
globalLogger.log(Warning, message)
proc logError*(message: string) =
if globalLogger != nil:
globalLogger.log(Error, message)
proc logFatal*(message: string) =
if globalLogger != nil:
globalLogger.log(Fatal, message)
# Operation logging helpers
proc logOperation*(operation: string, details: string = "") =
let msg = if details != "": fmt"{operation}: {details}" else: operation
logInfo(msg)
proc logSuccess*(operation: string, details: string = "") =
let msg = if details != "": fmt"✅ {operation}: {details}" else: fmt"✅ {operation}"
logInfo(msg)
proc logFailure*(operation: string, error: string) =
logError(fmt"❌ {operation} failed: {error}")
proc logException*(operation: string, e: ref Exception) =
logError(fmt"Exception in {operation}: {e.msg}")
if globalLogger != nil and globalLogger.verbose:
logError(e.getStackTrace())
# Log rotation
proc rotateLog*(logger: Logger, maxSize: int64 = 10_000_000) =
## Rotate log file if it exceeds maxSize (default 10MB)
if not fileExists(logger.logFile):
return
try:
let size = getFileSize(logger.logFile)
if size > maxSize:
# Rotate: nip.log -> nip.log.1, nip.log.1 -> nip.log.2, etc.
for i in countdown(2, 1):
let oldFile = logger.logFile & "." & $i
let newFile = logger.logFile & "." & $(i + 1)
if fileExists(oldFile):
moveFile(oldFile, newFile)
# Move current log to .1
moveFile(logger.logFile, logger.logFile & ".1")
logInfo("Log file rotated")
except:
discard
proc getLogPath*(): string =
## Get the current log file path
if globalLogger != nil:
return globalLogger.logFile
else:
return "/var/log/nip.log"
proc setVerbose*(verbose: bool) =
## Set verbose mode for global logger
if globalLogger != nil:
globalLogger.verbose = verbose