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

Improve testing logger #5346

Merged
merged 9 commits into from
Sep 6, 2024
Merged

Conversation

AndersonQ
Copy link
Member

@AndersonQ AndersonQ commented Aug 23, 2024

What does this PR do?

Adds a PrettyPrintf method to ObservedLogs to logger.NewTesting

Why is it important?

The testing logger is helpful, however on every failed test it's necessary to manually format the logs before printing. That leads to several tests just printing the logs as they are which isn't the best for reading them and investigating why the test failed.

The new PrettyPrintf method provides an easy way to pretty print the logs

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • [ ] I have made corresponding changes to the documentation
  • [ ] I have made corresponding change to the default configuration files
  • I have added tests that prove my fix is effective or that my feature works
  • [ ] I have added an entry in ./changelog/fragments using the changelog tool
  • [ ] I have added an integration test or an E2E test

Disruptive User Impact

None

How to test this PR locally

run the test TestObservedLogs_PrettyPrintf

Related issues

Questions to ask yourself

  • How are we going to support this in production?
  • How are we going to measure its adoption?
  • How are we going to debug this?
  • What are the metrics I should take care of?
  • ...

@AndersonQ AndersonQ added Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team skip-changelog Testing backport-8.15 Automated backport to the 8.15 branch with mergify labels Aug 23, 2024
@AndersonQ AndersonQ self-assigned this Aug 23, 2024
Copy link
Contributor

mergify bot commented Aug 23, 2024

⚠️ The sha of the head commit of this PR conflicts with #5345. Mergify cannot evaluate rules on this PR. ⚠️

@AndersonQ AndersonQ marked this pull request as ready for review August 23, 2024 08:23
@AndersonQ AndersonQ requested a review from a team as a code owner August 23, 2024 08:23
@elasticmachine
Copy link
Contributor

Pinging @elastic/elastic-agent-control-plane (Team:Elastic-Agent-Control-Plane)

@AndersonQ AndersonQ added backport-8.15 Automated backport to the 8.15 branch with mergify and removed backport-8.15 Automated backport to the 8.15 branch with mergify labels Aug 23, 2024
Copy link
Contributor

mergify bot commented Aug 23, 2024

⚠️ The sha of the head commit of this PR conflicts with #5345. Mergify cannot evaluate rules on this PR. ⚠️

log, obs := logger.NewTesting("watcher")
defer func() {
if t.Failed() {
obs.PrettyPrintf(t.Log)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can implement String() to ObserverLogs? Then the usage becomes t.Log(obs)

Copy link
Member Author

Choose a reason for hiding this comment

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

What I don't like on this idea is that ObservedLogs contains all logs, so a String() method would have to decide how to deal with the multi-line nature of it and it'd not be possible to print one log at a time.

Copy link
Member

Choose a reason for hiding this comment

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

You are right that String() would make this a bit more limited in use, but ObserverLogs has filters you can apply to limit the amount of logs that would be returned (see Filter* methods). I understand the limitation regarding multiline logs.

Another consideration is that PrettyPrintf calls the callback function with a string, so it is kinda like an inversion of control, wouldn't it read better to have t.Log(obs.Pretty())? This way it plays better with Logf, FatalF or any other Printer utility you might use. I believe it gives more control to the caller.

Copy link
Member

Choose a reason for hiding this comment

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

+1 for having String() or Pretty() implemented for ObservedLogs or even for a single LoggedEntry as it would be easier and more flexible

Copy link
Contributor

Choose a reason for hiding this comment

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

My reservation about adding ObserverLogs.String() is that it would write all logs into one big string, which can use a lot of memory when there's a lot of logs. I like the approach of dumping the logs to strings one at a time.

On the other hand, my concern about the ObserverLogs.TakeAndPrettyPrint() method is that causes code like https://github.com/elastic/elastic-agent/pull/5346/files#diff-1881a0cf2ec8461140501dbbb913d9d91b93f00572c2e13977a6fca09b5ba92bR185:

obs = &logger.ObservedLogs{ObservedLogs: obs.Filter(func(entry observer.LoggedEntry) bool {

where you need to encapsulate the result of obs.Filter in a new logger.ObservedLogs. This notation is a bit verbose.

Another approach would be to have a function like printTestLogs(t *testing.T, logs []observer.LoggedEntry) like the one already defined here:

func printLogs(t *testing.T, logs []observer.LoggedEntry) {
	t.Helper()
	for _, entry := range logs {
		t.Logf("[%s] %s", entry.Level, entry.Message) // add dumping entry.ContextMap()
	}
}

This function could then be used like this:

	defer func() {
		if t.Failed() {
			printTestLogs(t, obs.TakeAll())
		}
	}()

One benefit of ObserverLogs.TakeAndPrettyPrint over printLogs is that TakeAndPrettyPrint accepts a print function, giving the caller the choice where the logs are printed. But don't we always want to use t.Log in the tests?

Copy link
Member

Choose a reason for hiding this comment

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

I still think using t.Log(obs.Pretty()) or even obs.String() would be a more idiomatic approach. If performance becomes a concern, we can always use strings.Builder.

I don’t want to prolong this discussion as I don't have strong arguments to favor one approach over the other; it seems more like a matter of preference. What do you think, @AndersonQ?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd agree with you if it were a straight forward thing, but it isn't because the type isn't on our code.

It'd require to bring back the new type, which makes using the original methods a bit annoying. And again, ObserverLogs holds several lines of logs which I rather not put on a single string. Adding the method on LogEntry would mean more new types. So I kept the original idea, something to pretty print ObserverLogs but with a simpler implementation

@AndersonQ AndersonQ force-pushed the improve-logger-testing branch from 680661a to 1220dd6 Compare August 23, 2024 13:37
Copy link
Contributor

mergify bot commented Aug 26, 2024

This pull request is now in conflicts. Could you fix it? 🙏
To fixup this pull request, you can check out it locally. See documentation: https://help.github.com/articles/checking-out-pull-requests-locally/

git fetch upstream
git checkout -b improve-logger-testing upstream/improve-logger-testing
git merge upstream/main
git push upstream improve-logger-testing

@@ -133,12 +134,11 @@ LOOP:
// block on connection, don't retry connection, and fail on temp dial errors
// always a local connection it should connect quickly so the timeout is only 1 second
connectCtx, connectCancel := context.WithTimeout(ctx, 1*time.Second)
//nolint:staticcheck // requires changing client signature
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this nolint clause being removed?

Copy link
Member

@pchila pchila left a comment

Choose a reason for hiding this comment

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

Just a comment on the pretty-printing already pointed out by @mauri870
The removal of nolint directive now introduces a lint failure, I would rather we fix the problem if possible, or maybe restore the nolint if it's too much work extraneous to this PR and fix it in a follow-up PR

@AndersonQ AndersonQ force-pushed the improve-logger-testing branch from d807fd4 to 0f8abc5 Compare September 3, 2024 11:55
@AndersonQ
Copy link
Member Author

Just a comment on the pretty-printing already pointed out by @mauri870 The removal of nolint directive now introduces a lint failure, I would rather we fix the problem if possible, or maybe restore the nolint if it's too much work extraneous to this PR and fix it in a follow-up PR

it was removed by mistake, it's back now

@AndersonQ AndersonQ force-pushed the improve-logger-testing branch 2 times, most recently from 21e5c3d to 52b96dd Compare September 3, 2024 12:21
@AndersonQ
Copy link
Member Author

@mauri870, @pchila, @andrzej-stencel how about now? I think it's a better compromise, following the idea @andrzej-stencel suggested.
Sorry about the package move, it changed a lot of files, but I believe it's better to put a cleat boundary between production code and code to support test

logger.Error("an error message")
logger.Errorw("an error message with keys", "key1", "value1")

pintFn := func(a ...any) { fmt.Println(a...) }
Copy link
Member

Choose a reason for hiding this comment

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

s/pintFn/printFn/

Copy link
Contributor

Choose a reason for hiding this comment

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

A pint is always good! 🍻

Copy link
Member

Choose a reason for hiding this comment

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

🍻

move logger.NewTesting to loggertesting.New
add loggertesting.PrintObservedLogs to pretty print observer.ObservedLogs
@AndersonQ AndersonQ force-pushed the improve-logger-testing branch from 52b96dd to d49f013 Compare September 3, 2024 12:39
@AndersonQ AndersonQ requested a review from mauri870 September 5, 2024 09:17
// one at a time, printFn. It calls `observer.ObservedLogs.TakeAll`,
// therefore, after calling it, the ObservedLogs will be empty.
func PrintObservedLogs(obs *observer.ObservedLogs, printFn func(a ...any)) {
rawLogs := obs.TakeAll()
Copy link
Contributor

@andrzej-stencel andrzej-stencel Sep 5, 2024

Choose a reason for hiding this comment

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

[suggestion/non-blocking] TakeAll modifies the state of obs, so ideally I would keep it out of this function. I understand that this makes the call to PrintObservedLogs a bit longer, user needs to pass obs.TakeAll() instead of just obs as the parameter. I'm leaving it up to you Anderson.

@AndersonQ AndersonQ enabled auto-merge (squash) September 5, 2024 11:23
Copy link
Member

@pchila pchila left a comment

Choose a reason for hiding this comment

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

LGTM
I have more of a curiosity: why was the test logger moved into its own package? It's not wrong but I was wondering what was the reason behind that...

@AndersonQ AndersonQ merged commit 0126540 into elastic:main Sep 6, 2024
13 checks passed
mergify bot pushed a commit that referenced this pull request Sep 6, 2024
* add loggertesting package

move logger.NewTesting to loggertesting.New
add loggertesting.PrintObservedLogs to pretty print logs from a observer.ObservedLogs

(cherry picked from commit 0126540)

# Conflicts:
#	internal/pkg/agent/application/upgrade/watcher_test.go
#	pkg/component/runtime/manager_fake_input_test.go
@AndersonQ AndersonQ deleted the improve-logger-testing branch September 6, 2024 06:35
mergify bot pushed a commit that referenced this pull request Sep 6, 2024
* add loggertesting package

move logger.NewTesting to loggertesting.New
add loggertesting.PrintObservedLogs to pretty print logs from a observer.ObservedLogs

(cherry picked from commit 0126540)
AndersonQ added a commit that referenced this pull request Sep 10, 2024
* improve testing logger (#5346)

* add loggertesting package

move logger.NewTesting to loggertesting.New
add loggertesting.PrintObservedLogs to pretty print logs from a observer.ObservedLogs

(cherry picked from commit 0126540)
---------

Co-authored-by: Anderson Queiroz <anderson.queiroz@elastic.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-8.15 Automated backport to the 8.15 branch with mergify skip-changelog Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team Testing
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants