Istio + Jaeger tracing with gRPC calls using Spring Boot - spring-boot

I'm using this library (grpc-spring-boot-starter) so I can have gRPC capabilities in a Spring Boot app.
I want to know how to properly integrate this with Istio + Jaeger tracing.
I need to know what are the needed dependencies for this to happen.
I have two (2) apps, one serves as the gRPC client and one serves as the gRPC server,
The expectation is that the trace between the gRPC client and the gRPC server must be reflected in Jaeger. But it's not happening.
I am inside a Kubernetes cluster that has Istio.
What really happens is HTTP request -> Envoy sidecar -> gRPC Client's Spring Boot #RestController -> get the Headers from HTTP request -> copy those to gRPC call before making the call -> gRPC Service.
How can I make the gRPC client <-> gRPC Service trace shown to Jaeger?
Are there any dependencies that needs to be imported?
Right now I have:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- Used together with Sleuth to be able to trace gRPC calls. -->
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-grpc</artifactId>
</dependency>
<!-- Add'l dependency for Istio + Jaeger gRPC tracing -->
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-client</artifactId>
</dependency>
I also have done something like this to "propagate the headers":
private void propagateHeaders(HttpHeaders headers) {
// TODO: adding these string values to a final / constant.
// get these tracing headers from HTTP request, (coming from Envoy)
String xRequestIdFromHttpHeader = headers.getFirst("x-request-id");
String xB3TraceIdFromHttpHeader = headers.getFirst("x-b3-traceid");
String xB3SpanIdFromHttpHeader = headers.getFirst("x-b3-spanid");
String xB3ParentSpanIdFromHttpHeader = headers.getFirst("x-b3-parentspanid");
String xB3SampledFromHttpHeader = headers.getFirst("x-b3-sampled");
String xB3FlagsFromHttpHeader = headers.getFirst("x-b3-flags");
String xOtSpanContextFromHttpHeader = headers.getFirst("x-ot-span-context");
// create a custom gRPC header, they call it Metadata
Metadata xRequestIdMetadata = new Metadata();
Metadata xB3TraceIdMetadata = new Metadata();
Metadata xB3SpanIdMetadata = new Metadata();
Metadata xB3ParentSpanIdMetadata = new Metadata();
Metadata xB3SampledMetadata = new Metadata();
// TODO: refactor. putting to List<> and using foreach to attach this one.
// assign value of the x-request-id Metadata to the value of the HTTP Header
xRequestIdMetadata.put(Metadata.Key.of("x-request-id", Metadata.ASCII_STRING_MARSHALLER), xRequestIdFromHttpHeader);
xB3TraceIdMetadata.put(Metadata.Key.of("x-b3-traceid", Metadata.ASCII_STRING_MARSHALLER), xB3TraceIdFromHttpHeader);
xB3SpanIdMetadata.put(Metadata.Key.of("x-b3-spanid", Metadata.ASCII_STRING_MARSHALLER), xB3SpanIdFromHttpHeader);
xB3ParentSpanIdMetadata.put(Metadata.Key.of("x-b3-parentspanid", Metadata.ASCII_STRING_MARSHALLER), xB3ParentSpanIdFromHttpHeader);
xB3SampledMetadata.put(Metadata.Key.of("x-b3-sampled", Metadata.ASCII_STRING_MARSHALLER), xB3SampledFromHttpHeader);
// TODO: refactor. putting to List<> and using foreach to attach this one.
// use MetadataUtils to attach that new Metadata to our stub before requesting to our gRPC Server via gRPC
greetingServiceStub = MetadataUtils.attachHeaders(greetingServiceStub, xRequestIdMetadata);
greetingServiceStub = MetadataUtils.attachHeaders(greetingServiceStub, xB3TraceIdMetadata);
greetingServiceStub = MetadataUtils.attachHeaders(greetingServiceStub, xB3SpanIdMetadata);
greetingServiceStub = MetadataUtils.attachHeaders(greetingServiceStub, xB3ParentSpanIdMetadata);
greetingServiceStub = MetadataUtils.attachHeaders(greetingServiceStub, xB3SampledMetadata);
}
But it doesn't seem to work..

What you did is for http 1.x, and it doesn't work for http2/grpc. Please dive into grpc impl in springboot doc.

Related

Spring Coud Sleuth causing ActiveMQ/JMS message headers to be lost

Versions:
SpringBoot: 2.3.12.RELEASE
SpringCloud: Hoxton.SR12
SpringCloud Starter Sleuth: 3.0.3
Camel: 3.4.6
I want to add Sleuth to a pre-exisitng project that now makes use of ActiveMQ, previosuly it was jusing JMS. When I do, values from the ActiceMQ message get blocked/removed and (one being "filename" which is the key value for S2 requests). Other JMS values still seem to come through OK.
I need to understand why the non-JMS values are getting blocked/removed (I cannot find any information on what would be causing that to happen) to prevent the errors.
I know I can disbale Sleuth for JMS with spring.sleuth.messaging.jms.enabled=true but moving forwards I'd like to be able to trace the ActiveMQ/JMS code, so that workaround isn't particularly attractive.
As this is pre-exiting code, I'd also like to avoid having to re-write it if at all possible.
Has anyone gotten SPring Cloud Sleuth to work with ActiveMQ/JMS and can maybe point out where things are going wrong?
Edit:
Based on the initial response from Marcin, we found that the following version compile & execute, although significant problems do remain:
SpringBoot: 2.4.8
SpringCloud: 2020.0.3
Camel: 3.7.5
Without Sleuth
ActiveMQ message details:
Log message ("filename", "CamelAwsS3Etag" etc present):
2021-08-16 10:10:37.889 INFO [MyApp,,] 28775 --- [umer[taskQueue]] taskQueueConsumer: ***HEADERS IN***: {CamelAwsS3ETag=39d029a87fa4c6aaee5f1de643d9f3f6, Content-Type=application/json, filename=_bl001/group0/_bl001-group0-1629104686042.zip, JMSCorrelationID=null, JMSCorrelationIDAsBytes=null, JMSDeliveryMode=2, JMSDestination=queue://taskQueue, JMSExpiration=0, JMSMessageID=ID:server-44053-1629104652998-1:3:1:1:1, JMSPriority=4, JMSRedelivered=false, JMSReplyTo=null, JMSTimestamp=1629104686350, JMSType=null, JMSXGroupID=_bl001-group0, JMSXGroupSeq=0, JMSXUserID=null}
With Sleuth
ActiveMQ message details:
Log message ("filename", "CamelAwsS3Etag" etc are missing):
2021-08-16 10:24:33.821 INFO [MyApp,,] 31553 --- [umer[taskQueue]] taskQueueConsumer: ***HEADERS IN***: {JMSCorrelationID=null, JMSCorrelationIDAsBytes=null, JMSDeliveryMode=2, JMSDestination=queue://taskQueue, JMSExpiration=0, JMSMessageID=ID:server-42561-1629105658959-1:3:1:1:1, JMSPriority=4, JMSRedelivered=false, JMSReplyTo=null, JMSTimestamp=1629105675306, JMSType=null, JMSXGroupID=_bl000-group1, JMSXGroupSeq=0, JMSXUserID=null}
Sample Java code
#Component
public class MyAppCoreRouter extends RouteBuilder {
/* Other code */
#Override
public void configure() {
from("activemq:queue:taskQueue?concurrentConsumers=" + taskNumberOfConcurrentConsumers)
.log("***HEADERS IN***: ${headers}")
.routeId("taskQueueConsumer")
.threads(taskNumberOfConcurrentConsumers)
.pollEnrich().simple("aws-s3://myapp-task?amazonS3Client=#amazonS3Client&fileName=${header.filename}&operation=getObject")
.choice()
.when(header(S3Constants.KEY).endsWith(".zip"))
.to("file://" + taskLocalUnCompressedEndpoint + "?fileName=${header.CamelAwsS3ETag}/${header.CamelAwsS3Key}")
.process(exchange -> {
String camelAwsS3ETag = exchange.getIn().getHeader("CamelAwsS3ETag", String.class);
String camelAwsS3Key = exchange.getIn().getHeader("CamelAwsS3Key", String.class);
File uniqueDir = new File(taskLocalUnCompressedEndpoint, camelAwsS3ETag);
File taskZip = new File(uniqueDir, camelAwsS3Key);
new ZipFile(taskZip).extractAll(uniqueDir.getAbsolutePath());
})
.setHeader("resourceDirectory", simple(taskLocalUnCompressedEndpoint + "/${header.CamelAwsS3ETag}")).setHeader("schedule.time", simple("${date:now}"))
.removeHeader("CamelAwsS3Headers")
.log(LoggingLevel.DEBUG, "Processing batch job ${headers}")
.to("spring-batch:importMyAppRecordJob")
.endChoice()
.otherwise()
.log(LoggingLevel.WARN, "Unexpected file type, filtering file name ${header.CamelAwsS3Key} ")
.end();
/* Other code */
}
}
You're using wrong versions of Boot, Cloud and Sleuth. To use Sleuth 3.0.x you need to use Cloud 2020.0.x and Boot 2.4.x or 2.5.x.

How to make Feign POST request without a request body and with query params?

I am using Feign with the Apache Http Client and I would like to support the following jax-rs interface:
#POST
#Path("/do_something")
void doSomething(#QueryParam("arg") String arg);
But, ApacheHttpClient uses a RequestBuilder, which converts query parameters for requests without a body/entity into a UrlEncodedFormEntity.
I am converting my APIs to jax-rs, and I do not want to break backwards compatibility. Is there a way to use Feign without adjusting my API? Will the OkHttp or Ribbon clients support POSTs with query params and no body/entity? Is there another java jax-rs client that will support this?
Also, is there a reason why RequestBuilder turns query params into a UrlEncodedFormEntity? Is there an alternative HttpUriRequest builder within the apache-httpclient library that doesn't do this? RequestBuilder's build method has the following lines of code:
if (entity == null && (HttpPost.METHOD_NAME.equalsIgnoreCase(method) || HttpPut.METHOD_NAME.equalsIgnoreCase(method))) {
entity = new UrlEncodedFormEntity(parameters, HTTP.DEF_CONTENT_CHARSET);
} else {
// omitted expected behavior
}
Before switching to Feign, my code constructed a HttpUriRequest with something similar to the following:
URI uri = new URIBuilder()
.setScheme("https")
.setHost("localhost")
.setPath("service/do_something")
.addParameter("arg", "value")
.build();
HttpUriRequest request = new HttpPost(uri);
If you are willing to break the API slightly and maintain support for the #QueryParam, then you could define a request interceptor on the feign client that adds a plain text entity/body to the request:
.requestInterceptor(template -> {
if (template.method().equals(HttpPost.METHOD_NAME) && template.queries().keySet().size() > 0 && template.body() == null) {
template.body(" ");
}
})
Then, your API would change with the following:
#POST
#Consumes(MediaType.TEXT_PLAIN)
#Path("/do_something")
void doSomething(#QueryParam("arg") String arg);
But, this breaks the API since the server now expects/consumes a POST message with a plain text entity/body.
I think the same could be accomplished without the requestInterceptor and with Feign's #Body template:
#POST
#Consumes(MediaType.TEXT_PLAIN)
#Body(" ")
#Path("/do_something")
void doSomething(#QueryParam("arg") String arg);
But, this means that your API would have to include Feign rather than pure jax-rs annotations.

Apache Camel HTTP display request and response

I am using Apache Camel to load data from CSV file to a webservice. Is there anyway I can display request and response. Below is the route configuration..
I split and aggregate 100 items from array to be sent as POST body.
from(fileLocation)
.unmarshal().csv().bean(new CSVConverter(), "process")
.split(body())
.aggregate(constant(true), new GroupedBodyAggregationStrategy())
.completionSize(100)
.completionTimeout(1000)
.marshal().json(JsonLibrary.Jackson)
.setHeader("Authorization", simple(apiKEY))
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.HTTP_URI, simple(apiURL))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("https://serivceurl.com/abc");
Please let me know how can I display request and response with above route?
You can use camel log component to log headers; properties and body
ex:
.to("log:DEBUG?showBody=true&showHeaders=true")
.to("https://serivceurl.com/abc");
.to("log:DEBUG?showBody=true&showHeaders=true")
For more options pl refer: https://camel.apache.org/log.html
If you are planning to use CXF to invoke web service, out of the box logging feature can be used as below,
<cxf:bus>
<cxf:features>
<cxf:logging/>
</cxf:features>
</cxf:bus>
If you take a look into the org.apache.camel.component.http.HttpProducer class you'll see that there is some logging implemented.
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Executing http {} method: {}", method.getName(), method.getURI());
}
int responseCode = executeMethod(method);
LOG.debug("Http responseCode: {}", responseCode);
So if you configure your logging framework (like logback) to the correct LoggingLevel you'll see what the HTTP-Component exactly does.
If you want to log it yourself you can try with the log component or the log dsl like mentioned in the other answer.

Getting exception while Consuming https Webservice in mule

I'm trying to call a https web service using cxf generated client proxies within Mule. Almost 99% of the time, I get
Caused by: org.apache.commons.httpclient.ProtocolException: Unbuffered entity enclosing request can not be repeated.
at org.apache.commons.httpclient.methods.EntityEnclosingMethod.writeRequestBody(EntityEnclosingMethod.java:487)
at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2114)
at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)*
The app has http inbound end point. The Mule Java transformer tries to call a webservice using https using cxf generated client proxies. I'm running into above said exception.
I've provided screenshot the mule flow [http://i.stack.imgur.com/7X9Wg.jpg]. Much appreciated!!
Mule config xml
<cxf:jaxws-service serviceClass="test.service.https.TestService" doc:name="SOAP" configuration-ref="CXF_Configuration" enableMuleSoapHeaders="false"/>
<custom-transformer class="test.service.https.CallLicenseService" doc:name="Calls HTTPS WS using CXF generated client proxies" encoding="UTF-8" mimeType="text/plain"/>
<logger message="Success" level="INFO" doc:name="Logger"/>
<set-payload value="#['HELLO SUCCESS']" doc:name="Set Payload"/> </flow>
Transformer
URL wsdlURL = null;
String serviceUrl = "TARGET_HTTPS_WSDL"; //This would be the target https URL
try {
wsdlURL = new URL(serviceUrl);
} catch (MalformedURLException e) {
Logger.getLogger(getClass()).info("", e);
}
AuditLogServiceService ss = new AuditLogServiceService(wsdlURL);
AuditLoggingService port = ss.getAuditLoggingServicePort();
((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
serviceUrl.substring(0, serviceUrl.length() - 5));
AuditLogServiceRequest request = new AuditLogServiceRequest();
request.setClientId("4");
request.setUserId("101");
request.setEventSubType("1");
request.setEventType("1");
AuditLogMessage msg = new AuditLogMessage();
msg.setMessage("Hello Test");
request.getLogMessages().add(msg);
AuditLogServiceResponse response = port.logEvent(request);
System.out.println(response.getMessage());
return response.getMessage();
First of all if you need to consume a webservice You need to put <cxf:jaxws-client serviceClass instead of cxf:jaxws-client ...next step is you need to use an http outbound endpoint to post to the external webservice ... pls refer the following link :- http://www.mulesoft.org/documentation/display/current/Consuming+Web+Services+with+CXF
One more thing .. you need to use java component instead of <custom-transformer class ..you need to set the payload just before the component ... I mean you need to set the payload before posting it to external webservice

How to query SOAP based endpoint using Spring WS by passing pre generated SOAP Request instead of XML payload?

Need to query SOAP based endpoint using Sprint WS - but here instead of passing XML payload I will have pass a generated SOAP Request itself. I was using org.springframework.ws.client.core.WebServiceTemplate for this purpose right now ?
You can do the following:
ClientMessageCallBack _callBack =
new ClientMessageCallBack("yournamespaceuri/operationx");
final String _message =
"<ns1:operationx xmlns:ns1="yournamespaceuri"></ns1:operationx>";
StreamSource _source = new StreamSource(new StringReader(_message));
// the result will be output in the console
StreamResult _result = new StreamResult(System.out);
// suppose you have a reference to webServiceTemplate
webServiceTemplate.sendSourceAndReceiveToResult(_source,_callBack, _result);

Resources