-
Notifications
You must be signed in to change notification settings - Fork 3
Implement structured logging with zap #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ogical_backup with conflicts.
One issue that popped up when examining logical backup is that often we construct error messages with Sprintf before passing them to the upper-level logger. This makes logging inconsistent when taking advantage of the logging field to show parameters like table names or message LSNs.
Add more With functions (WithTableNameString, WithHint). Fix crash at NewLoggerFrom when no withFields arguments are passed. Call sync for loggers on exit.
Move InitGlobalLogger to the logger package.
By defining our logger we can take advantage of With functions and avoid excessive imports of zap. Minor cleanup all over the place, mostly replacing zap's With with our own With.. functions and changing *f functions to plain ones where possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found some fixes!
P.S. share your ideas, feedbacks or issues with us at https://github.com/fixmie/feedback (this message will be removed after the beta stage).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found some fixes!
P.S. share your ideas, feedbacks or issues with us at https://github.com/fixmie/feedback (this message will be removed after the beta stage).
} | ||
|
||
// PrintMessageForDebug emits a log entry describing the current message received for debug purposes. | ||
func PrintMessageForDebug(prefix string, msg message.Message, currentLSN dbutils.LSN, log *Log) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like to bot went bonkers here, reported and ignoring this one.
} | ||
|
||
// PrintMessageForDebug emits a log entry describing the current message received for debug purposes. | ||
func PrintMessageForDebug(prefix string, msg message.Message, currentLSN dbutils.LSN, log *Log) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like to bot went bonkers here, reported and ignoring this one.
pkg/config/config.go
Outdated
log.Printf("Staging directory: %q", c.StagingDir) | ||
} else { | ||
log.Printf("No staging directory. Writing directly to the archive dir") | ||
ops := [][]string{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
map[string]string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my initial choice, but map does not preserve the order and I don't want help screen to be sorted by random()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but the current implementation along with ops = ops[1:]
is ugly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it's less ugly than a bunch of unstructured log.Printfs, although I'll think about improving it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH I don't see a problem with bunch of logger.G.With(<param>, <value>).Info("option")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what you really can do to improve that part, is to make use of reflect
to iterate over config struct fields
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with a bunch of logger statements is that they promote unnecessary repetitions and shift the solution to writing more code instead of using proper data structures . The list solution is adequate, albeit some ugly parts you've highlighted; the reflection would neither solve the issue (as we don't output the literal name of the parameter, but some human-readable variation of it), nor simplify the code. Instead, I'm changing it back to maps and just sorting by keys to ensure a consistent output on every run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out map approach doesn't buy us much in terms of readability, as it is not possible to write a function like ternary(ptr != nil, fmt.Sprintf("%v", *ptr), "")
without crashing if ptr is null, reverted back to just show each parameter in a separate log.
pkg/config/config.go
Outdated
if cfg.PrometheusPort == 0 { | ||
cfg.PrometheusPort = defaultPrometheusPort | ||
} | ||
if debug { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if
makes no sense here. bool
is initialized with the false
value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to overwrite explicitly set to true cfg.Debug with a default (false) value from a command-line.
pkg/logger/logger.go
Outdated
} else { | ||
logger, err = zap.NewProduction() | ||
} | ||
if err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd do vice versa: check if there's an error here
|
||
psql -d postgres -c "drop database if exists lb_test1;" | ||
psql -d postgres -c "drop database if exists lb_test2;" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd create separate PR for the changes in the test script
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm....good catch, I didn't intend to change anything in the test.
cmd/restore/main.go
Outdated
schemaName = flag.String("schema", "public", "Schema name") | ||
targetTable = flag.String("target-table", "", "Target table name (optional)") | ||
backupDir = flag.String("backup-dir", "", "Backups dir") | ||
debug = flag.Bool("debug", false, "Toggle debug mode") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally there should be loglevel instead of just debug
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those were added in the latest commit.
Disable stack traces for warn level, allow setting minimum log level and disabling emitting source code line numbers in the log. Additional YAML configuration parameters for the backup and command-line for the restore to customize logging. As a collaterial to refining log messages, set the String() output of TableBackup to show unsanitized string representation, avoiding extra quotes in the logs (so far this function is only used for logging). Some minor stylistic changes around previously ignored errors in printf in the files touched by other changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found some fixes!
P.S. share your ideas, feedbacks or issues with us at https://github.com/fixmie/feedback (this message will be removed after the beta stage).
dbCfg pgx.ConnConfig, basebackupsQueue *queue.Queue, prom promexporter.PrometheusExporterInterface) (*TableBackup, error) { | ||
tableDir := utils.TableDir(oid) | ||
// New constructs a new TableBackup instance. | ||
func New(ctx context.Context, group *sync.WaitGroup, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi 👋 This is a bug, I'm planning to fix it tonight. Sorry for the inconvenience. In the future, if you can report these kind of bugs (https://github.com/fixmie/feedback), I could learn it earlier and would appreciate it :)
dbCfg pgx.ConnConfig, basebackupsQueue *queue.Queue, prom promexporter.PrometheusExporterInterface) (*TableBackup, error) { | ||
tableDir := utils.TableDir(oid) | ||
// New constructs a new TableBackup instance. | ||
func New(ctx context.Context, group *sync.WaitGroup, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi 👋 This is a bug, I'm planning to fix it tonight. Sorry for the inconvenience. In the future, if you can report these kind of bugs (https://github.com/fixmie/feedback), I could learn it earlier and would appreciate it :)
Naive approaches of putting them in a slice or a map before printing haven't produced either shorter or more readable code, partially because incapsulating a check into a ternary-operator like function is not possible with go, as the language evaluations function arguments at call time. We may revisit this code one day to fetch the description from the Config struct using reflection and to separate validation of arguments from the actual printing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found some fixes!
P.S. share your ideas, feedbacks or issues with us at https://github.com/fixmie/feedback (this message will be removed after the beta stage).
Closed in favor of #50 |
Having a structure in the logs helps to highlight important data (like errors or LSNs), especially when importing to the external log storage.
Another long-standing addition is debug levels, allowing to distinguish between debug and info messages.
This implementation wraps zap around our own logger module. The module's does not define an interface, as there seem no need for it, although it would be trivial to add when necessary (i.e. if we consider plugging another logger or use the same set of functions to pass structured error messages up to the calling functions).
The code is in shape for review, although the actual result may require some fine-tuning on the zap configuration, i.e. removing additional references to the code for the Warn messages. That can be done in an upcoming PR once we settle this one.
The current debug output is quite noisy, particularly because it shows details for every message received. Perhaps it makes sense to have a tri-state of debug/development/production, where development would be a default way to run during development and debug would be useful to diagnose particular issues? The changes also introduce a debug option for the backup and restore, which is off by default (so the default output is terse and is sent as a json, which the debug outputs plain text enriched by parameters).
Another question is how to deal with errors passed upstream that are composed mostly with
fmt.Errorf("some error %v when processing the message at LSN %v", err, lsn)
and would be displayed differently from the formatted output of"some error when processing the message", {error: "error", "LSN": lsn}
emitted by logger/zap. I believe we can settle this in a new pull-request to avoid making this one excessively long.