Skip to content

Observability support for gateway #2715

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

Merged
merged 6 commits into from
Oct 4, 2022
Merged

Observability support for gateway #2715

merged 6 commits into from
Oct 4, 2022

Conversation

marcingrzejszczak
Copy link
Contributor

No description provided.

pom.xml Outdated
@@ -57,10 +57,26 @@
<spring-cloud-circuitbreaker.version>3.0.0-SNAPSHOT</spring-cloud-circuitbreaker.version>
<spring-cloud-commons.version>4.0.0-SNAPSHOT</spring-cloud-commons.version>
<testcontainers.version>1.17.3</testcontainers.version>
<micrometer.version>1.10.0-SNAPSHOT</micrometer.version>
Copy link
Member

Choose a reason for hiding this comment

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

What versions does boot manage?

Copy link
Member

Choose a reason for hiding this comment

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

1.10.0-M3 right now but I have an open PR for M4.
They also want to move to SNAPSHOT ~in September I guess.

@tony-clarke-amdocs
Copy link
Contributor

How does relate to spring cloud sleuth?

@spencergibb
Copy link
Member

Only indirectly as the new micrometer functionality replaces sleuth

if (context.getCarrier() == null) {
return keyValues;
}
// TODO: URI is high cardinality
Copy link
Member

Choose a reason for hiding this comment

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

Do we have the pattern/template available instead of the raw URI?

// TODO: URI is high cardinality
keyValues = keyValues.and(METHOD.withValue(context.getRequest().getMethod().name()), URI.withValue(context.getRequest().getURI().toString()));
if (context.getResponse() != null && context.getResponse().getStatusCode() != null) {
keyValues = keyValues.and(STATUS.withValue(String.valueOf(context.getResponse().getStatusCode().value())));
Copy link
Member

Choose a reason for hiding this comment

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

This can lead to an interesting issue, please bear with me.
If the status code does not present (e.g.: timeout?) this will not add a status key-value which means that in those scenarios this tag will be missing from the Timer.

This can cause issues in certain backends (i.e.: Prometheus) since it will lead to time series where the name is the same but the tags are different, e.g.:

http_client_requests{method="GET", status="200"} ...
http_client_requests{method="GET"} ...

So I guess my question is: will the status code always be there if we already initiated the call (so we are not handling the LongTaskTimer case)?
If so, with some default value (0, -1), we are good. But if not, I would do this:

else {
    keyValues = keyValues.and(STATUS.withValue("n/a"));
}

if (parentObservation != null) {
childObservation = childObservation.parentObservation(parentObservation);
}
childObservation = childObservation.start();
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a comment about where to find stop so that whoever reads this will be able to easily navigate to the other half of this?

if (log.isDebugEnabled()) {
log.debug("The response was handled for observation " + childObservation);
}
childObservation.stop();
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a comment here that tells the reader where to find start?

* @author Marcin Grzejszczak
* @since 3.0.0
*/
public class ObservedResponseHttpHeadersFilter implements HttpHeadersFilter {
Copy link
Member

Choose a reason for hiding this comment

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

What happens if there was a network error and we don't have a response (e.g.: timeout, connection reset, etc)?

}
ServerWebExchange exchange = context.getExchange();
Map<String, String> uriTemplateVariables = ServerWebExchangeUtils.getUriTemplateVariables(exchange);
String url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
Copy link
Member

Choose a reason for hiding this comment

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

There are two options, the one used here or:

Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
URI routeUri = route.getUri();

This is GATEWAY_REQUEST_URL_ATTR is merged with the request URI, the one from the Route object is as configured.

if (log.isDebugEnabled()) {
log.debug("Will instrument the HTTP request headers " + newHeaders);
}
Observation parentObservation = exchange.getAttribute("micrometer.observation");
Copy link
Member

Choose a reason for hiding this comment

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

Where does "micrometer.observation" come from?

Copy link
Contributor Author

@marcingrzejszczak marcingrzejszczak Oct 4, 2022

Choose a reason for hiding this comment

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

It comes from https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-observation/src/main/java/io/micrometer/observation/contextpropagation/ObservationThreadLocalAccessor.java#L37 - we can make a direct reference to that but then the context propagation dependency would be a mandatory one. I don't really see a problem with that

Copy link
Member

@spencergibb spencergibb left a comment

Choose a reason for hiding this comment

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

Builds are failing with

java.lang.ClassCastException: class java.net.URI cannot be cast to class java.lang.String (java.net.URI and java.lang.String are in module java.base of loader 'bootstrap')
	at org.springframework.cloud.gateway.filter.headers.observation.DefaultGatewayObservationConvention.getLowCardinalityKeyValues(DefaultGatewayObservationConvention.java:54) ~[classes/:na]

@marcingrzejszczak marcingrzejszczak marked this pull request as ready for review October 4, 2022 10:55
@spencergibb spencergibb changed the title First pass on Observability support Observability support for gateway Oct 4, 2022
@spencergibb spencergibb merged commit f2192b0 into main Oct 4, 2022
@proteintogo
Copy link

proteintogo commented Jan 26, 2023

I added the following dependency micrometer-tracing-bridge-brave along with spring-cloud-starter-gateway:

Configured the logging.pattern.level=%clr(%5p) %clr([TraceId=%X{traceId:-},SpanId=%X{spanId:-}]){green}

But when I log any message, no traceId/SpanId is completed. Should I do any additional step/configuration ?

@marcingrzejszczak
Copy link
Contributor Author

Check this doc https://micrometer.io/docs/observation#instrumentation_of_reactive_libraries and if you don't want to do anything manually you have to wait until we patch reactor and boot to support this out of the box

@jonatan-ivanov jonatan-ivanov deleted the observability branch January 26, 2023 18:09
@taxone
Copy link

taxone commented May 4, 2023

@marcingrzejszczak I added;

Hooks.enableAutomaticContextPropagation();

but traceIds do not appear in the logs. I created a sample project to reproduce.
Any help would be much appreciated, thanks in advance.

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.

6 participants