setting.go 18 KB

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