The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/setting/setting.go

515 lines
14 KiB

11 years ago
// Copyright 2014 Unknwon
// Copyright 2014 Torkel Ödegaard
package setting
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
11 years ago
"github.com/macaron-contrib/session"
"gopkg.in/ini.v1"
11 years ago
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/util"
)
type Scheme string
const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
)
const (
DEV string = "development"
PROD string = "production"
TEST string = "test"
)
var (
// App settings.
Env string = DEV
AppName string
AppUrl string
AppSubUrl string
// build
BuildVersion string
BuildCommit string
BuildStamp int64
// Paths
LogsPath string
HomePath string
DataPath string
// Log settings.
LogModes []string
LogConfigs []util.DynMap
// Http server options
Protocol Scheme
Domain string
HttpAddr, HttpPort string
SshPort int
CertFile, KeyFile string
11 years ago
RouterLogging bool
StaticRootPath string
EnableGzip bool
11 years ago
// Security settings.
SecretKey string
LogInRememberDays int
CookieUserName string
CookieRememberName string
// User settings
AllowUserSignUp bool
AllowUserOrgCreate bool
AutoAssignOrg bool
AutoAssignOrgRole string
// Http auth
AdminUser string
AdminPassword string
AnonymousEnabled bool
AnonymousOrgName string
AnonymousOrgRole string
11 years ago
// Session settings.
SessionOptions session.Options
// Global setting objects.
Cfg *ini.File
ConfRootPath string
IsWindows bool
// PhantomJs Rendering
ImagesDir string
PhantomDir string
// for logging purposes
configFiles []string
appliedCommandLineProperties []string
appliedEnvOverrides []string
ReportingEnabled bool
GoogleAnalyticsId string
)
type CommandLineArgs struct {
Config string
HomePath string
Args []string
}
func init() {
IsWindows = runtime.GOOS == "windows"
log.NewLogger(0, "console", `{"level": 0}`)
}
func parseAppUrlAndSubUrl(section *ini.Section) (string, string) {
appUrl := section.Key("root_url").MustString("http://localhost:3000/")
if appUrl[len(appUrl)-1] != '/' {
appUrl += "/"
}
// Check if has app suburl.
url, err := url.Parse(appUrl)
if err != nil {
log.Fatal(4, "Invalid root_url(%s): %s", appUrl, err)
}
appSubUrl := strings.TrimSuffix(url.Path, "/")
return appUrl, appSubUrl
}
func ToAbsUrl(relativeUrl string) string {
return AppUrl + relativeUrl
}
func applyEnvVariableOverrides() {
appliedEnvOverrides = make([]string, 0)
for _, section := range Cfg.Sections() {
for _, key := range section.Keys() {
sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
envValue := os.Getenv(envKey)
if len(envValue) > 0 {
key.SetValue(envValue)
appliedEnvOverrides = append(appliedEnvOverrides, fmt.Sprintf("%s=%s", envKey, envValue))
}
}
}
}
func applyCommandLineDefaultProperties(props map[string]string) {
appliedCommandLineProperties = make([]string, 0)
for _, section := range Cfg.Sections() {
for _, key := range section.Keys() {
keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name())
value, exists := props[keyString]
if exists {
key.SetValue(value)
appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
}
}
}
}
func applyCommandLineProperties(props map[string]string) {
for _, section := range Cfg.Sections() {
for _, key := range section.Keys() {
keyString := fmt.Sprintf("%s.%s", section.Name(), key.Name())
value, exists := props[keyString]
if exists {
key.SetValue(value)
appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
}
}
}
}
func getCommandLineProperties(args []string) map[string]string {
props := make(map[string]string)
for _, arg := range args {
if !strings.HasPrefix(arg, "cfg:") {
continue
}
trimmed := strings.TrimPrefix(arg, "cfg:")
parts := strings.Split(trimmed, "=")
if len(parts) != 2 {
log.Fatal(3, "Invalid command line argument", arg)
return nil
}
props[parts[0]] = parts[1]
}
return props
}
func makeAbsolute(path string, root string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(root, path)
}
func evalEnvVarExpression(value string) string {
regex := regexp.MustCompile(`\${(\w+)}`)
return regex.ReplaceAllStringFunc(value, func(envVar string) string {
envVar = strings.TrimPrefix(envVar, "${")
envVar = strings.TrimSuffix(envVar, "}")
envValue := os.Getenv(envVar)
return envValue
})
}
func evalConfigValues() {
for _, section := range Cfg.Sections() {
for _, key := range section.Keys() {
key.SetValue(evalEnvVarExpression(key.Value()))
}
}
}
func loadSpecifedConfigFile(configFile string) {
if configFile == "" {
configFile = filepath.Join(HomePath, "conf/custom.ini")
// return without error if custom file does not exist
if !pathExists(configFile) {
return
}
}
userConfig, err := ini.Load(configFile)
userConfig.BlockMode = false
if err != nil {
log.Fatal(3, "Failed to parse %v, %v", configFile, err)
}
for _, section := range userConfig.Sections() {
for _, key := range section.Keys() {
if key.Value() == "" {
continue
}
defaultSec, err := Cfg.GetSection(section.Name())
if err != nil {
log.Fatal(3, "Unknown config section %s defined in %s", section.Name(), configFile)
}
defaultKey, err := defaultSec.GetKey(key.Name())
if err != nil {
log.Fatal(3, "Unknown config key %s defined in section %s, in file", key.Name(), section.Name(), configFile)
}
defaultKey.SetValue(key.Value())
}
}
configFiles = append(configFiles, configFile)
}
func loadConfiguration(args *CommandLineArgs) {
var err error
// load config defaults
defaultConfigFile := path.Join(HomePath, "conf/defaults.ini")
configFiles = append(configFiles, defaultConfigFile)
Cfg, err = ini.Load(defaultConfigFile)
Cfg.BlockMode = false
if err != nil {
log.Fatal(3, "Failed to parse defaults.ini, %v", err)
}
// command line props
commandLineProps := getCommandLineProperties(args.Args)
// load default overrides
applyCommandLineDefaultProperties(commandLineProps)
// load specified config file
loadSpecifedConfigFile(args.Config)
// apply environment overrides
applyEnvVariableOverrides()
// apply command line overrides
applyCommandLineProperties(commandLineProps)
// evaluate config values containing environment variables
evalConfigValues()
}
func pathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
func setHomePath(args *CommandLineArgs) {
if args.HomePath != "" {
HomePath = args.HomePath
return
}
HomePath, _ = filepath.Abs(".")
// check if homepath is correct
if pathExists(filepath.Join(HomePath, "conf/defaults.ini")) {
return
}
// try down one path
if pathExists(filepath.Join(HomePath, "../conf/defaults.ini")) {
HomePath = filepath.Join(HomePath, "../")
}
}
func NewConfigContext(args *CommandLineArgs) {
setHomePath(args)
loadConfiguration(args)
DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
initLogging(args)
AppName = Cfg.Section("").Key("app_name").MustString("Grafana")
Env = Cfg.Section("").Key("app_mode").MustString("development")
server := Cfg.Section("server")
AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
Protocol = HTTP
if server.Key("protocol").MustString("http") == "https" {
Protocol = HTTPS
CertFile = server.Key("cert_file").String()
KeyFile = server.Key("cert_key").String()
}
Domain = server.Key("domain").MustString("localhost")
HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
HttpPort = server.Key("http_port").MustString("3000")
11 years ago
StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath)
RouterLogging = server.Key("router_logging").MustBool(false)
EnableGzip = server.Key("enable_gzip").MustBool(false)
security := Cfg.Section("security")
SecretKey = security.Key("secret_key").String()
LogInRememberDays = security.Key("login_remember_days").MustInt()
CookieUserName = security.Key("cookie_username").String()
CookieRememberName = security.Key("cookie_remember_name").String()
// admin
AdminUser = security.Key("admin_user").String()
AdminPassword = security.Key("admin_password").String()
users := Cfg.Section("users")
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
// anonymous access
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
// PhantomJS rendering
ImagesDir = filepath.Join(DataPath, "png")
PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
analytics := Cfg.Section("analytics")
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
readSessionConfig()
}
func readSessionConfig() {
sec := Cfg.Section("session")
SessionOptions = session.Options{}
SessionOptions.Provider = sec.Key("provider").In("memory", []string{"memory", "file", "redis", "mysql", "postgres"})
SessionOptions.ProviderConfig = strings.Trim(sec.Key("provider_config").String(), "\" ")
SessionOptions.CookieName = sec.Key("cookie_name").MustString("grafana_sess")
SessionOptions.CookiePath = AppSubUrl
SessionOptions.Secure = sec.Key("cookie_secure").MustBool()
SessionOptions.Gclifetime = Cfg.Section("session").Key("gc_interval_time").MustInt64(86400)
SessionOptions.Maxlifetime = Cfg.Section("session").Key("session_life_time").MustInt64(86400)
SessionOptions.IDLength = 16
if SessionOptions.Provider == "file" {
SessionOptions.ProviderConfig = makeAbsolute(SessionOptions.ProviderConfig, DataPath)
os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
11 years ago
}
if SessionOptions.CookiePath == "" {
SessionOptions.CookiePath = "/"
}
11 years ago
}
var logLevels = map[string]int{
"Trace": 0,
"Debug": 1,
"Info": 2,
"Warn": 3,
"Error": 4,
"Critical": 5,
}
func initLogging(args *CommandLineArgs) {
// Get and check log mode.
LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
LogConfigs = make([]util.DynMap, len(LogModes))
for i, mode := range LogModes {
mode = strings.TrimSpace(mode)
sec, err := Cfg.GetSection("log." + mode)
if err != nil {
log.Fatal(4, "Unknown log mode: %s", mode)
}
// Log level.
levelName := Cfg.Section("log."+mode).Key("level").In("Trace",
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
level, ok := logLevels[levelName]
if !ok {
log.Fatal(4, "Unknown log level: %s", levelName)
}
// Generate log configuration.
switch mode {
case "console":
LogConfigs[i] = util.DynMap{"level": level}
case "file":
logPath := sec.Key("file_name").MustString(filepath.Join(LogsPath, "grafana.log"))
os.MkdirAll(filepath.Dir(logPath), os.ModePerm)
LogConfigs[i] = util.DynMap{
"level": level,
"filename": logPath,
"rotate": sec.Key("log_rotate").MustBool(true),
"maxlines": sec.Key("max_lines").MustInt(1000000),
"maxsize": 1 << uint(sec.Key("max_size_shift").MustInt(28)),
"daily": sec.Key("daily_rotate").MustBool(true),
"maxdays": sec.Key("max_days").MustInt(7),
}
case "conn":
LogConfigs[i] = util.DynMap{
"level": level,
"reconnectOnMsg": sec.Key("reconnect_on_msg").MustBool(),
"reconnect": sec.Key("reconnect").MustBool(),
"net": sec.Key("protocol").In("tcp", []string{"tcp", "unix", "udp"}),
"addr": sec.Key("addr").MustString(":7020"),
}
case "smtp":
LogConfigs[i] = util.DynMap{
"level": level,
"user": sec.Key("user").MustString("example@example.com"),
"passwd": sec.Key("passwd").MustString("******"),
"host": sec.Key("host").MustString("127.0.0.1:25"),
"receivers": sec.Key("receivers").MustString("[]"),
"subject": sec.Key("subject").MustString("Diagnostic message from serve"),
}
case "database":
LogConfigs[i] = util.DynMap{
"level": level,
"driver": sec.Key("driver").String(),
"conn": sec.Key("conn").String(),
}
}
cfgJsonBytes, _ := json.Marshal(LogConfigs[i])
log.NewLogger(Cfg.Section("log").Key("buffer_len").MustInt64(10000), mode, string(cfgJsonBytes))
}
}
func LogConfigurationInfo() {
var text bytes.Buffer
text.WriteString("Configuration Info\n")
text.WriteString("Config files:\n")
for i, file := range configFiles {
text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, file))
}
if len(appliedCommandLineProperties) > 0 {
text.WriteString("Command lines overrides:\n")
for i, prop := range appliedCommandLineProperties {
text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
}
}
if len(appliedEnvOverrides) > 0 {
text.WriteString("\tEnvironment variables used:\n")
for i, prop := range appliedCommandLineProperties {
text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
}
}
text.WriteString("Paths:\n")
text.WriteString(fmt.Sprintf(" home: %s\n", HomePath))
text.WriteString(fmt.Sprintf(" data: %s\n", DataPath))
text.WriteString(fmt.Sprintf(" logs: %s\n", LogsPath))
log.Info(text.String())
}