mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Add logging options (#154)
* Add various log options * Remove logFormat. Add UI for log config * Fix UI boolean flags
This commit is contained in:
@@ -4,6 +4,10 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
|||||||
generatedPath
|
generatedPath
|
||||||
username
|
username
|
||||||
password
|
password
|
||||||
|
logFile
|
||||||
|
logOut
|
||||||
|
logLevel
|
||||||
|
logAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ input ConfigGeneralInput {
|
|||||||
username: String
|
username: String
|
||||||
"""Password"""
|
"""Password"""
|
||||||
password: String
|
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 {
|
type ConfigGeneralResult {
|
||||||
@@ -22,6 +30,14 @@ type ConfigGeneralResult {
|
|||||||
username: String!
|
username: String!
|
||||||
"""Password"""
|
"""Password"""
|
||||||
password: String!
|
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 {
|
input ConfigInterfaceInput {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/manager/config"
|
"github.com/stashapp/stash/pkg/manager/config"
|
||||||
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/utils"
|
"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 {
|
if err := config.Write(); err != nil {
|
||||||
return makeConfigGeneralResult(), err
|
return makeConfigGeneralResult(), err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,17 @@ func makeConfigResult() *models.ConfigResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeConfigGeneralResult() *models.ConfigGeneralResult {
|
func makeConfigGeneralResult() *models.ConfigGeneralResult {
|
||||||
|
logFile := config.GetLogFile()
|
||||||
return &models.ConfigGeneralResult{
|
return &models.ConfigGeneralResult{
|
||||||
Stashes: config.GetStashPaths(),
|
Stashes: config.GetStashPaths(),
|
||||||
DatabasePath: config.GetDatabasePath(),
|
DatabasePath: config.GetDatabasePath(),
|
||||||
GeneratedPath: config.GetGeneratedPath(),
|
GeneratedPath: config.GetGeneratedPath(),
|
||||||
Username: config.GetUsername(),
|
Username: config.GetUsername(),
|
||||||
Password: config.GetPasswordHash(),
|
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(authenticateHandler())
|
||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
r.Use(middleware.Logger)
|
|
||||||
|
if config.GetLogAccess() {
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
}
|
||||||
r.Use(middleware.DefaultCompress)
|
r.Use(middleware.DefaultCompress)
|
||||||
r.Use(middleware.StripSlashes)
|
r.Use(middleware.StripSlashes)
|
||||||
r.Use(cors.AllowAll().Handler)
|
r.Use(cors.AllowAll().Handler)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package logger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -24,6 +26,50 @@ var waiting = false
|
|||||||
var lastBroadcast = time.Now()
|
var lastBroadcast = time.Now()
|
||||||
var logBuffer []LogItem
|
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) {
|
func addLogItem(l *LogItem) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
l.Time = time.Now()
|
l.Time = time.Now()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/utils"
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
@@ -24,6 +25,12 @@ const Port = "port"
|
|||||||
|
|
||||||
const CSSEnabled = "cssEnabled"
|
const CSSEnabled = "cssEnabled"
|
||||||
|
|
||||||
|
// Logging options
|
||||||
|
const LogFile = "logFile"
|
||||||
|
const LogOut = "logOut"
|
||||||
|
const LogLevel = "logLevel"
|
||||||
|
const LogAccess = "logAccess"
|
||||||
|
|
||||||
func Set(key string, value interface{}) {
|
func Set(key string, value interface{}) {
|
||||||
viper.Set(key, value)
|
viper.Set(key, value)
|
||||||
}
|
}
|
||||||
@@ -155,6 +162,48 @@ func GetCSSEnabled() bool {
|
|||||||
return viper.GetBool(CSSEnabled)
|
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 {
|
func IsValid() bool {
|
||||||
setPaths := viper.IsSet(Stash) && viper.IsSet(Cache) && viper.IsSet(Generated) && viper.IsSet(Metadata)
|
setPaths := viper.IsSet(Stash) && viper.IsSet(Cache) && viper.IsSet(Generated) && viper.IsSet(Metadata)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ func Initialize() *singleton {
|
|||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
_ = utils.EnsureDir(paths.GetConfigDirectory())
|
_ = utils.EnsureDir(paths.GetConfigDirectory())
|
||||||
initConfig()
|
initConfig()
|
||||||
|
initLog()
|
||||||
initFlags()
|
initFlags()
|
||||||
initEnvs()
|
initEnvs()
|
||||||
instance = &singleton{
|
instance = &singleton{
|
||||||
@@ -126,6 +127,10 @@ The error was: %s
|
|||||||
instance.FFProbePath = ffprobePath
|
instance.FFProbePath = ffprobePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initLog() {
|
||||||
|
logger.Init(config.GetLogFile(), config.GetLogOut(), config.GetLogLevel())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *singleton) refreshConfig() {
|
func (s *singleton) refreshConfig() {
|
||||||
s.Paths = paths.NewPaths()
|
s.Paths = paths.NewPaths()
|
||||||
if config.IsValid() {
|
if config.IsValid() {
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import {
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
Spinner,
|
Spinner,
|
||||||
Tag,
|
Tag,
|
||||||
|
Checkbox,
|
||||||
|
HTMLSelect,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import * as GQL from "../../core/generated-graphql";
|
import * as GQL from "../../core/generated-graphql";
|
||||||
import { StashService } from "../../core/StashService";
|
import { StashService } from "../../core/StashService";
|
||||||
import { ErrorUtils } from "../../utils/errors";
|
import { ErrorUtils } from "../../utils/errors";
|
||||||
import { TextUtils } from "../../utils/text";
|
|
||||||
import { ToastUtils } from "../../utils/toasts";
|
import { ToastUtils } from "../../utils/toasts";
|
||||||
import { FolderSelect } from "../Shared/FolderSelect/FolderSelect";
|
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 [generatedPath, setGeneratedPath] = useState<string | undefined>(undefined);
|
||||||
const [username, setUsername] = useState<string | undefined>(undefined);
|
const [username, setUsername] = useState<string | undefined>(undefined);
|
||||||
const [password, setPassword] = 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();
|
const { data, error, loading } = StashService.useConfiguration();
|
||||||
|
|
||||||
@@ -35,6 +40,10 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||||||
generatedPath,
|
generatedPath,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
logFile,
|
||||||
|
logOut,
|
||||||
|
logLevel,
|
||||||
|
logAccess,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -46,6 +55,10 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||||||
setGeneratedPath(conf.general.generatedPath);
|
setGeneratedPath(conf.general.generatedPath);
|
||||||
setUsername(conf.general.username);
|
setUsername(conf.general.username);
|
||||||
setPassword(conf.general.password);
|
setPassword(conf.general.password);
|
||||||
|
setLogFile(conf.general.logFile);
|
||||||
|
setLogOut(conf.general.logOut);
|
||||||
|
setLogLevel(conf.general.logLevel);
|
||||||
|
setLogAccess(conf.general.logAccess);
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
@@ -89,6 +102,9 @@ export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IPr
|
|||||||
>
|
>
|
||||||
<InputGroup value={generatedPath} onChange={(e: any) => setGeneratedPath(e.target.value)} />
|
<InputGroup value={generatedPath} onChange={(e: any) => setGeneratedPath(e.target.value)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<H4>Authentication</H4>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label="Username"
|
label="Username"
|
||||||
helperText="Username to access Stash. Leave blank to disable user authentication"
|
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)} />
|
<InputGroup type="password" value={password} onChange={(e: any) => setPassword(e.target.value)} />
|
||||||
</FormGroup>
|
</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 />
|
<Divider />
|
||||||
<Button intent="primary" onClick={() => onSave()}>Save</Button>
|
<Button intent="primary" onClick={() => onSave()}>Save</Button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user