github.com/xmidt-org/webpa-common@v1.11.9/server/viper.go (about) 1 package server 2 3 import ( 4 "fmt" 5 "os" 6 "runtime" 7 "runtime/pprof" 8 "strings" 9 "time" 10 11 "github.com/go-kit/kit/log" 12 "github.com/go-kit/kit/log/level" 13 "github.com/spf13/pflag" 14 "github.com/spf13/viper" 15 "github.com/xmidt-org/webpa-common/logging" 16 "github.com/xmidt-org/webpa-common/xmetrics" 17 ) 18 19 const ( 20 // DefaultPrimaryAddress is the bind address of the primary server (e.g. talaria, petasos, etc) 21 DefaultPrimaryAddress = ":8080" 22 23 // DefaultHealthAddress is the bind address of the health check server 24 DefaultHealthAddress = ":8081" 25 26 // DefaultMetricsAddress is the bind address of the metrics server 27 DefaultMetricsAddress = ":8082" 28 29 // DefaultPprofAddress is the bind address of the pprof server 30 DefaultPprofAddress = ":6060" 31 32 // DefaultHealthLogInterval is the interval at which health statistics are emitted 33 // when a non-positive log interval is specified 34 DefaultHealthLogInterval time.Duration = time.Duration(60 * time.Second) 35 36 // DefaultLogConnectionState is the default setting for logging connection state messages. This 37 // value is primarily used when a *WebPA value is nil. 38 DefaultLogConnectionState = false 39 40 // AlternateSuffix is the suffix appended to the server name, along with a period (.), for 41 // logging information pertinent to the alternate server. 42 AlternateSuffix = "alternate" 43 44 // DefaultProject is used as a metrics namespace when one is not defined. 45 DefaultProject = "xmidt" 46 47 // HealthSuffix is the suffix appended to the server name, along with a period (.), for 48 // logging information pertinent to the health server. 49 HealthSuffix = "health" 50 51 // PprofSuffix is the suffix appended to the server name, along with a period (.), for 52 // logging information pertinent to the pprof server. 53 PprofSuffix = "pprof" 54 55 // MetricsSuffix is the suffix appended to the server name, along with a period (.), for 56 // logging information pertinent to the metrics server. 57 MetricsSuffix = "metrics" 58 59 // FileFlagName is the name of the command-line flag for specifying an alternate 60 // configuration file for Viper to hunt for. 61 FileFlagName = "file" 62 63 // FileFlagShorthand is the command-line shortcut flag for FileFlagName 64 FileFlagShorthand = "f" 65 66 // CPUProfileFlagName is the command-line flag for creating a cpuprofile on the server 67 CPUProfileFlagName = "cpuprofile" 68 69 // CPUProfileShortHand is the command-line shortcut for creating cpushorthand on the server 70 CPUProfileShorthand = "c" 71 72 // MemProfileFlagName is the command-line flag for creating memprofile on the server 73 MemProfileFlagName = "memprofile" 74 75 // MemProfileShortHand is the command-line shortcut for creating memprofile on the server 76 MemProfileShorthand = "m" 77 ) 78 79 // ConfigureFlagSet adds the standard set of WebPA flags to the supplied FlagSet. Use of this function 80 // is optional, and necessary only if the standard flags should be supported. However, this is highly desirable, 81 // as ConfigureViper can make use of the standard flags to tailor how configuration is loaded or if gathering cpuprofile 82 // or memprofile data is needed. 83 func ConfigureFlagSet(applicationName string, f *pflag.FlagSet) { 84 f.StringP(FileFlagName, FileFlagShorthand, applicationName, "base name of the configuration file") 85 f.StringP(CPUProfileFlagName, CPUProfileShorthand, "cpuprofile", "base name of the cpuprofile file") 86 f.StringP(MemProfileFlagName, MemProfileShorthand, "memprofile", "base name of the memprofile file") 87 } 88 89 // create CPUProfileFiles creates a cpu profile of the server, its triggered by the optional flag cpuprofile 90 // 91 // the CPU profile is created on the server's start 92 func CreateCPUProfileFile(v *viper.Viper, fp *pflag.FlagSet, l log.Logger) { 93 if fp == nil { 94 return 95 } 96 97 flag := fp.Lookup("cpuprofile") 98 if flag == nil { 99 return 100 } 101 102 f, err := os.Create(flag.Value.String()) 103 if err != nil { 104 l.Log("could not create CPU profile: ", err) 105 } 106 defer f.Close() 107 108 if err := pprof.StartCPUProfile(f); err != nil { 109 l.Log("could not start CPU profile: ", err) 110 } 111 112 defer pprof.StopCPUProfile() 113 } 114 115 // Create CPUProfileFiles creates a memory profile of the server, its triggered by the optional flag memprofile 116 // 117 // the memory profile is created on the server's exit. 118 // this function should be used within the application. 119 func CreateMemoryProfileFile(v *viper.Viper, fp *pflag.FlagSet, l log.Logger) { 120 if fp == nil { 121 return 122 } 123 124 flag := fp.Lookup("memprofile") 125 if flag == nil { 126 return 127 } 128 129 f, err := os.Create(flag.Value.String()) 130 if err != nil { 131 l.Log("could not create memory profile: ", err) 132 } 133 134 defer f.Close() 135 runtime.GC() 136 if err := pprof.WriteHeapProfile(f); err != nil { 137 l.Log("could not write memory profile: ", err) 138 } 139 } 140 141 // ConfigureViper configures a Viper instances using the opinionated WebPA settings. All WebPA servers should 142 // use this function. 143 // 144 // The flagSet is optional. If supplied, it will be bound to the given Viper instance. Additionally, if the 145 // flagSet has a FileFlagName flag, it will be used as the configuration name to hunt for instead of the 146 // application name. 147 func ConfigureViper(applicationName string, f *pflag.FlagSet, v *viper.Viper) (err error) { 148 v.AddConfigPath(fmt.Sprintf("/etc/%s", applicationName)) 149 v.AddConfigPath(fmt.Sprintf("$HOME/.%s", applicationName)) 150 v.AddConfigPath(".") 151 152 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 153 v.SetEnvPrefix(applicationName) 154 v.AutomaticEnv() 155 156 v.SetDefault("primary.name", applicationName) 157 v.SetDefault("primary.address", DefaultPrimaryAddress) 158 v.SetDefault("primary.logConnectionState", DefaultLogConnectionState) 159 160 v.SetDefault("alternate.name", fmt.Sprintf("%s.%s", applicationName, AlternateSuffix)) 161 162 v.SetDefault("health.name", fmt.Sprintf("%s.%s", applicationName, HealthSuffix)) 163 v.SetDefault("health.address", DefaultHealthAddress) 164 v.SetDefault("health.logInterval", DefaultHealthLogInterval) 165 v.SetDefault("health.logConnectionState", DefaultLogConnectionState) 166 167 v.SetDefault("pprof.name", fmt.Sprintf("%s.%s", applicationName, PprofSuffix)) 168 v.SetDefault("pprof.address", DefaultPprofAddress) 169 v.SetDefault("pprof.logConnectionState", DefaultLogConnectionState) 170 171 v.SetDefault("metric.name", fmt.Sprintf("%s.%s", applicationName, MetricsSuffix)) 172 v.SetDefault("metric.address", DefaultMetricsAddress) 173 174 v.SetDefault("project", DefaultProject) 175 176 configName := applicationName 177 if f != nil { 178 if fileFlag := f.Lookup(FileFlagName); fileFlag != nil { 179 // use the command-line to specify the base name of the file to be searched for 180 configName = fileFlag.Value.String() 181 } 182 183 err = v.BindPFlags(f) 184 } 185 186 v.SetConfigName(configName) 187 return 188 } 189 190 /* 191 Configure is a one-stop shopping function for preparing WebPA configuration. This function 192 does not itself read in configuration from the Viper environment. Typical usage is: 193 194 var ( 195 f = pflag.NewFlagSet() 196 v = viper.New() 197 ) 198 199 if err := server.Configure("petasos", os.Args, f, v); err != nil { 200 // deal with the error, possibly just exiting 201 } 202 203 // further customizations to the Viper instance can be done here 204 205 if err := v.ReadInConfig(); err != nil { 206 // more error handling 207 } 208 209 Usage of this function is only necessary if custom configuration is needed. Normally, 210 using New will suffice. 211 */ 212 func Configure(applicationName string, arguments []string, f *pflag.FlagSet, v *viper.Viper) (err error) { 213 if f != nil { 214 ConfigureFlagSet(applicationName, f) 215 err = f.Parse(arguments) 216 if err != nil { 217 return 218 } 219 } 220 221 err = ConfigureViper(applicationName, f, v) 222 return 223 } 224 225 /* 226 Initialize handles the bootstrapping of the server code for a WebPA node. It configures Viper, 227 reads configuration, and unmarshals the appropriate objects. This function is typically all that's 228 needed to fully instantiate a WebPA server. Typical usage: 229 230 var ( 231 f = pflag.NewFlagSet() 232 v = viper.New() 233 234 // can customize both the FlagSet and the Viper before invoking New 235 logger, registry, webPA, err = server.Initialize("petasos", os.Args, f, v) 236 ) 237 238 if err != nil { 239 // deal with the error, possibly just exiting 240 } 241 242 Note that the FlagSet is optional but highly encouraged. If not supplied, then no command-line binding 243 is done for the unmarshalled configuration. 244 245 This function always returns a logger, regardless of any errors. This allows clients to use the returned 246 logger when reporting errors. This function falls back to a logger that writes to os.Stdout if it cannot 247 create a logger from the Viper environment. 248 */ 249 func Initialize(applicationName string, arguments []string, f *pflag.FlagSet, v *viper.Viper, modules ...xmetrics.Module) (logger log.Logger, registry xmetrics.Registry, webPA *WebPA, err error) { 250 defer func() { 251 if err != nil { 252 // never return a WebPA in the presence of an error, to 253 // avoid an ambiguous API 254 webPA = nil 255 256 // Make sure there's at least a default logger for the caller to use 257 logger = logging.DefaultLogger() 258 } 259 }() 260 261 if err = Configure(applicationName, arguments, f, v); err != nil { 262 return 263 } 264 265 if err = v.ReadInConfig(); err != nil { 266 return 267 } 268 269 webPA = &WebPA{ 270 ApplicationName: applicationName, 271 } 272 273 err = v.Unmarshal(webPA) 274 if err != nil { 275 return 276 } 277 278 logger = logging.New(webPA.Log) 279 logger.Log(level.Key(), level.InfoValue(), logging.MessageKey(), "initialized Viper environment", "configurationFile", v.ConfigFileUsed()) 280 281 if len(webPA.Metric.MetricsOptions.Namespace) == 0 { 282 webPA.Metric.MetricsOptions.Namespace = applicationName 283 } 284 285 if len(webPA.Metric.MetricsOptions.Subsystem) == 0 { 286 webPA.Metric.MetricsOptions.Subsystem = applicationName 287 } 288 289 webPA.Metric.MetricsOptions.Logger = logger 290 registry, err = webPA.Metric.NewRegistry(modules...) 291 if err != nil { 292 return 293 } 294 295 CreateCPUProfileFile(v, f, logger) 296 297 return 298 }