setting.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. // Copyright 2014 Unknwon
  2. // Copyright 2014 Torkel Ödegaard
  3. package setting
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "fmt"
  8. "net/url"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "regexp"
  13. "runtime"
  14. "strings"
  15. "github.com/go-macaron/session"
  16. "gopkg.in/ini.v1"
  17. "github.com/grafana/grafana/pkg/log"
  18. "github.com/grafana/grafana/pkg/util"
  19. )
  20. type Scheme string
  21. const (
  22. HTTP Scheme = "http"
  23. HTTPS Scheme = "https"
  24. )
  25. const (
  26. DEV string = "development"
  27. PROD string = "production"
  28. TEST string = "test"
  29. )
  30. var (
  31. // App settings.
  32. Env string = DEV
  33. AppUrl string
  34. AppSubUrl string
  35. InstanceName string
  36. // build
  37. BuildVersion string
  38. BuildCommit string
  39. BuildStamp int64
  40. // Paths
  41. LogsPath string
  42. HomePath string
  43. DataPath string
  44. PluginsPath string
  45. // Log settings.
  46. LogModes []string
  47. LogConfigs []util.DynMap
  48. // Http server options
  49. Protocol Scheme
  50. Domain string
  51. HttpAddr, HttpPort string
  52. SshPort int
  53. CertFile, KeyFile string
  54. RouterLogging bool
  55. StaticRootPath string
  56. EnableGzip bool
  57. EnforceDomain bool
  58. // Security settings.
  59. SecretKey string
  60. LogInRememberDays int
  61. CookieUserName string
  62. CookieRememberName string
  63. DisableGravatar bool
  64. EmailCodeValidMinutes int
  65. DataProxyWhiteList map[string]bool
  66. // Snapshots
  67. ExternalSnapshotUrl string
  68. ExternalSnapshotName string
  69. ExternalEnabled bool
  70. // User settings
  71. AllowUserSignUp bool
  72. AllowUserOrgCreate bool
  73. AutoAssignOrg bool
  74. AutoAssignOrgRole string
  75. VerifyEmailEnabled bool
  76. LoginHint string
  77. DefaultTheme string
  78. // Http auth
  79. AdminUser string
  80. AdminPassword string
  81. AnonymousEnabled bool
  82. AnonymousOrgName string
  83. AnonymousOrgRole string
  84. // Auth proxy settings
  85. AuthProxyEnabled bool
  86. AuthProxyHeaderName string
  87. AuthProxyHeaderProperty string
  88. AuthProxyAutoSignUp bool
  89. // Basic Auth
  90. BasicAuthEnabled bool
  91. // Session settings.
  92. SessionOptions session.Options
  93. // Global setting objects.
  94. Cfg *ini.File
  95. ConfRootPath string
  96. IsWindows bool
  97. // PhantomJs Rendering
  98. ImagesDir string
  99. PhantomDir string
  100. // for logging purposes
  101. configFiles []string
  102. appliedCommandLineProperties []string
  103. appliedEnvOverrides []string
  104. ReportingEnabled bool
  105. CheckForUpdates bool
  106. GoogleAnalyticsId string
  107. GoogleTagManagerId string
  108. // LDAP
  109. LdapEnabled bool
  110. LdapConfigFile string
  111. // SMTP email settings
  112. Smtp SmtpSettings
  113. // QUOTA
  114. Quota QuotaSettings
  115. // Alerting
  116. AlertingEnabled bool
  117. )
  118. type CommandLineArgs struct {
  119. Config string
  120. HomePath string
  121. Args []string
  122. }
  123. func init() {
  124. IsWindows = runtime.GOOS == "windows"
  125. log.NewLogger(0, "console", `{"level": 0, "formatting":true}`)
  126. }
  127. func parseAppUrlAndSubUrl(section *ini.Section) (string, string) {
  128. appUrl := section.Key("root_url").MustString("http://localhost:3000/")
  129. if appUrl[len(appUrl)-1] != '/' {
  130. appUrl += "/"
  131. }
  132. // Check if has app suburl.
  133. url, err := url.Parse(appUrl)
  134. if err != nil {
  135. log.Fatal(4, "Invalid root_url(%s): %s", appUrl, err)
  136. }
  137. appSubUrl := strings.TrimSuffix(url.Path, "/")
  138. return appUrl, appSubUrl
  139. }
  140. func ToAbsUrl(relativeUrl string) string {
  141. return AppUrl + relativeUrl
  142. }
  143. func shouldRedactKey(s string) bool {
  144. uppercased := strings.ToUpper(s)
  145. return strings.Contains(uppercased, "PASSWORD") || strings.Contains(uppercased, "SECRET")
  146. }
  147. func applyEnvVariableOverrides() {
  148. appliedEnvOverrides = make([]string, 0)
  149. for _, section := range Cfg.Sections() {
  150. for _, key := range section.Keys() {
  151. sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
  152. keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
  153. envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
  154. envValue := os.Getenv(envKey)
  155. if len(envValue) > 0 {
  156. key.SetValue(envValue)
  157. if shouldRedactKey(envKey) {
  158. envValue = "*********"
  159. }
  160. appliedEnvOverrides = append(appliedEnvOverrides, fmt.Sprintf("%s=%s", envKey, envValue))
  161. }
  162. }
  163. }
  164. }
  165. func applyCommandLineDefaultProperties(props map[string]string) {
  166. appliedCommandLineProperties = make([]string, 0)
  167. for _, section := range Cfg.Sections() {
  168. for _, key := range section.Keys() {
  169. keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name())
  170. value, exists := props[keyString]
  171. if exists {
  172. key.SetValue(value)
  173. if shouldRedactKey(keyString) {
  174. value = "*********"
  175. }
  176. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  177. }
  178. }
  179. }
  180. }
  181. func applyCommandLineProperties(props map[string]string) {
  182. for _, section := range Cfg.Sections() {
  183. for _, key := range section.Keys() {
  184. keyString := fmt.Sprintf("%s.%s", section.Name(), key.Name())
  185. value, exists := props[keyString]
  186. if exists {
  187. key.SetValue(value)
  188. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  189. }
  190. }
  191. }
  192. }
  193. func getCommandLineProperties(args []string) map[string]string {
  194. props := make(map[string]string)
  195. for _, arg := range args {
  196. if !strings.HasPrefix(arg, "cfg:") {
  197. continue
  198. }
  199. trimmed := strings.TrimPrefix(arg, "cfg:")
  200. parts := strings.Split(trimmed, "=")
  201. if len(parts) != 2 {
  202. log.Fatal(3, "Invalid command line argument", arg)
  203. return nil
  204. }
  205. props[parts[0]] = parts[1]
  206. }
  207. return props
  208. }
  209. func makeAbsolute(path string, root string) string {
  210. if filepath.IsAbs(path) {
  211. return path
  212. }
  213. return filepath.Join(root, path)
  214. }
  215. func evalEnvVarExpression(value string) string {
  216. regex := regexp.MustCompile(`\${(\w+)}`)
  217. return regex.ReplaceAllStringFunc(value, func(envVar string) string {
  218. envVar = strings.TrimPrefix(envVar, "${")
  219. envVar = strings.TrimSuffix(envVar, "}")
  220. envValue := os.Getenv(envVar)
  221. // if env variable is hostname and it is emtpy use os.Hostname as default
  222. if envVar == "HOSTNAME" && envValue == "" {
  223. envValue, _ = os.Hostname()
  224. }
  225. return envValue
  226. })
  227. }
  228. func evalConfigValues() {
  229. for _, section := range Cfg.Sections() {
  230. for _, key := range section.Keys() {
  231. key.SetValue(evalEnvVarExpression(key.Value()))
  232. }
  233. }
  234. }
  235. func loadSpecifedConfigFile(configFile string) {
  236. if configFile == "" {
  237. configFile = filepath.Join(HomePath, "conf/custom.ini")
  238. // return without error if custom file does not exist
  239. if !pathExists(configFile) {
  240. return
  241. }
  242. }
  243. userConfig, err := ini.Load(configFile)
  244. userConfig.BlockMode = false
  245. if err != nil {
  246. log.Fatal(3, "Failed to parse %v, %v", configFile, err)
  247. }
  248. for _, section := range userConfig.Sections() {
  249. for _, key := range section.Keys() {
  250. if key.Value() == "" {
  251. continue
  252. }
  253. defaultSec, err := Cfg.GetSection(section.Name())
  254. if err != nil {
  255. defaultSec, _ = Cfg.NewSection(section.Name())
  256. }
  257. defaultKey, err := defaultSec.GetKey(key.Name())
  258. if err != nil {
  259. defaultKey, _ = defaultSec.NewKey(key.Name(), key.Value())
  260. }
  261. defaultKey.SetValue(key.Value())
  262. }
  263. }
  264. configFiles = append(configFiles, configFile)
  265. }
  266. func loadConfiguration(args *CommandLineArgs) {
  267. var err error
  268. // load config defaults
  269. defaultConfigFile := path.Join(HomePath, "conf/defaults.ini")
  270. configFiles = append(configFiles, defaultConfigFile)
  271. Cfg, err = ini.Load(defaultConfigFile)
  272. Cfg.BlockMode = false
  273. if err != nil {
  274. log.Fatal(3, "Failed to parse defaults.ini, %v", err)
  275. }
  276. // command line props
  277. commandLineProps := getCommandLineProperties(args.Args)
  278. // load default overrides
  279. applyCommandLineDefaultProperties(commandLineProps)
  280. // init logging before specific config so we can log errors from here on
  281. DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
  282. initLogging(args)
  283. // load specified config file
  284. loadSpecifedConfigFile(args.Config)
  285. // apply environment overrides
  286. applyEnvVariableOverrides()
  287. // apply command line overrides
  288. applyCommandLineProperties(commandLineProps)
  289. // evaluate config values containing environment variables
  290. evalConfigValues()
  291. // update data path and logging config
  292. DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
  293. initLogging(args)
  294. }
  295. func pathExists(path string) bool {
  296. _, err := os.Stat(path)
  297. if err == nil {
  298. return true
  299. }
  300. if os.IsNotExist(err) {
  301. return false
  302. }
  303. return false
  304. }
  305. func setHomePath(args *CommandLineArgs) {
  306. if args.HomePath != "" {
  307. HomePath = args.HomePath
  308. return
  309. }
  310. HomePath, _ = filepath.Abs(".")
  311. // check if homepath is correct
  312. if pathExists(filepath.Join(HomePath, "conf/defaults.ini")) {
  313. return
  314. }
  315. // try down one path
  316. if pathExists(filepath.Join(HomePath, "../conf/defaults.ini")) {
  317. HomePath = filepath.Join(HomePath, "../")
  318. }
  319. }
  320. var skipStaticRootValidation bool = false
  321. func validateStaticRootPath() error {
  322. if skipStaticRootValidation {
  323. return nil
  324. }
  325. if _, err := os.Stat(path.Join(StaticRootPath, "css")); err == nil {
  326. return nil
  327. }
  328. if _, err := os.Stat(StaticRootPath + "_gen/css"); err == nil {
  329. StaticRootPath = StaticRootPath + "_gen"
  330. return nil
  331. }
  332. return fmt.Errorf("Failed to detect generated css or javascript files in static root (%s), have you executed default grunt task?", StaticRootPath)
  333. }
  334. // func readInstanceName() string {
  335. // hostname, _ := os.Hostname()
  336. // if hostname == "" {
  337. // hostname = "hostname_unknown"
  338. // }
  339. //
  340. // instanceName := Cfg.Section("").Key("instance_name").MustString("")
  341. // if instanceName = "" {
  342. // // set value as it might be used in other places
  343. // Cfg.Section("").Key("instance_name").SetValue(hostname)
  344. // instanceName = hostname
  345. // }
  346. //
  347. // return
  348. // }
  349. func NewConfigContext(args *CommandLineArgs) error {
  350. setHomePath(args)
  351. loadConfiguration(args)
  352. Env = Cfg.Section("").Key("app_mode").MustString("development")
  353. InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name")
  354. PluginsPath = Cfg.Section("paths").Key("plugins").String()
  355. server := Cfg.Section("server")
  356. AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
  357. Protocol = HTTP
  358. if server.Key("protocol").MustString("http") == "https" {
  359. Protocol = HTTPS
  360. CertFile = server.Key("cert_file").String()
  361. KeyFile = server.Key("cert_key").String()
  362. }
  363. Domain = server.Key("domain").MustString("localhost")
  364. HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
  365. HttpPort = server.Key("http_port").MustString("3000")
  366. RouterLogging = server.Key("router_logging").MustBool(false)
  367. EnableGzip = server.Key("enable_gzip").MustBool(false)
  368. EnforceDomain = server.Key("enforce_domain").MustBool(false)
  369. StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath)
  370. if err := validateStaticRootPath(); err != nil {
  371. return err
  372. }
  373. // read security settings
  374. security := Cfg.Section("security")
  375. SecretKey = security.Key("secret_key").String()
  376. LogInRememberDays = security.Key("login_remember_days").MustInt()
  377. CookieUserName = security.Key("cookie_username").String()
  378. CookieRememberName = security.Key("cookie_remember_name").String()
  379. DisableGravatar = security.Key("disable_gravatar").MustBool(true)
  380. // read snapshots settings
  381. snapshots := Cfg.Section("snapshots")
  382. ExternalSnapshotUrl = snapshots.Key("external_snapshot_url").String()
  383. ExternalSnapshotName = snapshots.Key("external_snapshot_name").String()
  384. ExternalEnabled = snapshots.Key("external_enabled").MustBool(true)
  385. // read data source proxy white list
  386. DataProxyWhiteList = make(map[string]bool)
  387. for _, hostAndIp := range security.Key("data_source_proxy_whitelist").Strings(" ") {
  388. DataProxyWhiteList[hostAndIp] = true
  389. }
  390. // admin
  391. AdminUser = security.Key("admin_user").String()
  392. AdminPassword = security.Key("admin_password").String()
  393. users := Cfg.Section("users")
  394. AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
  395. AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
  396. AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
  397. AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
  398. VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
  399. LoginHint = users.Key("login_hint").String()
  400. DefaultTheme = users.Key("default_theme").String()
  401. // anonymous access
  402. AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
  403. AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
  404. AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
  405. // auth proxy
  406. authProxy := Cfg.Section("auth.proxy")
  407. AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
  408. AuthProxyHeaderName = authProxy.Key("header_name").String()
  409. AuthProxyHeaderProperty = authProxy.Key("header_property").String()
  410. AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
  411. authBasic := Cfg.Section("auth.basic")
  412. BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
  413. // PhantomJS rendering
  414. ImagesDir = filepath.Join(DataPath, "png")
  415. PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
  416. analytics := Cfg.Section("analytics")
  417. ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
  418. CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
  419. GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
  420. GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
  421. ldapSec := Cfg.Section("auth.ldap")
  422. LdapEnabled = ldapSec.Key("enabled").MustBool(false)
  423. LdapConfigFile = ldapSec.Key("config_file").String()
  424. alerting := Cfg.Section("alerting")
  425. AlertingEnabled = alerting.Key("enabled").MustBool(false)
  426. readSessionConfig()
  427. readSmtpSettings()
  428. readQuotaSettings()
  429. if VerifyEmailEnabled && !Smtp.Enabled {
  430. log.Warn("require_email_validation is enabled but smpt is disabled")
  431. }
  432. return nil
  433. }
  434. func readSessionConfig() {
  435. sec := Cfg.Section("session")
  436. SessionOptions = session.Options{}
  437. SessionOptions.Provider = sec.Key("provider").In("memory", []string{"memory", "file", "redis", "mysql", "postgres", "memcache"})
  438. SessionOptions.ProviderConfig = strings.Trim(sec.Key("provider_config").String(), "\" ")
  439. SessionOptions.CookieName = sec.Key("cookie_name").MustString("grafana_sess")
  440. SessionOptions.CookiePath = AppSubUrl
  441. SessionOptions.Secure = sec.Key("cookie_secure").MustBool()
  442. SessionOptions.Gclifetime = Cfg.Section("session").Key("gc_interval_time").MustInt64(86400)
  443. SessionOptions.Maxlifetime = Cfg.Section("session").Key("session_life_time").MustInt64(86400)
  444. SessionOptions.IDLength = 16
  445. if SessionOptions.Provider == "file" {
  446. SessionOptions.ProviderConfig = makeAbsolute(SessionOptions.ProviderConfig, DataPath)
  447. os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
  448. }
  449. if SessionOptions.CookiePath == "" {
  450. SessionOptions.CookiePath = "/"
  451. }
  452. }
  453. var logLevels = map[string]int{
  454. "Trace": 0,
  455. "Debug": 1,
  456. "Info": 2,
  457. "Warn": 3,
  458. "Error": 4,
  459. "Critical": 5,
  460. }
  461. func getLogLevel(key string, defaultName string) (string, int) {
  462. levelName := Cfg.Section(key).Key("level").In(defaultName, []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
  463. level, ok := logLevels[levelName]
  464. if !ok {
  465. log.Fatal(4, "Unknown log level: %s", levelName)
  466. }
  467. return levelName, level
  468. }
  469. func initLogging(args *CommandLineArgs) {
  470. //close any existing log handlers.
  471. log.Close()
  472. // Get and check log mode.
  473. LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
  474. LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
  475. defaultLevelName, _ := getLogLevel("log", "Info")
  476. LogConfigs = make([]util.DynMap, len(LogModes))
  477. for i, mode := range LogModes {
  478. mode = strings.TrimSpace(mode)
  479. sec, err := Cfg.GetSection("log." + mode)
  480. if err != nil {
  481. log.Fatal(4, "Unknown log mode: %s", mode)
  482. }
  483. // Log level.
  484. _, level := getLogLevel("log."+mode, defaultLevelName)
  485. // Generate log configuration.
  486. switch mode {
  487. case "console":
  488. formatting := sec.Key("formatting").MustBool(true)
  489. LogConfigs[i] = util.DynMap{
  490. "level": level,
  491. "formatting": formatting,
  492. }
  493. case "file":
  494. logPath := sec.Key("file_name").MustString(filepath.Join(LogsPath, "grafana.log"))
  495. os.MkdirAll(filepath.Dir(logPath), os.ModePerm)
  496. LogConfigs[i] = util.DynMap{
  497. "level": level,
  498. "filename": logPath,
  499. "rotate": sec.Key("log_rotate").MustBool(true),
  500. "maxlines": sec.Key("max_lines").MustInt(1000000),
  501. "maxsize": 1 << uint(sec.Key("max_size_shift").MustInt(28)),
  502. "daily": sec.Key("daily_rotate").MustBool(true),
  503. "maxdays": sec.Key("max_days").MustInt(7),
  504. }
  505. case "conn":
  506. LogConfigs[i] = util.DynMap{
  507. "level": level,
  508. "reconnectOnMsg": sec.Key("reconnect_on_msg").MustBool(),
  509. "reconnect": sec.Key("reconnect").MustBool(),
  510. "net": sec.Key("protocol").In("tcp", []string{"tcp", "unix", "udp"}),
  511. "addr": sec.Key("addr").MustString(":7020"),
  512. }
  513. case "smtp":
  514. LogConfigs[i] = util.DynMap{
  515. "level": level,
  516. "user": sec.Key("user").MustString("example@example.com"),
  517. "passwd": sec.Key("passwd").MustString("******"),
  518. "host": sec.Key("host").MustString("127.0.0.1:25"),
  519. "receivers": sec.Key("receivers").MustString("[]"),
  520. "subject": sec.Key("subject").MustString("Diagnostic message from serve"),
  521. }
  522. case "database":
  523. LogConfigs[i] = util.DynMap{
  524. "level": level,
  525. "driver": sec.Key("driver").String(),
  526. "conn": sec.Key("conn").String(),
  527. }
  528. case "syslog":
  529. LogConfigs[i] = util.DynMap{
  530. "level": level,
  531. "network": sec.Key("network").MustString(""),
  532. "address": sec.Key("address").MustString(""),
  533. "facility": sec.Key("facility").MustString("local7"),
  534. "tag": sec.Key("tag").MustString(""),
  535. }
  536. }
  537. cfgJsonBytes, _ := json.Marshal(LogConfigs[i])
  538. log.NewLogger(Cfg.Section("log").Key("buffer_len").MustInt64(10000), mode, string(cfgJsonBytes))
  539. }
  540. }
  541. func LogConfigurationInfo() {
  542. var text bytes.Buffer
  543. text.WriteString("Configuration Info\n")
  544. text.WriteString("Config files:\n")
  545. for i, file := range configFiles {
  546. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, file))
  547. }
  548. if len(appliedCommandLineProperties) > 0 {
  549. text.WriteString("Command lines overrides:\n")
  550. for i, prop := range appliedCommandLineProperties {
  551. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
  552. }
  553. }
  554. if len(appliedEnvOverrides) > 0 {
  555. text.WriteString("\tEnvironment variables used:\n")
  556. for i, prop := range appliedEnvOverrides {
  557. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
  558. }
  559. }
  560. text.WriteString("Paths:\n")
  561. text.WriteString(fmt.Sprintf(" home: %s\n", HomePath))
  562. text.WriteString(fmt.Sprintf(" data: %s\n", DataPath))
  563. text.WriteString(fmt.Sprintf(" logs: %s\n", LogsPath))
  564. text.WriteString(fmt.Sprintf(" plugins: %s\n", PluginsPath))
  565. log.Info(text.String())
  566. }