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

[exporter/kafka] Add factory options for custom metric and log marshalers #16100

Closed
wants to merge 0 commits into from
Closed

[exporter/kafka] Add factory options for custom metric and log marshalers #16100

wants to merge 0 commits into from

Conversation

bgranetzke
Copy link
Contributor

Description:
Adding ExportFactory options to add custom log/metric marshalers (similar to existing: WithTracesMarshalers)

Link to tracking Issue: 14514

Testing:
Tests were added for the new options as well as adapting the previous test for WithTracesMarshalers to the new method.

@TylerHelmuth
Copy link
Member

pinging @pavolloffay @MovieStoreGuy as code owners

@bogdandrutu
Copy link
Member

Need to fix lint/tests by running make gotidy

@bgranetzke bgranetzke requested a review from dmitryax as a code owner November 7, 2022 20:04
@bgranetzke bgranetzke requested a review from a team November 7, 2022 20:04
Copy link
Contributor

@MovieStoreGuy MovieStoreGuy 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 few things I would like addressed just to keep the practices consistent within the component.

Comment on lines 281 to 305
type customTracesMarshaler struct {
}

var _ TracesMarshaler = (*customTracesMarshaler)(nil)

func (c customTracesMarshaler) Marshal(_ ptrace.Traces, topic string) ([]*sarama.ProducerMessage, error) {
panic("implement me")
}

func (c customTracesMarshaler) Encoding() string {
return "custom"
}

type customMetricsMarshaler struct {
}

var _ MetricsMarshaler = (*customMetricsMarshaler)(nil)

func (c customMetricsMarshaler) Marshal(_ pmetric.Metrics, topic string) ([]*sarama.ProducerMessage, error) {
panic("implement me")
}

func (c customMetricsMarshaler) Encoding() string {
return "custom"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please the declaration above the tests?

I am not a big fan of forward declaration of types and I try keep it inline with how it would be presented with godoc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To make sure I'm clear, do you want me to:

  1. move all the declarations to the top of the file
  2. move all the declarations before the first marshaler test
  3. move each custom marshaler declaration ahead of its respective test

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to give a bit of context, I prefer to keep code into logical groupings similar as you would in Java. A class or type per file and an associated test. This mostly help me group logic and behaviour in one spot so that it is really simple for me to glance and know if this is the code I want or not.

To be clear, I not against having shared functionality in file since it can make sense when the logic / types are tightly coupled

What I like to avoid is referencing something before it has been defined, for example:

// foo_test.go
func TestMethod(...) {
     mocker := NewMock(...)
}

// Additional lines here

func NewMock(...) Mock {

}

type Mock struct {}

So I how I would arrange this is:

// foo_test.go
type Mock struct {}

func NewMock(...) Mock

func TestMethod(...) {
    mocker := NewMock(..)
}

Structuring like above has the added benefit that as I read the file, I can understand what the store state is, what things it depends on, and understand how you're expecting others to use it.

Copy link
Contributor

Choose a reason for hiding this comment

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

To make sure I'm clear, do you want me to:

  1. move all the declarations to the top of the file
  2. move all the declarations before the first marshaler test
  3. move each custom marshaler declaration ahead of its respective test

Option 2 from your list, I hope my explanation above helps convey what I mean a bit better and why I would like it this way. It is honestly so I don't have to jump around the file to double check how things work or should work.

@@ -173,6 +174,7 @@ func newMetricsExporter(config Config, set component.ExporterCreateSettings, mar
if marshaler == nil {
return nil, errUnrecognizedEncoding
}
set.Logger.Info(fmt.Sprintf(logMarshalerUsedMsg, marshaler))
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the intent of these log lines?

I would encourage the use of structured logging instead of making a called to fmt.Sprintf

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
set.Logger.Info(fmt.Sprintf(logMarshalerUsedMsg, marshaler))
set.Logger.Debug("Configured marshaller", zap.String("encoding", config.Encoding))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The strategy for the test is to validate the message contents via a zap.Hook. Unfortunately, the fields of the message are not provided in the zapcore.Entry provided to the hook.

Copy link
Member

Choose a reason for hiding this comment

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

I think @MovieStoreGuy is right. Are you sure about "Unfortunately, the fields of the message are not provided in the zapcore.Entry provided to the hook.", does seem like a bug in zap, and hard to believe it.

Copy link
Contributor

@MovieStoreGuy MovieStoreGuy Nov 11, 2022

Choose a reason for hiding this comment

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

Oh interesting, I wasn't aware that logs lines were covered as part of the test. It didn't register that was how it was being tested.

From my perspective, I don't see too much value in validating that log lines happened since it often leads into an antipattern of checking if logs exist to see if some functionality has been reached. There should be other state to be able to inspect to validate that it has indeed done what it says it has.

Furthermore, These are more just feedback to the user who provided the configuration. They may or may not find them useful.

Copy link
Contributor

Choose a reason for hiding this comment

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

As per my comment on structured logging, the reason I'm firm for this is that it makes search logs more efficient in something like loki, splunk, tail -f | grep, pick your poison.

Keeping the message field static means that I can take that text and either include or exclude those lines really easily and those systems can take advantage of their engines that can optimise on that static string. (From my understand, some use bloom filters to make filtering easier).

Finally, not that it is strictly important here but it does remove one additional allocation for the string which may not seem like much but it definitely adds up when you consider all the components and core service of the collector.

@@ -193,6 +195,7 @@ func newTracesExporter(config Config, set component.ExporterCreateSettings, mars
if marshaler == nil {
return nil, errUnrecognizedEncoding
}
set.Logger.Info(fmt.Sprintf(logMarshalerUsedMsg, marshaler))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

@@ -210,6 +213,7 @@ func newLogsExporter(config Config, set component.ExporterCreateSettings, marsha
if marshaler == nil {
return nil, errUnrecognizedEncoding
}
set.Logger.Info(fmt.Sprintf(logMarshalerUsedMsg, marshaler))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

@MovieStoreGuy
Copy link
Contributor

Hey @bgranetzke ,

This appears to be a duplicate of #13433, is that correct?

@bgranetzke
Copy link
Contributor Author

@MovieStoreGuy You are correct. The addition of the factory options are identical, but the testing approaches are different.

#13433 tried to verify custom marshalers by verifying message output. However, in order to capture the resultant messages, a new abstraction had to be added to the design...which you did not like.

The new approach validates successful injection of the custom marshalers by injecting a log hook into the zap.logger which travels with the exporterSettings. Also not ideal, but the only change to the existing design was the addition of the log message.

@TylerHelmuth
Copy link
Member

@bgranetzke did you have a bad merge? There are a lot of changed files now.

@bgranetzke
Copy link
Contributor Author

@TylerHelmuth maybe because I rebased against latest main this morning? My source branch still only shows 6 files changed. I try to keep it current with main, but that was probably a bad decision. Any suggestions beyond just recreating the branch?

@TylerHelmuth
Copy link
Member

@bgranetzke trying rebasing again from upstream/main or doing a merge from upstream/main

@MovieStoreGuy
Copy link
Contributor

@MovieStoreGuy You are correct. The addition of the factory options are identical, but the testing approaches are different.

Easy, I thought I was going mad thinking I had already reviewed this.

#13433 tried to verify custom marshalers by verifying message output. However, in order to capture the resultant messages, a new abstraction had to be added to the design...which you did not like.

Yes, sorry about this.
Unfortunately I had some family health issues that I needed to help with and unfortunately dropped the ball on the reviews I was doing.

In future, I would appreciate just pinging me again on the PR if that is okay?

The new approach validates successful injection of the custom marshalers by injecting a log hook into the zap.logger which travels with the exporterSettings. Also not ideal, but the only change to the existing design was the addition of the log message.

I think we are on the same page on it noting being ideal, however, I may have a solution for you.

})
}
func TestWithMetricsMarshalers(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Alright, to help make lives easier on how best to test this, what I suggest you do is the following:

func TestWithMarshalers(t *testing.T) {
	tests := []struct {
		name       string
		encoding   string
		marshalers []TracesMarshaler
		err        error
	}{
		{
			name:       "default_encoding",
			encoding:   defaultEncoding,
			marshalers: nil,
			err:        nil,
		},
		{
			name:       "custom_encoding",
			encoding:   "custom",
			marshalers: []TracesMarshaler{customMarshaler{}},
			err:        nil,
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			conf := createDefaultConfig().(*Config)
			// disable contacting broker
			conf.Metadata.Full = false
			conf.Encoding = tc.encoding

			f := NewFactory(WithTracesMarshalers(tc.marshalers...))
			exporter, err := f.CreateTracesExporter(
				context.Background(),
				componenttest.NewNopExporterCreateSettings(),
				conf,
			)
			assert.ErrorIs(t, err, tc.err, "Must match the expected error")
			if tc.err != nil {
				assert.Nil(t, exporter, "Must return nil value for invalid exporter")
				return
			}
			assert.NotNil(t, exporter, "Must return valid exporter when no error is returned")
		})
	}
}

Structuring the tests like this would mean that it is clear what it trying to validate and only the table value need to change and the rest can remain the same.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am so sorry that the existing testing doesn't make this easy, however, moving to the above will make it easier for you and those contributing make this component better :)

@github-actions
Copy link
Contributor

This PR was marked stale due to lack of activity. It will be closed in 14 days.

@github-actions github-actions bot added the Stale label Nov 26, 2022
@MovieStoreGuy
Copy link
Contributor

Hey @bgranetzke,

Are you still working on this?

@bgranetzke
Copy link
Contributor Author

@MovieStoreGuy Yes. I'd like to figure out how to finish this PR. Sorry for the delay. I'm paying the price for waiting so long with this current merge. After that I will incorporate your changes. I did figure out how to intercept the structured log fields, but it's a bit more effort than the hooks.

@bgranetzke bgranetzke closed this Nov 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants