mirror of https://github.com/stashapp/stash.git
Add logging options (#154)
* Add various log options * Remove logFormat. Add UI for log config * Fix UI boolean flags
This commit is contained in:
parent
f29509577a
commit
564786f968
|
@ -4,6 +4,10 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
|||
generatedPath
|
||||
username
|
||||
password
|
||||
logFile
|
||||
logOut
|
||||
logLevel
|
||||
logAccess
|
||||
}
|
||||
|
||||
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||
|
|
|
@ -9,6 +9,14 @@ input ConfigGeneralInput {
|
|||
username: String
|
||||
"""Password"""
|
||||
password: String
|
||||
"""Name of the log file"""
|
||||
logFile: String
|
||||
"""Whether to also output to stderr"""
|
||||
logOut: Boolean!
|
||||
"""Minimum log level"""
|
||||
logLevel: String!
|
||||
"""Whether to log http access"""
|
||||
logAccess: Boolean!
|
||||
}
|
||||
|
||||
type ConfigGeneralResult {
|
||||
|
@ -22,6 +30,14 @@ type ConfigGeneralResult {
|
|||
username: String!
|
||||
"""Password"""
|
||||
password: String!
|
||||
"""Name of the log file"""
|
||||
logFile: String
|
||||
"""Whether to also output to stderr"""
|
||||
logOut: Boolean!
|
||||
"""Minimum log level"""
|
||||
logLevel: String!
|
||||
"""Whether to log http access"""
|
||||
logAccess: Boolean!
|
||||
}
|
||||
|
||||
input ConfigInterfaceInput {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
@ -50,6 +51,18 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
|
|||
}
|
||||
}
|
||||
|
||||
if input.LogFile != nil {
|
||||
config.Set(config.LogFile, input.LogFile)
|
||||
}
|
||||
|
||||
config.Set(config.LogOut, input.LogOut)
|
||||
config.Set(config.LogAccess, input.LogAccess)
|
||||
|
||||
if input.LogLevel != config.GetLogLevel() {
|
||||
config.Set(config.LogLevel, input.LogLevel)
|
||||
logger.SetLogLevel(input.LogLevel)
|
||||
}
|
||||
|
||||
if err := config.Write(); err != nil {
|
||||
return makeConfigGeneralResult(), err
|
||||
}
|
||||
|
|
|
@ -28,12 +28,17 @@ func makeConfigResult() *models.ConfigResult {
|
|||
}
|
||||
|
||||
func makeConfigGeneralResult() *models.ConfigGeneralResult {
|
||||
logFile := config.GetLogFile()
|
||||
return &models.ConfigGeneralResult{
|
||||
Stashes: config.GetStashPaths(),
|
||||
DatabasePath: config.GetDatabasePath(),
|
||||
GeneratedPath: config.GetGeneratedPath(),
|
||||
Username: config.GetUsername(),
|
||||
Password: config.GetPasswordHash(),
|
||||
LogFile: &logFile,
|
||||
LogOut: config.GetLogOut(),
|
||||
LogLevel: config.GetLogLevel(),
|
||||
LogAccess: config.GetLogAccess(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,10 @@ func Start() {
|
|||
|
||||
r.Use(authenticateHandler())
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(middleware.Logger)
|
||||
|
||||
if config.GetLogAccess() {
|
||||
r.Use(middleware.Logger)
|
||||
}
|
||||
r.Use(middleware.DefaultCompress)
|
||||
r.Use(middleware.StripSlashes)
|
||||
r.Use(cors.AllowAll().Handler)
|
||||
|
|
|
@ -2,6 +2,8 @@ package logger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -24,6 +26,50 @@ var waiting = false
|
|||
var lastBroadcast = time.Now()
|
||||
var logBuffer []LogItem
|
||||
|
||||
// Init initialises the logger based on a logging configuration
|
||||
func Init(logFile string, logOut bool, logLevel string) {
|
||||
var file *os.File
|
||||
|
||||
if logFile != "" {
|
||||
var err error
|
||||
file, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Could not open '%s' for log output due to error: %s\n", logFile, err.Error())
|
||||
logFile = ""
|
||||
}
|
||||
}
|
||||
|
||||
if file != nil && logOut {
|
||||
mw := io.MultiWriter(os.Stderr, file)
|
||||
logger.Out = mw
|
||||
} else if file != nil {
|
||||
logger.Out = file
|
||||
}
|
||||
|
||||
// otherwise, output to StdErr
|
||||
|
||||
SetLogLevel(logLevel)
|
||||
}
|
||||
|
||||
func SetLogLevel(level string) {
|
||||
logger.Level = logLevelFromString(level)
|
||||
}
|
||||
|
||||
func logLevelFromString(level string) logrus.Level {
|
||||
ret := logrus.InfoLevel
|
||||
|
||||
if level == "Debug" {
|
||||
ret = logrus.DebugLevel
|
||||
} else if level == "Warning" {
|
||||
ret = logrus.WarnLevel
|
||||
} else if level == "Error" {
|
||||
ret = logrus.ErrorLevel
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func addLogItem(l *LogItem) {
|
||||
mutex.Lock()
|
||||
l.Time = time.Now()
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
|
@ -24,6 +25,12 @@ const Port = "port"
|
|||
|
||||
const CSSEnabled = "cssEnabled"
|
||||
|
||||
// Logging options
|
||||
const LogFile = "logFile"
|
||||
const LogOut = "logOut"
|
||||
const LogLevel = "logLevel"
|
||||
const LogAccess = "logAccess"
|
||||
|
||||
func Set(key string, value interface{}) {
|
||||
viper.Set(key, value)
|
||||
}
|
||||
|
@ -155,6 +162,48 @@ func GetCSSEnabled() bool {
|
|||
return viper.GetBool(CSSEnabled)
|
||||
}
|
||||
|
||||
// GetLogFile returns the filename of the file to output logs to.
|
||||
// An empty string means that file logging will be disabled.
|
||||
func GetLogFile() string {
|
||||
return viper.GetString(LogFile)
|
||||
}
|
||||
|
||||
// GetLogOut returns true if logging should be output to the terminal
|
||||
// in addition to writing to a log file. Logging will be output to the
|
||||
// terminal if file logging is disabled. Defaults to true.
|
||||
func GetLogOut() bool {
|
||||
ret := true
|
||||
if viper.IsSet(LogOut) {
|
||||
ret = viper.GetBool(LogOut)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetLogLevel returns the lowest log level to write to the log.
|
||||
// Should be one of "Debug", "Info", "Warning", "Error"
|
||||
func GetLogLevel() string {
|
||||
const defaultValue = "Info"
|
||||
|
||||
value := viper.GetString(LogLevel)
|
||||
if value != "Debug" && value != "Info" && value != "Warning" && value != "Error" {
|
||||
value = defaultValue
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// GetLogAccess returns true if http requests should be logged to the terminal.
|
||||
// HTTP requests are not logged to the log file. Defaults to true.
|
||||
func GetLogAccess() bool {
|
||||
ret := true
|
||||
if viper.IsSet(LogAccess) {
|
||||
ret = viper.GetBool(LogAccess)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func IsValid() bool {
|
||||
setPaths := viper.IsSet(Stash) && viper.IsSet(Cache) && viper.IsSet(Generated) && viper.IsSet(Metadata)
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ func Initialize() *singleton {
|
|||
once.Do(func() {
|
||||
_ = utils.EnsureDir(paths.GetConfigDirectory())
|
||||
initConfig()
|
||||
initLog()
|
||||
initFlags()
|
||||
initEnvs()
|
||||
instance = &singleton{
|
||||
|
@ -126,6 +127,10 @@ The error was: %s
|
|||
instance.FFProbePath = ffprobePath
|
||||
}
|
||||
|
||||
func initLog() {
|
||||
logger.Init(config.GetLogFile(), config.GetLogOut(), config.GetLogLevel())
|
||||
}
|
||||
|
||||
func (s *singleton) refreshConfig() {
|
||||
s.Paths = paths.NewPaths()
|
||||
if config.IsValid() {
|
||||
|
|
|
@ -8,12 +8,13 @@ import {
|
|||
InputGroup,
|
||||
Spinner,
|
||||
Tag,
|
||||
Checkbox,
|
||||
HTMLSelect,
|
||||
} from "@blueprintjs/core";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import * as GQL from "../../core/generated-graphql";
|
||||
import { StashService } from "../../core/StashService";
|
||||
import { ErrorUtils } from "../../utils/errors";
|
||||
import { TextUtils } from "../../utils/text";
|
||||
import { ToastUtils } from "../../utils/toasts";
|
||||
import { FolderSelect } from "../Shared/FolderSelect/FolderSelect";
|
||||
|
||||
|
@ -26,6 +27,10 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||
const [generatedPath, setGeneratedPath] = useState<string | undefined>(undefined);
|
||||
const [username, setUsername] = useState<string | undefined>(undefined);
|
||||
const [password, setPassword] = useState<string | undefined>(undefined);
|
||||
const [logFile, setLogFile] = useState<string | undefined>();
|
||||
const [logOut, setLogOut] = useState<boolean>(true);
|
||||
const [logLevel, setLogLevel] = useState<string>("Info");
|
||||
const [logAccess, setLogAccess] = useState<boolean>(true);
|
||||
|
||||
const { data, error, loading } = StashService.useConfiguration();
|
||||
|
||||
|
@ -35,6 +40,10 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||
generatedPath,
|
||||
username,
|
||||
password,
|
||||
logFile,
|
||||
logOut,
|
||||
logLevel,
|
||||
logAccess,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -46,6 +55,10 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||
setGeneratedPath(conf.general.generatedPath);
|
||||
setUsername(conf.general.username);
|
||||
setPassword(conf.general.password);
|
||||
setLogFile(conf.general.logFile);
|
||||
setLogOut(conf.general.logOut);
|
||||
setLogLevel(conf.general.logLevel);
|
||||
setLogAccess(conf.general.logAccess);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
|
@ -89,6 +102,9 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||
>
|
||||
<InputGroup value={generatedPath} onChange={(e: any) => setGeneratedPath(e.target.value)} />
|
||||
</FormGroup>
|
||||
|
||||
<Divider />
|
||||
<H4>Authentication</H4>
|
||||
<FormGroup
|
||||
label="Username"
|
||||
helperText="Username to access Stash. Leave blank to disable user authentication"
|
||||
|
@ -101,6 +117,44 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||
>
|
||||
<InputGroup type="password" value={password} onChange={(e: any) => setPassword(e.target.value)} />
|
||||
</FormGroup>
|
||||
|
||||
<Divider />
|
||||
<H4>Logging</H4>
|
||||
<FormGroup
|
||||
label="Log file"
|
||||
helperText="Path to the file to output logging to. Blank to disable file logging. Requires restart."
|
||||
>
|
||||
<InputGroup value={logFile} onChange={(e: any) => setLogFile(e.target.value)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
helperText="Logs to the terminal in addition to a file. Always true if file logging is disabled. Requires restart."
|
||||
>
|
||||
<Checkbox
|
||||
checked={logOut}
|
||||
label="Log to terminal"
|
||||
onChange={() => setLogOut(!logOut)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup inline={true} label="Log Level">
|
||||
<HTMLSelect
|
||||
options={["Debug", "Info", "Warning", "Error"]}
|
||||
onChange={(event) => setLogLevel(event.target.value)}
|
||||
value={logLevel}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
helperText="Logs http access to the terminal. Requires restart."
|
||||
>
|
||||
<Checkbox
|
||||
checked={logAccess}
|
||||
label="Log http access"
|
||||
onChange={() => setLogAccess(!logAccess)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Divider />
|
||||
<Button intent="primary" onClick={() => onSave()}>Save</Button>
|
||||
</>
|
||||
|
|
Loading…
Reference in New Issue