451 lines
11 KiB
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--
|
|
}
|
|
}
|
|
}
|
|
}
|