mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 00:13:46 +01:00
add lumberjack log rotation (#5696)
* [logging] add UI and graphql for maximum log size * [logger] set default size to 0MB and don't rotate
This commit is contained in:
parent
2f65a1da3e
commit
78aeb06f20
11 changed files with 64 additions and 19 deletions
|
|
@ -110,7 +110,7 @@ func main() {
|
||||||
// Logs only error level message to stderr.
|
// Logs only error level message to stderr.
|
||||||
func initLogTemp() *log.Logger {
|
func initLogTemp() *log.Logger {
|
||||||
l := log.NewLogger()
|
l := log.NewLogger()
|
||||||
l.Init("", true, "Error")
|
l.Init("", true, "Error", 0)
|
||||||
logger.Logger = l
|
logger.Logger = l
|
||||||
|
|
||||||
return l
|
return l
|
||||||
|
|
@ -118,7 +118,7 @@ func initLogTemp() *log.Logger {
|
||||||
|
|
||||||
func initLog(cfg *config.Config) *log.Logger {
|
func initLog(cfg *config.Config) *log.Logger {
|
||||||
l := log.NewLogger()
|
l := log.NewLogger()
|
||||||
l.Init(cfg.GetLogFile(), cfg.GetLogOut(), cfg.GetLogLevel())
|
l.Init(cfg.GetLogFile(), cfg.GetLogOut(), cfg.GetLogLevel(), cfg.GetLogFileMaxSize())
|
||||||
logger.Logger = l
|
logger.Logger = l
|
||||||
|
|
||||||
return l
|
return l
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -63,6 +63,7 @@ require (
|
||||||
golang.org/x/text v0.25.0
|
golang.org/x/text v0.25.0
|
||||||
golang.org/x/time v0.10.0
|
golang.org/x/time v0.10.0
|
||||||
gopkg.in/guregu/null.v4 v4.0.0
|
gopkg.in/guregu/null.v4 v4.0.0
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -1122,6 +1122,8 @@ gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,8 @@ input ConfigGeneralInput {
|
||||||
logLevel: String
|
logLevel: String
|
||||||
"Whether to log http access"
|
"Whether to log http access"
|
||||||
logAccess: Boolean
|
logAccess: Boolean
|
||||||
|
"Maximum log size"
|
||||||
|
logFileMaxSize: Int
|
||||||
"True if galleries should be created from folders with images"
|
"True if galleries should be created from folders with images"
|
||||||
createGalleriesFromFolders: Boolean
|
createGalleriesFromFolders: Boolean
|
||||||
"Regex used to identify images as gallery covers"
|
"Regex used to identify images as gallery covers"
|
||||||
|
|
@ -279,6 +281,8 @@ type ConfigGeneralResult {
|
||||||
logLevel: String!
|
logLevel: String!
|
||||||
"Whether to log http access"
|
"Whether to log http access"
|
||||||
logAccess: Boolean!
|
logAccess: Boolean!
|
||||||
|
"Maximum log size"
|
||||||
|
logFileMaxSize: Int!
|
||||||
"Array of video file extensions"
|
"Array of video file extensions"
|
||||||
videoExtensions: [String!]!
|
videoExtensions: [String!]!
|
||||||
"Array of image file extensions"
|
"Array of image file extensions"
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,10 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
|
||||||
logger.SetLogLevel(*input.LogLevel)
|
logger.SetLogLevel(*input.LogLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.LogFileMaxSize != nil && *input.LogFileMaxSize != c.GetLogFileMaxSize() {
|
||||||
|
c.SetInt(config.LogFileMaxSize, *input.LogFileMaxSize)
|
||||||
|
}
|
||||||
|
|
||||||
if input.Excludes != nil {
|
if input.Excludes != nil {
|
||||||
for _, r := range input.Excludes {
|
for _, r := range input.Excludes {
|
||||||
_, err := regexp.Compile(r)
|
_, err := regexp.Compile(r)
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ func makeConfigGeneralResult() *ConfigGeneralResult {
|
||||||
LogOut: config.GetLogOut(),
|
LogOut: config.GetLogOut(),
|
||||||
LogLevel: config.GetLogLevel(),
|
LogLevel: config.GetLogLevel(),
|
||||||
LogAccess: config.GetLogAccess(),
|
LogAccess: config.GetLogAccess(),
|
||||||
|
LogFileMaxSize: config.GetLogFileMaxSize(),
|
||||||
VideoExtensions: config.GetVideoExtensions(),
|
VideoExtensions: config.GetVideoExtensions(),
|
||||||
ImageExtensions: config.GetImageExtensions(),
|
ImageExtensions: config.GetImageExtensions(),
|
||||||
GalleryExtensions: config.GetGalleryExtensions(),
|
GalleryExtensions: config.GetGalleryExtensions(),
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogItem struct {
|
type LogItem struct {
|
||||||
|
|
@ -41,8 +43,8 @@ func NewLogger() *Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialises the logger based on a logging configuration
|
// Init initialises the logger based on a logging configuration
|
||||||
func (log *Logger) Init(logFile string, logOut bool, logLevel string) {
|
func (log *Logger) Init(logFile string, logOut bool, logLevel string, logFileMaxSize int) {
|
||||||
var file *os.File
|
var logger io.WriteCloser
|
||||||
customFormatter := new(logrus.TextFormatter)
|
customFormatter := new(logrus.TextFormatter)
|
||||||
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
|
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
|
||||||
customFormatter.ForceColors = true
|
customFormatter.ForceColors = true
|
||||||
|
|
@ -57,30 +59,38 @@ func (log *Logger) Init(logFile string, logOut bool, logLevel string) {
|
||||||
// the access log colouring not being applied
|
// the access log colouring not being applied
|
||||||
_, _ = customFormatter.Format(logrus.NewEntry(log.logger))
|
_, _ = customFormatter.Format(logrus.NewEntry(log.logger))
|
||||||
|
|
||||||
|
// if size is 0, disable rotation
|
||||||
if logFile != "" {
|
if logFile != "" {
|
||||||
var err error
|
if logFileMaxSize == 0 {
|
||||||
file, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
var err error
|
||||||
|
logger, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Could not open '%s' for log output due to error: %s\n", logFile, err.Error())
|
fmt.Fprintf(os.Stderr, "unable to open log file %s: %v\n", logFile, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger = &lumberjack.Logger{
|
||||||
|
Filename: logFile,
|
||||||
|
MaxSize: logFileMaxSize, // Megabytes
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if file != nil {
|
if logger != nil {
|
||||||
if logOut {
|
if logOut {
|
||||||
// log to file separately disabling colours
|
// log to file separately disabling colours
|
||||||
fileFormatter := new(logrus.TextFormatter)
|
fileFormatter := new(logrus.TextFormatter)
|
||||||
fileFormatter.TimestampFormat = customFormatter.TimestampFormat
|
fileFormatter.TimestampFormat = customFormatter.TimestampFormat
|
||||||
fileFormatter.FullTimestamp = customFormatter.FullTimestamp
|
fileFormatter.FullTimestamp = customFormatter.FullTimestamp
|
||||||
log.logger.AddHook(&fileLogHook{
|
log.logger.AddHook(&fileLogHook{
|
||||||
Writer: file,
|
Writer: logger,
|
||||||
Formatter: fileFormatter,
|
Formatter: fileFormatter,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// logging to file only
|
// logging to file only
|
||||||
// turn off the colouring for the file
|
// turn off the colouring for the file
|
||||||
customFormatter.ForceColors = false
|
customFormatter.ForceColors = false
|
||||||
log.logger.Out = file
|
log.logger.Out = logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,13 +252,15 @@ const (
|
||||||
DLNAPortDefault = 1338
|
DLNAPortDefault = 1338
|
||||||
|
|
||||||
// Logging options
|
// Logging options
|
||||||
LogFile = "logfile"
|
LogFile = "logfile"
|
||||||
LogOut = "logout"
|
LogOut = "logout"
|
||||||
defaultLogOut = true
|
defaultLogOut = true
|
||||||
LogLevel = "loglevel"
|
LogLevel = "loglevel"
|
||||||
defaultLogLevel = "Info"
|
defaultLogLevel = "Info"
|
||||||
LogAccess = "logaccess"
|
LogAccess = "logaccess"
|
||||||
defaultLogAccess = true
|
defaultLogAccess = true
|
||||||
|
LogFileMaxSize = "logfile_max_size"
|
||||||
|
defaultLogFileMaxSize = 0 // megabytes, default disabled
|
||||||
|
|
||||||
// Default settings
|
// Default settings
|
||||||
DefaultScanSettings = "defaults.scan_task"
|
DefaultScanSettings = "defaults.scan_task"
|
||||||
|
|
@ -1636,6 +1638,16 @@ func (i *Config) GetLogAccess() bool {
|
||||||
return i.getBoolDefault(LogAccess, defaultLogAccess)
|
return i.getBoolDefault(LogAccess, defaultLogAccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLogFileMaxSize returns the maximum size of the log file in megabytes for lumberjack to rotate
|
||||||
|
func (i *Config) GetLogFileMaxSize() int {
|
||||||
|
value := i.getInt(LogFileMaxSize)
|
||||||
|
if value < 0 {
|
||||||
|
value = defaultLogFileMaxSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
// Max allowed graphql upload size in megabytes
|
// Max allowed graphql upload size in megabytes
|
||||||
func (i *Config) GetMaxUploadSize() int64 {
|
func (i *Config) GetMaxUploadSize() int64 {
|
||||||
i.RLock()
|
i.RLock()
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||||
logOut
|
logOut
|
||||||
logLevel
|
logLevel
|
||||||
logAccess
|
logAccess
|
||||||
|
logFileMaxSize
|
||||||
createGalleriesFromFolders
|
createGalleriesFromFolders
|
||||||
galleryCoverRegex
|
galleryCoverRegex
|
||||||
videoExtensions
|
videoExtensions
|
||||||
|
|
|
||||||
|
|
@ -465,6 +465,14 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||||
checked={general.logAccess ?? false}
|
checked={general.logAccess ?? false}
|
||||||
onChange={(v) => saveGeneral({ logAccess: v })}
|
onChange={(v) => saveGeneral({ logAccess: v })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<NumberSetting
|
||||||
|
id="log-file-max-size"
|
||||||
|
headingID="config.general.auth.log_file_max_size"
|
||||||
|
subHeadingID="config.general.auth.log_file_max_size_desc"
|
||||||
|
value={general.logFileMaxSize ?? 10}
|
||||||
|
onChange={(v) => saveGeneral({ logFileMaxSize: v })}
|
||||||
|
/>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,8 @@
|
||||||
"log_http_desc": "Logs http access to the terminal. Requires restart.",
|
"log_http_desc": "Logs http access to the terminal. Requires restart.",
|
||||||
"log_to_terminal": "Log to terminal",
|
"log_to_terminal": "Log to terminal",
|
||||||
"log_to_terminal_desc": "Logs to the terminal in addition to a file. Always true if file logging is disabled. Requires restart.",
|
"log_to_terminal_desc": "Logs to the terminal in addition to a file. Always true if file logging is disabled. Requires restart.",
|
||||||
|
"log_file_max_size": "Maximum log size",
|
||||||
|
"log_file_max_size_desc": "Maximum size in megabytes of the log file before it is compressed. 0MB is disabled. Requires restart.",
|
||||||
"maximum_session_age": "Maximum Session Age",
|
"maximum_session_age": "Maximum Session Age",
|
||||||
"maximum_session_age_desc": "Maximum idle time before a login session is expired, in seconds. Requires restart.",
|
"maximum_session_age_desc": "Maximum idle time before a login session is expired, in seconds. Requires restart.",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue