~bayindirh/nudge

9e414264dbe399ba108feb00a9b5f140842cd957 — Hakan Bayindir 4 months ago ce23037 + 551738c
Merge branch 'devel'
5 files changed, 70 insertions(+), 57 deletions(-)

M CHANGELOG.md
M README.md
M RELEASE_NOTES.md
M src/conf/nudge.conf.example
M src/nudge.go
M CHANGELOG.md => CHANGELOG.md +18 -0
@@ 2,6 2,24 @@

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

## 2023-10-16

- proj: Release version 0.3.0.
- proj: Bumped version to 0.3.0.
- fix: Change a mistyped `Debug` to `Debugf`.
- refactor: Did some code reorganization.
- refactor: Remove logging level support from configuration file.
- refactor: Remove logging relates examples from `nudge.conf.example`.
- refactor: Logging file path support removed from `runTimeConfiguration` struct and `readAndApplyConfiguration` function.
- refactor: Remove a couple of information lines from `printState` function. Because logging file support is going away.
- refactor: Move logger initialization to the beginning of the process.

## 2023-10-15

- proj: Bump version to `0.3.0a1` to mark development start.
- feat: Add a small `debug` log line in `printState` function to show how many log targets are defined.
- refactor: Pass through `gofmt`.

## 2023-07-31

- proj: Version is changed to 0.2.0

M README.md => README.md +8 -2
@@ 4,10 4,18 @@

**Current state:** *Almost beta*. Can be used reliably to send messages daily, but things may still change, esp. command line arguments.

**Current version:** 0.3.0

nudge provides a small command line tool to send push notifications over 
[Pushover](https://pushover.net). It aims to provide a simple and composable
tool to send push notifications.

## What's New

- Since Zap doesn't allow log sink reconfiguration during runtime, all logging related configuration options are removed from the configuration file. Please use redirections to save your logs to files. Normal logs go to `stdout`, errors go to `stderr`.
- Log levels passed via command line are respected, but not applied immediately. If you're debugging for development, please change default options inside the code (see `applyDefaultConfiguration` function).
- You can override the location of your configuration file via command line. See `-help`. Doing this disables auto detection. Nudge will exit with an error if the file cannot be found or read.

## Building

After cloning the repository, going to `src/` folder and running `go build nudge.go` should do.


@@ 75,8 83,6 @@ Current options are as follows:

## Known Issues

- Logs can't be redirected to files via configuration file or command line, yet. These options are not handled in the code.
- Configuration sanity checking doesn't check everything for acceptable values.
- Not all features provided by Pushover is implemented (image attachments, HTML formatting, and possibly others)

## Other Details

M RELEASE_NOTES.md => RELEASE_NOTES.md +20 -0
@@ 2,6 2,26 @@

# Nudge Release Notes

## v0.3.0

### At a Glance

This version brings Nudge to Beta level. Configuration file is stabilized for now, and inner workings are polished. Some half baked parts are removed.

### What’s New

- Logging related options are removed from the configuration file, since Zap library doesn't support dynamic reconfiguration except logging level.

### Known Issues

- Not all features provided by Pushover is supported yet.

### Next Plans

- Starting to add missing Pushover features.



## v0.2.0

**Release Date:** 20230731

M src/conf/nudge.conf.example => src/conf/nudge.conf.example +0 -7
@@ 6,10 6,3 @@
# Both keys should be 30 characters long.
api_key  = '!-CHANGE-WITH-API-KEY-CAFEBABE'
user_key = '!CHANGE-WITH-USER-KEY-CAFED00D'

[logging]
# For more details using logging_paths, see https://pkg.go.dev/go.uber.org/zap#example-package-BasicConfiguration
logging_paths  = ['stdout', '/var/log/nudge.log']
# A default, fairly verbose log level to see what the code is doing.
# NOTE: Log levels are case sensitive.
log_level = 'info'

M src/nudge.go => src/nudge.go +24 -48
@@ 51,14 51,13 @@ type Notification struct {

// This struct contains the configuration information we are going to use during execution.
type RuntimeConfiguration struct {
	applicationName string   // Stores the sanitized version of the application name.
	version         string   // Stores the version of the application.
	dryrun          bool     // Setting to true will make code switch to "simulation" mode.
	configFilePath  string   // This variable stores our configuration file's path.
	apiKey          string   // Secret key we use for sending messages.
	userKey         string   // Secret key of the user, used as a recipient address.
	logfilePaths    []string // Contains the absolute path for the logfile.
	logLevel        string   // Contains the logging level the application starts with.
	applicationName string // Stores the sanitized version of the application name.
	version         string // Stores the version of the application.
	dryrun          bool   // Setting to true will make code switch to "simulation" mode.
	configFilePath  string // This variable stores our configuration file's path.
	apiKey          string // Secret key we use for sending messages.
	userKey         string // Secret key of the user, used as a recipient address.
	logLevel        string // Contains the logging level the application starts with.
}

// Following struct is for temporarily storing flag values passed during program invocation.


@@ 118,9 117,8 @@ 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.2.0"
	runtimeConfiguration.version = "0.3.0"
	runtimeConfiguration.dryrun = false
	runtimeConfiguration.logfilePaths = []string{"stdout"}
	runtimeConfiguration.logLevel = "warn"
}



@@ 169,8 167,8 @@ func readAndApplyConfiguration(configurationFilePath *string, runtimeConfigurati
	if err != nil {
		logger.Debugf("Cannot find any configuration file, continuing using built-in defaults (%s).", err.Error())
	} else {

		// If we've reached here, we can take note of the file path now.
		// This also means that we have read the config file successfully.
		runtimeConfiguration.configFilePath = viper.ConfigFileUsed()

		// Let the user know where the config file is.


@@ 188,19 186,6 @@ func readAndApplyConfiguration(configurationFilePath *string, runtimeConfigurati
			runtimeConfiguration.userKey = viper.GetString("pushover.user_key")
			logger.Debugf("Pushover User key is found & set to %s.", runtimeConfiguration.userKey)
		}

		// Logging related configuration follows:
		// Logging paths:
		if viper.IsSet("logging.logging_paths") {
			runtimeConfiguration.logfilePaths = viper.GetStringSlice("logging.logging_paths")
			logger.Debugf("Logging paths are found & set to %s.", runtimeConfiguration.logfilePaths)
		}

		// Logging level:
		if viper.IsSet("logging.log_level") {
			runtimeConfiguration.logLevel = viper.GetString("logging.log_level")
			logger.Debugf("Logging level is found and set to %s.", runtimeConfiguration.logLevel)
		}
	}
}



@@ 338,7 323,6 @@ func printState(runtimeConfiguration *RuntimeConfiguration, notification *Notifi
	logger.Infof("Configuration file path: %s", runtimeConfiguration.configFilePath)
	logger.Infof("Pushover API key: %s", runtimeConfiguration.apiKey)
	logger.Infof("Pushover user key: %s", runtimeConfiguration.userKey)
	logger.Infof("Log file path: %s", runtimeConfiguration.logfilePaths)
	logger.Infof("Current logging level is %s.", runtimeConfiguration.logLevel)
	logger.Infof("") // Leave an empty line.



@@ 401,17 385,9 @@ func main() {
	// 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.
	 */
	// Initialize Zap logger once and for all here. Because except log levels, Zap
	// doesn't support reconfiguration. For a completely reconfigurable variant,
	// there's Thales' flume (https://github.com/ThalesGroup/flume)
	zapDefaultConfigJSON := []byte(`{
	  "level": "debug",
	  "encoding": "console",


@@ 430,18 406,18 @@ func main() {
	if err := json.Unmarshal(zapDefaultConfigJSON, &zapRuntimeConfig); err != nil {
		panic(err)
	}
	

	/*
	 * We build the logger with the default configuration before everything else.
	 * We know that the config is sane, because it's hardcoded. We're using built-in
	 * defaults at that point.
	 */
	zapDefaultAtomicLevel, err := zap.ParseAtomicLevel(runtimeConfiguration.logLevel)
	

	if err != nil {
		panic (err)
		panic(err)
	}
	

	zapRuntimeConfig.Level = zapDefaultAtomicLevel
	logger := zap.Must(zapRuntimeConfig.Build())



@@ 467,27 443,27 @@ func main() {
	flag.Visit(func(setFlag *flag.Flag) {
		setFlags = append(setFlags, setFlag.Name)
	})
	

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

	// 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)

	// This is the demarcation line between config reading and starting doing things.
	

	// Let's try to change the logging level of our current logger.
	newLogLevelJSON := []byte(runtimeConfiguration.logLevel)
	

	err = zapRuntimeConfig.Level.UnmarshalText(newLogLevelJSON)
	

	if err != nil {
		sugaredLogger.Panicf("Supplied log level %s is invalid, exiting.", runtimeConfiguration.logLevel)
	}
	
	sugaredLogger.Debug("Logging level is changed to %s.", runtimeConfiguration.logLevel)
	

	sugaredLogger.Debugf("Logging level is changed to %s.", runtimeConfiguration.logLevel)

	// Check whether our version is asked or not.
	// Version info shall always return clean.
	if flagStorage.versionRequested {