Skip to content
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

Added support for local files to --log-output #2285

Merged
merged 1 commit into from
Feb 9, 2022
Merged

Added support for local files to --log-output #2285

merged 1 commit into from
Feb 9, 2022

Conversation

alyakimenko
Copy link
Contributor

@alyakimenko alyakimenko commented Dec 4, 2021

Added file hook for logger.

Examples of usage:

k6 run --log-output file=./k6.log --logformat json ./examples/stages.js

With a defined log level:

k6 run --log-output file=./k6.log,level=info --logformat json ./examples/stages.js

Closes #2249

@CLAassistant
Copy link

CLAassistant commented Dec 4, 2021

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @alyakimenko,
thanks for your contribution 🙏 🎉

Some request changes in the comments. Also, did you try to use the logrus' builtin writer?

cmd/root.go Outdated Show resolved Hide resolved
log/levels.go Outdated Show resolved Hide resolved
log/levels_test.go Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Dec 6, 2021

Codecov Report

Merging #2285 (a06dfbd) into master (42f2e60) will increase coverage by 1.65%.
The diff coverage is 75.67%.

❗ Current head a06dfbd differs from pull request most recent head ec328a7. Consider uploading reports for the commit ec328a7 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2285      +/-   ##
==========================================
+ Coverage   72.71%   74.37%   +1.65%     
==========================================
  Files         184      188       +4     
  Lines       14571    15230     +659     
==========================================
+ Hits        10596    11328     +732     
+ Misses       3333     3200     -133     
- Partials      642      702      +60     
Flag Coverage Δ
ubuntu ?
windows 74.37% <75.67%> (+2.00%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
api/v1/client/client.go 0.00% <0.00%> (ø)
api/v1/client/metrics.go 0.00% <0.00%> (ø)
api/v1/client/status.go 0.00% <0.00%> (ø)
api/v1/group.go 31.25% <ø> (-57.82%) ⬇️
api/v1/metric.go 48.00% <ø> (-52.00%) ⬇️
api/v1/routes.go 55.31% <ø> (ø)
api/v1/status.go 75.00% <ø> (-25.00%) ⬇️
cmd/inspect.go 0.00% <0.00%> (ø)
cmd/login_cloud.go 0.00% <0.00%> (ø)
cmd/login_influxdb.go 0.00% <0.00%> (ø)
... and 137 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0dda328...ec328a7. Read the comment docs.

@codebien codebien self-requested a review December 10, 2021 18:15
Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these changes, this version looks very close.

log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Show resolved Hide resolved
log/tokenizer.go Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
log/file_test.go Outdated Show resolved Hide resolved
log/file_test.go Outdated Show resolved Hide resolved
log/file_test.go Outdated Show resolved Hide resolved
log/file_test.go Outdated Show resolved Hide resolved
log/file_test.go Outdated Show resolved Hide resolved
log/tokenizer_test.go Outdated Show resolved Hide resolved
Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly commented in the previous suggestion #2285 (comment)

Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👏, let's wait for @mstoykov review

@codebien
Copy link
Contributor

@alyakimenko in the meanwhile you could consider rebasing the git history to simplify the next review.

@na-- na-- requested a review from oleiade January 5, 2022 13:34
@na-- na-- added this to the v0.36.0 milestone Jan 5, 2022
Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM in general:clap: :tada: ! But I have some considerations around defaults, for which you will find comments

cmd/root.go Outdated
Comment on lines 297 to 300
if c.fallbackLogger == nil {
return nil, fmt.Errorf("fallback logger is nil")
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this added because in some test this was true?

I think this is currently not possible to happen in the real code, so I am against there being an error for it ;) (and if it can happen here it needs to be added to the loki case as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mstoykov This was mostly suggested by me. It should make easier the detection of eventual nil logger in any possible future change. It would be difficult to detect an issue just by testing and/or running some examples because it would be used only in case of an error of the main writer. And in the case, the panic would happen we are losing the main error generated by the writer. That is my thought about why I requested to have it. WDYT?

log/file.go Outdated
hook := &fileHook{
fallbackLogger: fallbackLogger,
ch: make(chan []byte),
path: defaultLogfilePath,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs a defaultLogFilePath especially as it will just log somewhere on hte system. Additionally the current one is Posix specific and likely will have problems on windows.

I do propose that for a first release we :

  1. don't have a default one
  2. if we decide to have one, it to actually be in the current directory, but I think this needs to be discussed more so let's open an issue instead ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your proposal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got rid of the default log file path

log/file.go Outdated
Comment on lines 57 to 62
if fallbackLogger != nil {
hook.fallbackLogger = fallbackLogger
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again afaik there is no case (outside of some tests I guess) where fallbackLogger will be nil so if it's nil it's a much bigger issue and IMO worthy of a panic.

log/file.go Outdated
Comment on lines 111 to 106
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create dir for logfile %s: %w", h.path, err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of feel like we shouldn't just be creating directories for users if they don't exist, I am much more for erroring out and letting the user create the directory with the permissions they want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced the mkdir logic with a check for the existence of a directory.

log/file.go Outdated
) (logrus.Hook, error) {
hook := &fileHook{
fallbackLogger: fallbackLogger,
ch: make(chan []byte),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be buffered. As it stands we won't block the first Log that gets fired but the one after that will block on the channel(given that it happens fast enough). IMO we either drop the channel or we have some buffering so at least for some burst it won't block at all. I don't have a particular value, but likely something like 100 will be enough, adding it as a configuration option might be a good idea but can also be left for a following PR and just have it as a constant for now.

WDYT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it buffered and defined fileHookDataChDefaultSize constant with a value of 100.

return fmt.Errorf("failed to open logfile %s: %w", h.path, err)
}

h.w = file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have this buffered? I remember that there were some benchmarks showing considerable speedup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it buffered. But I kept the underlying io.WriteCloser in order to have the ability to close a file on exit.

@codebien I also returned the loggerStopped channel to properly flush when work is done.

@mstoykov mstoykov requested a review from codebien January 6, 2022 12:49
Copy link
Member

@oleiade oleiade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🍾

Great work overall! I've left a bunch of recommendations and proposals. Let me know what you think. I don't expect to have further comments after that : bullettrain_front:

log/file.go Outdated
)

// defaultLogfilePath is a default logfile path.
const defaultLogfilePath = "/tmp/k6run.log"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't expect this to work on anything else than a Linux system. I would recommend to use something along the lines of: const defaultLogFilePath = filepath.Join(os.TempDir(), "k6run.log") instead 😸

Also, in order to be good citizens on our users OS', I would recommend to write the log files in a k6 subfolder too. To do so, I would tend to split things a bit for clarity of intent; something along those lines would do:

const defaultLogFolder = filepath.Join(os.TempDir(), "k6")
const defaultLogFilePath = filepath.Join(defaultLogFolder, "k6run.log")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#2285 (comment) I'm for the plan suggested there but this can be a valid alternative to discuss in the issue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave it up to you @alyakimenko and @mstoykov to figure out what makes most sense 😸

log/file.go Outdated

// createOpenFile opens logfile by provided path,
// it will create a file if it doesn't exist.
func (h *fileHook) createOpenFile() error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the name of this function quite unconventional, as it groups two operations that are exclusive: creating and opening a file. I would recommend to modify the signature to openFile(create bool) instead. Then the body could produce the proper opening flags by doing something along the lines of:

flags := os.O_WRONLY | os.O_APPEND
if create {
  flags = flags | os.O_CREATE
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the name of this function quite unconventional

IMHO, it would make sense in the case it would be an exported method so a public API for this struct. But it's something internal for wrapping the OR logic. If we delegate the input again then better to have directly the entire logic in the caller, otherwise we have just one more layer of logic where we could generate a failure.

Maybe we can improve the name? Just a suggestion, I don't have one better at the moment.

Suggested change
func (h *fileHook) createOpenFile() error {
func (h *fileHook) openOrCreateFile() error {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've given this a bit more thought and, given the choice, I do think I'd prefer to get rid of this helper function all in all.

If we were to keep the function, I find that the naming is not the issue. Whether you open, or create the file ends up in the same underlying standard library call, and system call. It's always technically opening a file, with, in one scenario the instruction to not fail if it doesn't exist, but have the OS create it instead. But I'm really, REALLY, nitpicking on this, and I really don't want it to be a blocker! (from my point of view, it's not)

log/file.go Show resolved Hide resolved
return fmt.Errorf("failed to open logfile %s: %w", h.path, err)
}

h.w = file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in line with Mihail on this and would recommend using bufio.NewWriter(f) here instead.

log/file.go Outdated Show resolved Hide resolved
log/file_test.go Outdated
Comment on lines 155 to 167
logger.Info("example log line")

cancel()
<-hook.dataCh

assert.Contains(t, buffer.String(), "example log line")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test now fails. This (IMO) is because we now have a buffered channel between logger.Info -> ... -> fileHook.Fire and the actual place it's logged (the loop).

With a buffered channel (as long as there is space) the send (in Fire) can just put it in the channel and continue. With an unbuffered channel (as before) the send needs to wait for the actual receive on the channel to happen, before it can continue.

The latter will always manage to enter the select and write the message, the first won't. I don't think we care about losing the last few messages in some cases, as in the real case that should not be possible. But we still need this test to pass, so please add a small sleep and do -race -count 10 a few times locally to be sure it's enough. I would wager anything above 1-10ms should be sufficient

Copy link
Contributor Author

@alyakimenko alyakimenko Jan 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the timeout, it works as expected. Thanks!
There are a few complaints from linter:

log/file.go:21:1    stylecheck  ST1000: at least one file in a package should have a package comment
log/levels.go:21:1  stylecheck  ST1000: at least one file in a package should have a package comment

Should I worry about them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alyakimenko Yes, please. The linter must be fixed for getting the PR merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codebien
Package log implements various logrus hooks.
Do you find the description suitable for the package?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alyakimenko it's what it does so it's fine for me, but other reviewers could have different opinions

@na-- na-- modified the milestones: v0.36.0, v0.37.0 Jan 12, 2022
Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @alyakimenko, thanks for the improvements. I spot few suggestions, also the linter should be fixed 🙏

log/file_test.go Outdated
Comment on lines 160 to 163
time.Sleep(10 * time.Millisecond)

cancel()
<-hook.loggerStoppedCh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
time.Sleep(10 * time.Millisecond)
cancel()
<-hook.loggerStoppedCh
cancel()
<-nc.closed

I would prefer to remove the time for asserting concurrency, I think we can benefit from the Close method of the WriteCloser in this case.

type nopCloser struct {
	io.Writer
	closed chan struct{}
}

func (nc *nopCloser) Close() error {
	nc.closed <- struct{}{}
	return nil
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good suggestion. I applied it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like, It didn't help :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you like it if I brought the timeout back?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'm sorry this was my fault, the problem is with the Write sync order. You should restore the timeout. 😔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I restored the timeout.

log/file.go Outdated Show resolved Hide resolved
Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry few more things

log/file.go Outdated Show resolved Hide resolved
log/file.go Outdated Show resolved Hide resolved
Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @alyakimenko, due to some refactorings you now will need to rebase this PR on master, sorry for the inconvience :(

@alyakimenko
Copy link
Contributor Author

@mstoykov Hello! I've finished rebasing

@mstoykov
Copy link
Contributor

@alyakimenko I think you didn't pull the upstream changes before rebasing :(

@alyakimenko
Copy link
Contributor Author

@mstoykov Sorry, it's my bad. I fixed it.

codebien
codebien previously approved these changes Jan 31, 2022
Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great job 👏 . Apart from the removing of the defer I think we can merge this.

Sorry that this took so long and is still not over :(

log/file.go Outdated
loglines := make(chan []byte, fileHookBufferSize)

go func() {
defer close(loglines)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closing this can get us into panic as Fire is the one writing. There also isn't really any reason to do this - loop is the only place that reads from it, and it's exiting here so we won't stop anything closing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. Thank you. I removed the defer.

Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alyakimenko going to merge 🎉 thanks for your effort 🙏

@codebien codebien merged commit 4c0558a into grafana:master Feb 9, 2022
@alyakimenko alyakimenko deleted the feature/log-output-local-file branch February 9, 2022 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for local files to --log-output
7 participants