213 lines
5.5 KiB
Go

package main
import (
"fmt"
"io"
"os"
"os/user"
"path"
"path/filepath"
"runtime"
"github.com/kkyr/fig"
log "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
)
// Configurations relating to HTTP server.
type HTTPConfig struct {
BindAddr string `fig:"bind_addr"`
Port uint `fig:"port"`
Debug bool `fig:"debug"`
APIKey string `fig:"api_key"`
Enabled bool `fig:"enabled"`
}
// Configuration for logging.
type LogConfig struct {
// Limit the log output by the log level.
Level string `fig:"level" yaml:"level" enum:"debug,info,warn,error" default:"info"`
// How should the log output be formatted.
Type string `fig:"type" yaml:"type" enum:"json,console" default:"console"`
// The outputs that the log should go to. Output of `console` will
// go to the stderr. An file path, will log to the file. Using `default-file`
// it'll either save to `/var/log/name.log`, or to the same directory as the
// executable if the path is not writable, or on Windows.
Outputs []string `fig:"outputs" yaml:"outputs" default:"[console,default-file]"`
// Maximum size of the log file in megabytes before it gets rotated.
MaxSize int `fig:"max_size" yaml:"max_size" default:"1"`
// Maximum number of backups to save.
MaxBackups int `fig:"max_backups" yaml:"max_backups" default:"3"`
// Maximum number of days to retain old log files.
MaxAge int `fig:"max_age" yaml:"max_age" default:"0"`
// Use the logal system time instead of UTC for file names of rotated backups.
LocalTime *bool `fig:"local_time" yaml:"local_time" default:"true"`
// Should the rotated logs be compressed.
Compress *bool `fig:"compress" yaml:"compress" default:"true"`
}
// Apply log config.
func (l *LogConfig) Apply() {
// Apply level.
switch l.Level {
case "debug":
log.SetLevel(log.DebugLevel)
case "info":
log.SetLevel(log.InfoLevel)
case "warn":
log.SetLevel(log.WarnLevel)
default:
log.SetLevel(log.ErrorLevel)
}
// Apply type.
switch l.Type {
case "json":
log.SetFormatter(&log.JSONFormatter{})
default:
log.SetFormatter(&log.TextFormatter{})
}
// Change the outputs.
var outputs []io.Writer
for _, output := range l.Outputs {
// If output is console, add stderr and continue.
if output == "console" {
outputs = append(outputs, os.Stderr)
continue
}
// If default-file defined, find the default file.
if output == "default-file" {
var f *os.File
var err error
var logDir, logPath string
logName := fmt.Sprintf("%s.log", serviceName)
// On *nix, `/var/log/` should be default if writable.
if runtime.GOOS != "windows" {
logDir = "/var/log"
logPath = filepath.Join(logDir, logName)
// Verify we can write to log file.
f, err = os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
}
// If we could not open the file, then we should try the executable path.
if err != nil || f == nil {
exe, err := os.Executable()
if err != nil {
log.Println("Unable to find an writable log path to save log to.")
continue
} else {
logDir = filepath.Dir(exe)
logPath = filepath.Join(logDir, logName)
// Verify we can write to log file.
f, err = os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Println("Unable to find an writable log path to save log to.")
continue
} else {
f.Close()
}
}
} else {
// Close file.
f.Close()
}
// Update the config log path.
output = logPath
}
// Setup lumberjack log rotate for the output, and add to the list.
logFile := &lumberjack.Logger{
Filename: output,
MaxSize: l.MaxSize,
MaxBackups: l.MaxBackups,
MaxAge: l.MaxAge,
LocalTime: *l.LocalTime,
Compress: *l.Compress,
}
outputs = append(outputs, logFile)
}
// If there are outputs, set the outputs.
if len(outputs) != 0 {
mw := io.MultiWriter(outputs...)
log.SetOutput(mw)
}
}
// Configuration Structure.
type Config struct {
HTTP HTTPConfig `fig:"http"`
Log *LogConfig `fig:"log" yaml:"log"`
MidiRouters []*MidiRouter `fig:"midi_routers"`
}
// Load the configuration.
func (a *App) ReadConfig() {
usr, err := user.Current()
if err != nil {
log.Fatal(err)
}
// Configuration paths.
localConfig, _ := filepath.Abs("./config.yaml")
homeDirConfig := usr.HomeDir + "/.config/midi-request-trigger/config.yaml"
etcConfig := "/etc/midi-request-trigger/config.yaml"
// Determine which configuration to use.
var configFile string
if _, err := os.Stat(app.flags.ConfigPath); err == nil && app.flags.ConfigPath != "" {
configFile = app.flags.ConfigPath
} else if _, err := os.Stat(localConfig); err == nil {
configFile = localConfig
} else if _, err := os.Stat(homeDirConfig); err == nil {
configFile = homeDirConfig
} else if _, err := os.Stat(etcConfig); err == nil {
configFile = etcConfig
} else {
log.Println("Unable to find a configuration file.")
}
// Load the configuration file.
config := &Config{
HTTP: HTTPConfig{
BindAddr: "",
Port: 34936,
Debug: true,
Enabled: false,
},
Log: &LogConfig{},
}
// Load configuration.
filePath, fileName := path.Split(configFile)
err = fig.Load(config,
fig.File(fileName),
fig.Dirs(filePath),
)
if err != nil {
app.config = config
log.Printf("Error parsing configuration: %s\n", err)
return
}
// Flag Overrides.
if app.flags.HTTPBind != "" {
config.HTTP.BindAddr = app.flags.HTTPBind
}
if app.flags.HTTPPort != 0 {
config.HTTP.Port = app.flags.HTTPPort
}
// Apply log configs.
config.Log.Apply()
// Set global config structure.
app.config = config
}