-
Notifications
You must be signed in to change notification settings - Fork 27
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
feat: rewrite output #260
feat: rewrite output #260
Conversation
ff0a3df
to
36aea06
Compare
4f035b8
to
26aa2fe
Compare
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.
Some feedback to give you things to think about.
internal/run/outputer.go
Outdated
var _ ui.Outputer = (*Output)(nil) | ||
|
||
func (o *Output) Display(outputable ui.Outputable) { | ||
if o.printer.Interactive && o.logToFile { |
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 find logToFile
confusing here. I would have expected logToFile
to control whether or not Log
is called, not whether or not a message is printed to (what looks like) stdout
internal/ui/output.go
Outdated
type Outputer interface { | ||
Display(outputable Outputable) | ||
Logger() *slog.Logger | ||
Printer() *Printer |
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.
Is this used anywhere? I might be missing something but I can't see anywhere that calls Printer()
other than one place that initialises a new outputer, passing in the parent Printer()
internal/ui/printer.go
Outdated
type Printer struct { | ||
Writer io.Writer | ||
ErrWriter io.Writer | ||
Interactive bool |
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.
Interactive
is only used by Outputer
implementations, so maybe it belongs in there rather than in the Printer
internal/run/outputer.go
Outdated
"github.com/form3tech-oss/f1/v2/internal/ui" | ||
) | ||
|
||
type Output struct { |
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.
Do you need a new implementation of Output
? Isn't this the same as ui.Output
if you move logToFile
into there and default it to true?
return &ConsoleOutput{printer: printer} | ||
} | ||
|
||
func NewDiscardOutput() *Output { |
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 found this name confusing because it only discards logs, not interactive output. I think it should also discard the Printer
output, or rename it to make its behaviour clearer.
internal/ui/output.go
Outdated
return NewOutput(logger, printer) | ||
} | ||
|
||
func NewOutput(logger *slog.Logger, printer *Printer) *Output { |
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.
A minor thing, but I'd move NewOutput
closer to the declaration of the type and it's methods. At the moment the definition of ConsoleOnlyOutput
and DiscardOutput
in the middle make this less obvious when scanning the code.
internal/ui/output.go
Outdated
return o.printer | ||
} | ||
|
||
func NewConoleOnlyOutput() *ConsoleOutput { |
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.
func NewConoleOnlyOutput() *ConsoleOutput { | |
func NewConsoleOnlyOutput() *ConsoleOutput { |
internal/ui/output.go
Outdated
} | ||
|
||
func (o *ConsoleOutput) Logger() *slog.Logger { | ||
return 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.
This is unsafe because places using the logger aren't doing a nil check first. In practice you're only using the console only logger to list the scenarios so it doesn't also log, but in that case I'd suggest that this isn't really an Outputer
. Why not just print to stdout from scenarios_cmd
if that's all it's ever going to do?
internal/ui/messages.go
Outdated
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.
In almost every case (except InteractiveMessage
) these types just call through to the underlying printer or logger. This seems more complicated that is necessary. The whole interaction between Outputable
, Outputer
, and Printer
seems wrong to me. The flow is roughly:
Create an Outputable
(some kind of message)
Send it to an Outputer
The Outputer
asks its Printer
whether it should print and log, or just log
It then calls a method on the Outputable
, passing in the Printer
or Logger
The Outputable
mostly does nothing other than call a corresponding method on the Printer
or Logger
(except some extra formatting for errors, or (unusually) some logic for interactive messages to not output anything)
I wonder if you can move most of this into the Outputer
. It would have enough config to know where to send messages (without having to ask the Printer
), and could provide an interface so that the caller could say Error
, Info
, ... InteractiveOnly
, LogOnly
. I've only half thought this through, but I'm not convinced you need this many levels of abstraction here.
This is a complete rewrite of the f1 UI and text output across the package. With the goal to natively support structured logging using the Go standard library log/slog package. New features: - Support JSON Logging with LOG_FORMAT=json - Support log levels with LOG_LEVEL - Automatically output structured logs when f1 is run from non-interactive shells. No longer showing term escapes in log aggregators when running in a container. - Automatically output structured logs when --verbose option is enabled so that human readable and structured logs are no longer interwoven in the same output. - Use log/slog as logging back-end with a logrus backwards compatibility layer. The logrus logger exposed has a custom handler defined that translates log entries to slog. Deprecation: - Deprecate: --verbose-fail option. It no longer has an effect. Typical f1 use case is running unattended. Prefer clearly defined logging behavior to conditional based on scenario success. - Deprecate: t.Logger() that returns a logrus.Logger it will be removed in future versions. Changes: - Default log output to stdout instead of stderr - Add a 4 character random component to log file names to avoid duplicate files created in the same second ( from f1-scenario-YYYY-MM-DD_HH-MM-SS.log to f1-scenario-XXXX-YYYY-MM-DD_HH-MM-SS.log) Changes in behavior: - No changes for interactive shells - When run from non-interactive shells - default to structured logs - When verbose is enabled, make sure the full output is structured log regardless of interactive mode. A mix of structured and non-structured is difficult to manage for log collection systems.
71dd9c1
to
3869ffa
Compare
3869ffa
to
bfd6047
Compare
Description
This is a complete rewrite of the f1 UI and text output across the package. With the goal to natively support structured logging using the Go standard library
log/slog
package.New Features
LOG_FORMAT=json
LOG_LEVEL
--verbose
option is enabled so that human readable and structured logs are no longer interwoven in the same output.log/slog
as logging back-end with alogrus
backwards compatibility layer. The logrus logger exposed has a custom handler defined that translates log entries to slog.Deprecation
--verbose-fail
option. It no longer has an effect. Typical f1 use case is running unattended. Prefer clearly defined logging behavior to conditional based on scenario success.t.Logger()
that returns alogrus.Logger
it will be removed in future versions.Changes
stdout
instead ofstderr
f1-scenario-YYYY-MM-DD_HH-MM-SS.log
tof1-scenario-XXXX-YYYY-MM-DD_HH-MM-SS.log
)Changes in behavior
No changes for interactive shells
When run from non-interactive shells - default to structured logs
When
verbose
is enabled, make sure the full output is structured log regardless of interactive mode.A mix of structured and non-structured is difficult to manage for log collection systems.