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

allow tagFilter to use response data #2607

Merged

Conversation

lukas-c-wilhelm
Copy link
Contributor

@lukas-c-wilhelm lukas-c-wilhelm commented Sep 20, 2023

Fixes #2316

This change introduces a new filter tracingTagOnResponse that works just like the tracingTag filter, but is applied only after ther request has been processed. This allows using properties of the response (a header value) in the tag.

The use case for this is that users might want to consider an operation as failed even if it technically succeeds, e.g. because fallbacks were used. With this change, a response header can be sent, e.g. "X-Fallback": "true", and then the filter tracingTagOnResponse("error", "${response.header.X-Fallback}") would result in the span being marked as error.

Another use cases could be that metrics should be captured by use case, but the use case is not apparent from the request and instead only defined by the result of the request processing.

@lukas-c-wilhelm lukas-c-wilhelm force-pushed the tag-span-from-response branch 2 times, most recently from 2de97b1 to 2e2f52e Compare September 20, 2023 09:49
@lukas-c-wilhelm lukas-c-wilhelm marked this pull request as draft September 20, 2023 09:51
@AlexanderYastrebov
Copy link
Member

Looks like it attempts to fix #2316

This change introduces a new filter tracingTagOnResponse that works just like the tracingTag filter, but is applied only after ther request has been processed. This allows using properties of the response (a header value) in the tag.

The use case for this is that users might want to consider an operation as failed even if it technically succeeds, e.g. because fallbacks were used. With this change, a response header can be sent, e.g. `"X-Fallback": "true"`, and then the filter `tracingTag("error", "${response.header.X-Fallback}")` would result in the span being marked as error.

Another use cases could be that metrics should be captured by use case, but the use case is not apparent from the request and instead only defined by the result of the request processing.

Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>
In the test, the span tag was obtained before the response filter method was executed, resulting in no tags being found. This commit moves obtaining the tags to after the incovaction of the response filter method.

Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>
@lukas-c-wilhelm lukas-c-wilhelm marked this pull request as ready for review September 20, 2023 12:14
@szuecs
Copy link
Member

szuecs commented Sep 20, 2023

You also need to register the spec into the builtins https://github.com/zalando/skipper/blob/master/filters/builtin/builtin.go#L210 to make it available for use in skipper.

Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>

response
@lukas-c-wilhelm
Copy link
Contributor Author

@szuecs Thanks for your review. I addressed your comments.

@szuecs
Copy link
Member

szuecs commented Sep 21, 2023

Only one tiny nit and it's ready to merge, thanks

Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>
@AlexanderYastrebov
Copy link
Member

Looks ok, couple of ideas.

Since the filter matches 99% with existing you could also add a flag like here https://github.com/zalando/skipper/blob/master/filters/diag/histogram.go

$ grep -o -E '".*esponse.*"' filters/filters.go
"modResponseHeader"
"setResponseHeader"
"appendResponseHeader"
"dropResponseHeader"
"setContextResponseHeader"
"appendContextResponseHeader"
"copyResponseHeader"
"uniformResponseLatency"
"normalResponseLatency"
"histogramResponseLatency"
"dropResponseCookie"
"responseCookie"
"opaServeResponse"

$ grep -o -i -E '".*from.*"' filters/filters.go | sort
"setDynamicBackendHostFromHeader"
"setDynamicBackendSchemeFromHeader"
"setDynamicBackendUrlFromHeader"

Should we name it tracingTagFromResponse maybe?

@szuecs
Copy link
Member

szuecs commented Sep 21, 2023

@lukas-c-wilhelm
Copy link
Contributor Author

@AlexanderYastrebov @szuecs I'll try to implement it like that.

This commit refactors the tracingTagOnResponse filter to use the same class as the tracingTag filter. It follows the same pattern as histogram.go.

Also, the filter is renamed to tracingTagFromResponse and the documentation simplified.

Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>
@szuecs
Copy link
Member

szuecs commented Sep 21, 2023

👍

})
}
}

func TestTagFilterIgnoresResponse(t *testing.T) {
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 this could be a test case in the TestTracingTag

	{
		"tag ignores response",
		NewTag(),
		"${response.header.X-Fallback}",
		&filtertest.Context{
			FRequest: &http.Request{
				Header: http.Header{
					"X-Fallback": []string{"true"},
				},
			},
		},
		"",
	}

and likely we need the symmetric that NewTagFromResponse ignores request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure about this. Wouldn't the header in the request only match "${request.header.X-Fallback}", not the provided template for the response? So in any case the result would be nil, right?

My reasoning was that we need to have a case where a header is only present in the response, not in the request.

Will add a test for the symmetrical case, though.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I meant to use FResponse of course, i.e. filter is NewTag but template is "${response.header.X-Fallback}" and X-Fallback is a response header

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 does not really work because the context is only created once in the test and is the same for the whole duration of the test. This means that the response header is already present at request time. To replicate the real behavior, we have to provide a different context for the f.Response call than for the f.Request call, where the header was added. This cannot be done within the structure of this test.

I could generalize the template to provide both a context at request time and one at response time. But this seems a bit overkill to me given that it is a global construct.

Copy link
Member

@AlexanderYastrebov AlexanderYastrebov Sep 21, 2023

Choose a reason for hiding this comment

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

We can have request and response fields in the test cases instead of context, create context at the start of the test and set response after f.Request(ctx) but before f.Response(ctx):

ctx := &filtertest.Context{
  FRequest: ti.request,
}
...

f.Request(ctx)

ctx.FResponse = ti.response

f.Response(ctx)

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 works for a test that show tracingTag only runs on the request. It does not work for the symmetric case to test that tracingTagFromResponse only runs on the response. To prove this, the request in the context has to be changed (header removed) to prove that tracingTagFromResponse cannot make use of headers that are only present at request time. It is a bit artificial anyway.

Would you prefer to just have one test according to the method you described, or separate tests as they are now? Or a different change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alternatively, I could change the test code to reset the context to blank after the request call. But this would also be a bit strange, because in a real scenario the request context would be available also at response time, if I understand it correctly.

Copy link
Member

Choose a reason for hiding this comment

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

Lets have a single additional test case then.

Copy link
Member

Choose a reason for hiding this comment

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

In the additional test you can also have a real backend httptest.Server to create the response header to test it.

Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>
Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>
Signed-off-by: lukas-c-wilhelm <11819719+lukas-c-wilhelm@users.noreply.github.com>
@AlexanderYastrebov
Copy link
Member

👍

@AlexanderYastrebov
Copy link
Member

@lukas-c-wilhelm Thank you for your contribution.

@szuecs
Copy link
Member

szuecs commented Sep 22, 2023

👍

@AlexanderYastrebov AlexanderYastrebov merged commit 9ad6457 into zalando:master Sep 22, 2023
6 checks passed
@lukas-c-wilhelm lukas-c-wilhelm deleted the tag-span-from-response branch September 22, 2023 10:55
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.

Allow tracingTag filter to use response header values
3 participants