~bayindirh/nudge

a86277cb25a9f9ce91caa42a1cfe81f925b8f495 — Hakan Bayindir 6 months ago 0035d0d
feat: Continue implementing log_level flag.

- refactor: Remove logging directives from initializeValidConfigurationOptions(), because it needs to run before logger initialization.
- refactor: Remove logging directives from applyDefaultConfiguration(), because it needs to run before logger initialization.
- refactor: Promote "No message body specified, exiting." message to warning level.
2 files changed, 70 insertions(+), 21 deletions(-)

M CHANGELOG.md
M src/nudge.go
M CHANGELOG.md => CHANGELOG.md +10 -0
@@ 2,6 2,16 @@

**Note:** Please add newest entries on top. Use ISO date format YYYY-MM-DD and markdown formatting.

## 2023-07-30

- refactor: Remove logging directives from `initializeValidConfigurationOptions()`, because it needs to run before logger initialization.
- refactor: Remove logging directives from `applyDefaultConfiguration()`, because it needs to run before logger initialization.
- refactor: Promote "No message body specified, exiting." message to `warning` level.

## 2023-07-29

- refactor: Demote "No message body specified, exiting." message to `debug` level.

## 2023-07-26

- fix: Correct a small debug log line to print prettier.

M src/nudge.go => src/nudge.go +60 -21
@@ 32,6 32,7 @@ import (

	"github.com/spf13/viper"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// This struct contains all the fields required for sending a notification.


@@ 91,19 92,20 @@ type ValidConfigurationOptions struct {
// This function initializes the ValidConfigurationOptions data structure, which will be
// used by the configuration sanity checker down the road, to make sure that everything
// is nice and shiny.
func initializeValidConfigurationOptions(ValidConfigurationOptions *ValidConfigurationOptions, logger *zap.SugaredLogger) {
func initializeValidConfigurationOptions(ValidConfigurationOptions *ValidConfigurationOptions) {
	ValidConfigurationOptions.possibleLogLevels = []string{"debug", "info", "warning", "error", "fatal", "panic"}
}

// This function stores and applies the defaults of the application.
// It's called first to initialize the defaults, then config file and the lastly the flags
// Can change these defaults.
func applyDefaultConfiguration(runtimeConfiguration *RuntimeConfiguration, notificationToSend *Notification, logger *zap.SugaredLogger) {
func applyDefaultConfiguration(runtimeConfiguration *RuntimeConfiguration, notificationToSend *Notification) {
	// Get the notificationTitle first. We will use it for message titles, as default
	notificationTitle, err := os.Hostname()

	if err != nil {
		logger.Warnf("Encountered an error while obtaining hostname, will use \"Nudge\" instead.")
		// Since logger is not up when we call this function, we'll use Println here.
		fmt.Println("Encountered an error while obtaining hostname, will use \"Nudge\" instead.")
		notificationTitle = "Nudge"
	}



@@ 117,10 119,10 @@ func applyDefaultConfiguration(runtimeConfiguration *RuntimeConfiguration, notif
	// Let's set defaults for the application itself, again where it makes sense.
	applicationName := strings.Split(os.Args[0], "/")
	runtimeConfiguration.applicationName = applicationName[len(applicationName)-1]
	runtimeConfiguration.version = "0.1.0"
	runtimeConfiguration.version = "0.2.0a20230729"
	runtimeConfiguration.dryrun = false
	runtimeConfiguration.logfilePaths = []string{"stdout"}
	runtimeConfiguration.logLevel = "info"
	runtimeConfiguration.logLevel = "warning"
}

// This function reads the configuration file and applies it to relevant data structures.


@@ 293,6 295,30 @@ func applyFlags(setFlags *[]string, flagStorage *FlagStorage, runtimeConfigurati
	logger.Debugf("Application of flags is completed, returning.")
}

/*
 * This function returns the appropriate Zap level for configuring the logger on the fly.
 * As a precaution, this function returns a sane default (INFO) if the supplied level
 * makes no sense.
 */
func getZapLoggerLevel(logLevel *string) zap.AtomicLevel {
	switch *logLevel {
	case "debug", "DEBUG":
		return zap.NewAtomicLevelAt(zapcore.DebugLevel)
	case "info", "INFO":
		return zap.NewAtomicLevelAt(zapcore.InfoLevel)
	case "warning", "WARNING":
		return zap.NewAtomicLevelAt(zapcore.WarnLevel)
	case "error", "ERROR":
		return zap.NewAtomicLevelAt(zapcore.ErrorLevel)
	case "fatal", "FATAL":
		return zap.NewAtomicLevelAt(zapcore.FatalLevel)
	case "panic", "PANIC":
		return zap.NewAtomicLevelAt(zapcore.PanicLevel)
	default:
		return zap.NewAtomicLevel()
	}
}

// This function checks configuration sanity when called, and creates warnings or errors depending on the situation.
// This function also called just before sending the notification, hence it handles showing help, creating warnings and other
// related user interfacing notifications.


@@ 393,9 419,24 @@ func main() {
	// Since there's no easy way to check whether a flag is set, I need a list of set flags.
	var setFlags []string

	// Start by applying default configuration before building things up.
	// Start with initializing valid configuration space.
	initializeValidConfigurationOptions(&validConfigurationOptions)

	// Next, apply the defaults:
	applyDefaultConfiguration(&runtimeConfiguration, &notificationToSend)

	// TODO: Prettify logger with some stylistic configuration here.
	// Relevant documentation is here: https://pkg.go.dev/go.uber.org/zap#hdr-Configuring_Zap

	/*
	 * To be able to ship 0.2 as quickly as possible, Zap will be configured as follows:
	 * 1- Create a good enough configuration with JSON based on system defaults.
	 * 2- Build Zap with that configuration.
	 * 3- Reconfigure Zap after parsing all the options and creating the final runtime
	 *    configuration.
	 *
	 * XXX: This JSON config part will be replaced with a programmatic block later.
	 */
	zapDefaultConfigJSON := []byte(`{
	  "level": "debug",
	  "encoding": "console",


@@ 410,11 451,14 @@ func main() {
	  }
	}`)

	var cfg zap.Config
	if err := json.Unmarshal(zapDefaultConfigJSON, &cfg); err != nil {
	var zapDefaultConfig zap.Config
	if err := json.Unmarshal(zapDefaultConfigJSON, &zapDefaultConfig); err != nil {
		panic(err)
	}
	logger := zap.Must(cfg.Build())
	
	// Let's change the logger's logging level before building the logger.
	zapDefaultConfig.Level = getZapLoggerLevel(&runtimeConfiguration.logLevel)
	logger := zap.Must(zapDefaultConfig.Build())

	defer logger.Sync() // Make sure that we sync when we exit.



@@ 422,12 466,6 @@ func main() {
	sugaredLogger := logger.Sugar()
	sugaredLogger.Debugf("Logger is up.")

	// Start with initializing valid configuration space.
	initializeValidConfigurationOptions(&validConfigurationOptions, sugaredLogger)

	// Next, apply the defaults:
	applyDefaultConfiguration(&runtimeConfiguration, &notificationToSend, sugaredLogger)

	// Then let's see what we have at hand (options, parameters, flags).
	// Flags are parsed first, stored in a secondary config area.
	// This allows us to override config file with flags more gracefully.


@@ 444,6 482,10 @@ func main() {
	flag.Visit(func(setFlag *flag.Flag) {
		setFlags = append(setFlags, setFlag.Name)
	})
	
	// Run all the configuration checks and issue relevant warnings or errors.
	// TODO: Is here the best place to call this? Take a look before releasing.
	checkConfigurationSanity(&notificationToSend, &runtimeConfiguration, &validConfigurationOptions, sugaredLogger)

	// Then I can apply the set flags.
	applyFlags(&setFlags, &flagStorage, &runtimeConfiguration, &notificationToSend, sugaredLogger)


@@ 457,15 499,12 @@ func main() {

	// If no message is given, let's print help and exit.
	if notificationToSend.messageBody == "" {
		sugaredLogger.Errorf("No message body specified, exiting.")
		sugaredLogger.Errorf("Usage: %s [OPTIONS] message", runtimeConfiguration.applicationName)
		sugaredLogger.Debugf("No message body specified, exiting.")
		fmt.Printf("Usage: %s [OPTIONS] message\n", runtimeConfiguration.applicationName)
		flag.PrintDefaults()
		os.Exit(1)
	}

	// Run all the configuration checks and issue relevant warnings or errors.
	checkConfigurationSanity(&notificationToSend, &runtimeConfiguration, &validConfigurationOptions, sugaredLogger)

	// Show the current state of the code.
	printState(&runtimeConfiguration, &notificationToSend, sugaredLogger)



@@ 473,6 512,6 @@ func main() {
	if runtimeConfiguration.dryrun == false {
		sendNotification(&notificationToSend, &runtimeConfiguration)
	} else {
		sugaredLogger.Infow("Not sending the notification since in dry run mode.")
		sugaredLogger.Warnf("Not sending the notification since in dry run mode.")
	}
}