Skip to content

Commit

Permalink
refactor logger config via env
Browse files Browse the repository at this point in the history
  • Loading branch information
sotnikov-s committed Oct 17, 2022
1 parent 2cf0006 commit 1c05dc4
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 136 deletions.
45 changes: 12 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ This repository contains a helper zap.Logger constructor that injects a mandator

Basically, the logger is configured with the [zap.NewProductionLogger](https://github.com/uber-go/zap/blob/d6ce3b9b283401bc6cf975de6ac3e5ed5aec5341/config.go#L115) with modified logs timestamps as RFC3339-formatted string with nanosecond precision.

For the default logger configuration the logging level is set using an env variable `LOGGER_LEVEL`. According to the [zap.Logger docs](https://github.com/uber-go/zap/blob/d6ce3b9b283401bc6cf975de6ac3e5ed5aec5341/level.go#L28), the expected `LOGGER_LEVEL` values are (the `dpanic`/`DPANIC` level is omitted, higher levels are more important):
For the default logger configuration the logging level is set using an env variable `LOGGER_LEVEL`. According to the [zap.Logger docs](https://github.com/uber-go/zap/blob/d6ce3b9b283401bc6cf975de6ac3e5ed5aec5341/level.go#L28), the expected `LOGGER_LEVEL` values are (higher levels are more important):
- `debug` or `DEBUG` — typically voluminous logs that are usually disabled in production;
- `info` or `INFO` — the default logging priority;
- `warn` or `WARN` — logs that are more important than Info, but don't need individual human review;
- `error` or `ERROR` — high-priority logs. If an application is running smoothly, it shouldn't generate any error-level logs;
- `dpanic` or `DPANIC` — logs a message, then panics. Works only if logger's development field set to true;
- `panic` or `PANIC` — logs a message, then panics;
- `fatal` or `FATAL` — logs a message, then calls os.Exit(1).

Although the default cfg is made for logger instantiation and usage simplicity, a user can still configure the logger the way the [zap.Config](https://github.com/uber-go/zap/blob/d6ce3b9b283401bc6cf975de6ac3e5ed5aec5341/config.go#L45) allows it by assigning a path to a config file to the `LOGGER_CFG_PATH` env variable. Supported extensions for the config file are `.json`, `.yml` and `.yaml`. Configuration made by the config file overwrites the `LOGGER_LEVEL` and should contain comprehensive configuration (this is why the example file below is so detailed although it doesn't contain all the configurable fields).
Although the default cfg is introduced for logger instantiation and usage simplicity, a user can still configure the logger the way the [zap.Config](https://github.com/uber-go/zap/blob/d6ce3b9b283401bc6cf975de6ac3e5ed5aec5341/config.go#L45) allows it via _setting_ env variables. If no value is set to an env variable, the production logger config value is used by default. See the [env configuration example](#configuration-via-env-variables) for details.

Reminder: a _set_ env variable is a variable of any value stored in the system, even an empty one. So, e.g. `export LOGGER_ENCODERCONFIG_TIMEKEY=` will not apply a default value to the logger, but will result in the timestamp key absence. To set a default value, use `unset LOGGER_ENCODERCONFIG_TIMEKEY`. This is true for all config parameters.

## Examples

Expand Down Expand Up @@ -50,41 +53,17 @@ results in (stack trace and caller messages are removed for simplicity):
{"level":"error","ts":"2022-09-27T09:00:19.78001+03:00","msg":"error","context":"my_application"}
```

### Providing a cfg file

define a `cfg.json` (you can use the following as a boilerplate):
```json
{
"level": "warn",
"outputPaths": [
"stderr"
],
"errorOutputPaths": [
"stderr"
],
"encoding": "console",
"sampling": {
"initial": 100,
"thereafter": 100
},
"encoderConfig": {
"timeKey": "ts",
"levelKey": "level",
"nameKey": "logger",
"callerKey": "caller",
"messageKey": "msg",
"stacktraceKey": "stacktrace",
"lineEnding": "\n",
"timeEncoder": "ISO8601"
}
}
```
### Configuration via env variables

make the config file available via `LOGGER_CFG_PATH` env variable
configure all needed logger parameters by exporting corresponding env variables, e.g.:
```bash
export LOGGER_CFG_PATH=cfg.json
export LOGGER_LEVEL=warn
export LOGGER_ENCODING=console
export LOGGER_ENCODERCONFIG_ENCODETIME=ISO8601
```

As you can see, just like LOGGER_LEVEL, all logger-related env variables are prefixed with `LOGGER_` and then writen solidly with `_` as structure level separator.

```go
package main

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/neutron-org/neutron-logger
go 1.18

require (
github.com/kelseyhightower/envconfig v1.4.0
go.uber.org/zap v1.23.0
gopkg.in/yaml.v2 v2.4.0
)

require (
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -15,8 +17,4 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
62 changes: 9 additions & 53 deletions logger.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
package logger

import (
"fmt"
"io/ioutil"
"os"

"github.com/kelseyhightower/envconfig"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

var (
// loggerCfgPath is the path to a logger configuration file. Expected to be either json or yaml.
loggerCfgPath = os.Getenv("LOGGER_CFG_PATH")
// loggerLevel defines the logger logging level.
loggerLevel = os.Getenv("LOGGER_LEVEL")
)
// loggerPrefix is the prefix for env variables that configure the logger.
const loggerPrefix = "LOGGER"

// NewForContext returns a new zap logger with an injected "context" field that is always
// presented in the logger messages. If a LOGGER_CFG_PATH env variable is defined, the logger
// config is build using the given file. Otherwise, a default simple config is used.
func NewForContext(context string) (*zap.Logger, error) {
cfg, err := loadCfg()
cfg, err := loadConfig()
if err != nil {
return nil, err
}
Expand All @@ -32,51 +25,14 @@ func NewForContext(context string) (*zap.Logger, error) {
return l, nil
}

// loadCfg loads logger configuration from the given config file or, if it's empty, returns the
// default config.
func loadCfg() (*zap.Config, error) {
if loggerCfgPath == "" {
return defaultLoggerCfg(), nil
}
cfg, err := cfgFromFile()
if err != nil {
return nil, fmt.Errorf("failed to build logger config from provided file %s: %w", loggerCfgPath, err)
}
return cfg, nil
}

// defaultLoggerCfg creates a default logger config.
func defaultLoggerCfg() *zap.Config {
// loadConfig initializes a default production logger config with parameters overwritten by
// corresponding env vars if set.
func loadConfig() (*zap.Config, error) {
cfg := zap.NewProductionConfig()
cfg.Level = zap.NewAtomicLevelAt(parseLevel())
cfg.EncoderConfig.EncodeTime = zapcore.RFC3339NanoTimeEncoder
return &cfg
}

// cfgFromFile loads logger configuration from loggerCfgPath.
func cfgFromFile() (*zap.Config, error) {
unmarshalFn, err := getCfgUnmarshaller(loggerCfgPath)
err := envconfig.Process(loggerPrefix, &cfg)
if err != nil {
return nil, err
}

cfgData, err := ioutil.ReadFile(loggerCfgPath)
if err != nil {
return nil, fmt.Errorf("read cfg file error: %w", err)
}
cfg, err := unmarshalFn(cfgData)
if err != nil {
return nil, fmt.Errorf("unmarshal error: %w", err)
}
return cfg, nil
}

// parseLevel parses logger level env into zapcore.Level. The env value is expected to contain one of
// the zapcore.Level const values. On an unexpected value, an Info level is returned.
func parseLevel() zapcore.Level {
var level zapcore.Level
if err := level.UnmarshalText([]byte(loggerLevel)); err != nil {
return zapcore.InfoLevel
}
return level
return &cfg, nil
}
45 changes: 0 additions & 45 deletions unmarshal.go

This file was deleted.

0 comments on commit 1c05dc4

Please sign in to comment.