logimpl/logimpl.go

451 lines
11 KiB
Go

// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// logimpl.go
package logimpl
import (
"fmt"
"io/fs"
"os"
"path"
"slices"
"strings"
"time"
"git.portale-stac.it/go-pkg/golang"
"git.portale-stac.it/go-pkg/logger"
"git.portale-stac.it/go-pkg/utils"
)
// Implementation specific properties
const (
// Max log file size that triggers the log rotation process
// Rotation-Size == 0 means no rotation at all
FULL_LOGGER_ROTATION_SIZE = int(iota + logger.IMPLEMENTATION_PROPERTY_BASE_ID)
// Number of most recent files to keep
FULL_LOGGER_RETENTION_NUMBER
// Calling function offset in the process call stack
FULL_LOGGER_STACK_OFFSET
// Enable raw data dump into log file. See Dumpf() function.
FULL_LOGGER_DUMPER_ENABLED
// ATTENTION: Add new property id above this line
FULL_LOGGER_DERIVED_BASE_ID
)
// Log levels
const (
LOG_NOLOG = int(iota) - 1
LOG_OOB
LOG_FATAL
LOG_ERROR
LOG_WARN
LOG_INFO
LOG_DEBUG
)
const (
MIN_LOG_ROTATE_SIZE = int64(5000)
LOG_NO_SIZE_LIMIT = int64(0)
)
const (
oobOffset = 1000
defaultMaxMessageLength = 1024
baseStackOffset = 3
)
var LOG_CLASS_COLOR = []uint{utils.BLACK, utils.RED, utils.RED, utils.MAGENTA, utils.BROWN, utils.CYAN}
var logClassLabel = []string{"OOB", "FATAL", "ERROR", "WARN", "INFO", "DEBUG"}
type logImpl struct {
// TTY utils.TTYContext
appendMode bool
enabled bool //Variabile di controllo per bloccare la creazione del log prima che i suoi parametri siano acquisiti
filePath string
flags int
rotateNumber int
maxSize int64
size int64
stream *os.File
maxLevel int
debugOnStderr bool
maxMessageLength int
stackOffset int
dumperEnabled bool
customProperties map[int]any
}
func NewAppendLogger(fileName string, maxLevel int) logger.FullLogger {
return newLogger(fileName, maxLevel, true)
}
func NewLogger(fileName string, maxLevel int) logger.FullLogger {
return newLogger(fileName, maxLevel, false)
}
func newLogger(filePath string, maxLevel int, appendMode bool) logger.FullLogger {
return (&logImpl{}).initLogger(filePath, maxLevel, appendMode)
}
func (log *logImpl) initLogger(filePath string, maxLevel int, appendMode bool) *logImpl {
log.filePath = filePath
log.maxSize = LOG_NO_SIZE_LIMIT
log.size = 0
log.stream = nil
log.maxLevel = maxLevel
log.appendMode = appendMode
log.maxMessageLength = defaultMaxMessageLength
log.stackOffset = baseStackOffset
log.dumperEnabled = false
log.customProperties = make(map[int]any)
// log.TTY.Init()
return log
}
func (log *logImpl) SetProperty(propertyId int, value any) (success bool) {
var intValue int
var int64Value int64
var boolValue bool
switch propertyId {
case FULL_LOGGER_STACK_OFFSET:
if intValue, success = value.(int); success {
if intValue < baseStackOffset {
intValue = baseStackOffset
success = false
}
log.stackOffset = intValue
}
case logger.LOGGER_MAX_MESSAGE_LENGTH:
if intValue, success = value.(int); success {
log.maxMessageLength = intValue
}
case FULL_LOGGER_ROTATION_SIZE:
if int64Value, success = value.(int64); success {
log.maxSize = int64Value
}
case FULL_LOGGER_RETENTION_NUMBER:
if intValue, success = value.(int); success {
log.rotateNumber = intValue
}
case FULL_LOGGER_DUMPER_ENABLED:
if boolValue, success = value.(bool); success {
log.dumperEnabled = boolValue
}
default:
log.customProperties[propertyId] = value
}
return
}
func (log *logImpl) GetProperty(propertyId int) (value any) {
switch propertyId {
case FULL_LOGGER_STACK_OFFSET:
value = log.stackOffset
case logger.LOGGER_MAX_MESSAGE_LENGTH:
value = log.maxMessageLength
case FULL_LOGGER_ROTATION_SIZE:
value = log.maxSize
case FULL_LOGGER_RETENTION_NUMBER:
value = log.rotateNumber
default:
if v, ok := log.customProperties[propertyId]; ok {
value = v
}
}
return
}
func (log *logImpl) GetPropertyBool(propertyId int) (value bool, ok bool) {
if rawValue := log.GetProperty(propertyId); rawValue != nil {
value, ok = rawValue.(bool)
}
return
}
func (log *logImpl) Finalize() {
if log.stream != nil {
log.stream.Sync()
log.stream = nil
}
}
func Label2Level(label string) (level int, found bool) {
label = strings.ToUpper(label)
for i, lab := range logClassLabel {
if lab == label {
level = i
found = true
break
}
}
return
}
func LogClassLabel(level int) (label string) {
if level >= 0 && level < len(logClassLabel) {
label = logClassLabel[level]
} else {
label = "unknown"
}
return
}
func (log *logImpl) SetRotation(maxSize int64, rotateNumber int) {
log.maxSize = maxSize
log.rotateNumber = rotateNumber
}
func (log *logImpl) Logf(logClass int, upStack int, templ string, args ...any) (text string) {
if logClass >= oobOffset {
logClass = logClass - oobOffset
} else if logClass > log.maxLevel {
return
}
stream := log.getLog()
if stream != nil {
source, line, _ := golang.Trace(upStack + log.stackOffset)
now := time.Now()
// stamp := now.Format("2006-01-02 15:04:05.999")
stamp := now.Format("2006-01-02 15:04:05")
// label := logimpl.LOG_CLASS_LABEL[logClass]
label := LogClassLabel(logClass)
header := fmt.Sprintf("%v [%-5s] %s::%d -- ", stamp, label, path.Base(source), line)
// templ = header + templ
msg := fmt.Sprintf(templ, args...)
text = msg
msgLen := len(msg) + len(header) + 1
if msgLen > log.maxMessageLength {
msg = msg[0 : log.maxMessageLength-len(header)-1]
msgLen = len(msg)
}
// Rotazione
log.rotateLogFiles(msgLen)
n, _ := log.stream.WriteString(header)
n, _ = log.stream.WriteString(msg)
log.stream.Write([]byte{'\n'})
log.size += int64(n)
}
return
}
func (log *logImpl) Rawb(logClass int, msg []byte) {
if logClass >= oobOffset {
logClass = logClass - oobOffset
} else if logClass > log.maxLevel {
return
}
stream := log.getLog()
if stream != nil {
msgLen := len(msg)
// Rotazione
log.rotateLogFiles(msgLen)
n, _ := log.stream.Write(msg)
log.size += int64(n)
}
}
func (log *logImpl) Rawf(logClass int, templ string, args ...any) {
if logClass >= oobOffset {
logClass = logClass - oobOffset
} else if logClass > log.maxLevel {
return
}
stream := log.getLog()
if stream != nil {
msg := fmt.Sprintf(templ, args...)
msgLen := len(msg)
// Rotazione
log.rotateLogFiles(msgLen)
n, _ := log.stream.Write([]byte(msg))
log.size += int64(n)
}
}
func (log *logImpl) Dumpf(data []byte, perm fs.FileMode, templ string, args ...any) (dumpFilePath string) {
if log.dumperEnabled {
dirPath := path.Dir(log.filePath)
dumpFilePath = path.Join(dirPath, fmt.Sprintf(templ, args...))
os.WriteFile(dumpFilePath, data, perm)
}
return
}
func (log *logImpl) OobLogf(templ string, args ...any) {
log.Logf(oobOffset, 1, templ, args...)
}
func (log *logImpl) IsDebugEnabled() bool {
return log.maxLevel >= LOG_DEBUG
}
func (log *logImpl) IsInfoEnabled() bool {
return log.maxLevel >= LOG_INFO
}
func (log *logImpl) Debugf(templ string, args ...any) {
// Se la variabile d'ambiente <prefix>_DEBUG è impostata
// i messaggi di debug sono mostrati anche su stderr
if log.debugOnStderr {
// var extTempl string
source, line, _ := golang.Trace(3)
header := fmt.Sprintf("[%5s] %s::%d -- ", logClassLabel[LOG_DEBUG], path.Base(source), line)
// fd := uint(os.Stderr.Fd())
// if log.TTY.IsTTY(fd) {
// c := log.TTY.FgColor(fd, utils.CYAN)
// extTempl = c + header + templ + log.TTY.Reset(fd) + "\n"
// } else {
// extTempl = header + templ + "\n"
// }
extTempl := header + templ + "\n"
fmt.Fprintf(os.Stderr, extTempl, args...)
}
log.Logf(LOG_DEBUG, 1, templ, args...)
}
func (log *logImpl) DebugRawb(msg []byte) {
// Se la variabile d'ambiente <prefix>_DEBUG è impostata
// i messaggi di debug sono mostrati anche su stderr
if log.debugOnStderr {
os.Stderr.Write(msg)
}
log.Rawb(LOG_DEBUG, msg)
}
func (log *logImpl) DebugRawf(templ string, args ...any) {
// Se la variabile d'ambiente <prefix>_DEBUG è impostata
// i messaggi di debug sono mostrati anche su stderr
if log.debugOnStderr {
fmt.Fprintf(os.Stderr, templ, args...)
}
log.Rawf(LOG_DEBUG, templ, args...)
}
func (log *logImpl) Infof(templ string, args ...any) {
log.Logf(LOG_INFO, 1, templ, args...)
}
func (log *logImpl) InfoRawf(templ string, args ...any) {
log.Rawf(LOG_INFO, templ, args...)
}
func (log *logImpl) Warnf(templ string, args ...any) {
log.Logf(LOG_WARN, 1, templ, args...)
}
func (log *logImpl) WarnRawf(templ string, args ...any) {
log.Rawf(LOG_WARN, templ, args...)
}
func (log *logImpl) Errorf(templ string, args ...any) {
log.Logf(LOG_ERROR, 1, templ, args...)
}
func (log *logImpl) ErrorRawf(templ string, args ...any) {
log.Rawf(LOG_ERROR, templ, args...)
}
func (log *logImpl) getLog() *os.File {
if log.maxLevel == LOG_OOB {
return nil
}
if log.stream == nil && len(log.filePath) > 0 {
var err error
log.flags = os.O_CREATE | os.O_RDWR
if log.appendMode {
log.flags |= os.O_APPEND
} else {
log.flags |= os.O_TRUNC
}
if _, err = utils.MakeParentDir(log.filePath); err != nil {
return nil
}
log.stream, err = os.OpenFile(log.filePath, log.flags, 0644)
if err == nil {
log.size = utils.FileSize(log.filePath)
// self.Logf(1000+LOG_INFO, 0, VERSION)
} else {
utils.ExitErrorf(1, "Can't open log file %#v: %v", log.filePath, err)
}
log.reduceLogStock()
}
return log.stream
}
func (log *logImpl) rotateLogFiles(msgLen int) {
if log.maxSize != LOG_NO_SIZE_LIMIT && (log.size+int64(msgLen)) >= log.maxSize {
var err error
// Rotazione
now := time.Now()
stamp := now.Format("2006-01-02_15-04-05")
rotFile := log.filePath + "." + stamp
_, err = utils.MoveFile(log.filePath, rotFile)
if err != nil {
utils.ExitErrorf(1, "Unable to rotate current log file: %v", err)
}
log.stream, err = os.OpenFile(log.filePath, log.flags, 0644)
if err != nil {
utils.ExitErrorf(1, "Can't open log file %#v after rotation: %v", log.filePath, err)
}
log.size = 0
// Sfoltimento
log.reduceLogStock()
}
}
func (log *logImpl) reduceLogStock() {
if log.rotateNumber > 0 {
dirPath := path.Dir(log.filePath)
logName := path.Base(log.filePath)
entries, err := os.ReadDir(dirPath)
if err != nil {
utils.ExitErrorf(1, "Can't read program's log directory %#v: %v", dirPath, err)
}
logFiles := make([]string, 0)
rotatedNameSize := len(logName) + 20
for _, e := range entries {
if !e.IsDir() {
name := e.Name()
if len(name) == rotatedNameSize && strings.HasPrefix(name, logName) {
logFiles = append(logFiles, name)
}
}
}
slices.Sort(logFiles)
remainingRotatedCount := len(logFiles)
if remainingRotatedCount > log.rotateNumber {
for i := 0; i < len(logFiles) && remainingRotatedCount > log.rotateNumber; i++ {
filePath := path.Join(dirPath, logFiles[i])
os.Remove(filePath)
remainingRotatedCount--
}
}
}
}