Add logging options (#154)

* Add various log options

* Remove logFormat. Add UI for log config

* Fix UI boolean flags
This commit is contained in:
WithoutPants 2019-10-25 11:13:44 +11:00 committed by Leopere
parent f29509577a
commit 564786f968
9 changed files with 197 additions and 2 deletions

View File

@ -4,6 +4,10 @@ fragment ConfigGeneralData on ConfigGeneralResult {
generatedPath
username
password
logFile
logOut
logLevel
logAccess
}
fragment ConfigInterfaceData on ConfigInterfaceResult {

View File

@ -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 {

View File

@ -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
}

View File

@ -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(),
}
}

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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() {

View File

@ -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>
</>