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  }