Spring Cloud Sleuth: Export data from remote system to local Zipkin - spring-boot

In my Spring Boot application I use spring-cloud-starter-sleuth (Version Hoxton.SR10) for tracing. It is (still) a monolithic application so I widely use the #NewSpan annotation for creating new spans.
In my development environment I also use spring-cloud-starter-zipkin, which works great.
But on the servers of our customers I don't have access to any Zipkin server nor am allowed to install one. Is there any possibility to save the data Spring is sending to Zipkin and import that to my local Zipkin server?
Solution thanks to Marcin's inspiration:
#Configuration
#ConditionalOnProperty(name = "custom.property", havingValue = "true")
public class SleuthConfiguration {
#Bean("zipkinSender")
Sender restTemplateSender() {
return new Sender() {
public Encoding encoding() { return Encoding.JSON; }
public int messageMaxBytes() { return Integer.MAX_VALUE; }
public int messageSizeInBytes(List<byte[]> list) { return Integer.MAX_VALUE; }
#Override
public Call<Void> sendSpans(List<byte[]> list) {
String result = convertByteArrayToList(list);
saveToFile(result);
return new Call.Base<Void>() {...};
}
};
}
}
Implement convertByteArrayToList and saveToFile your own, because my solution depends on custom libraries.

You could create your own SpanHandler bean that takes the FinishedSpan, converts into JSON and stores it somewhere on your drive. Then you could just iterate over jsons and upload them to the Zipkin server

Related

Spring configuration / properties different per request

I need to figure out if the following scenario is possible in Spring.
If we have different services / databases per region, can Spring facilitate directing calls to those services / databases per request from a single deployment? To give an example, all requests from user X will be directed to services / databases in the EAST region while all requests from user Y will be directed to services / databases in the WEST region.
Obviously connections to each database will use connection pooling, so the configuration will need to differ, not just properties. When other services are initialized, there is authentication done, so it's not just about databases connections.
This being Spring, I'd like to avoid having to pass implementations around. Can I direct Spring to use a specific configuration per request? Is there a better way to accomplish this?
-- Edit --
Technically it can be done like this, though this isn't exactly easily maintainable.
#Configuration
#PropertySource("classpath:region1.properties")
public class TestIndependentConfigurationRegion1Configuration {
#Bean
public String sampleServiceUrl(#Value("${sample.service.url}") String value) {
return value;
}
#Bean
public TestIndependentConfigurationSampleService testSampleService() {
return new TestIndependentConfigurationSampleService();
}
}
#Configuration
#PropertySource("classpath:region2.properties")
public class TestIndependentConfigurationRegion2Configuration {
#Bean
public String sampleServiceUrl(#Value("${sample.service.url}") String value) {
return value;
}
#Bean
public TestIndependentConfigurationSampleService testSampleService() {
return new TestIndependentConfigurationSampleService();
}
}
#Controller
public class TestIndependentConfigurationController {
protected ApplicationContext testRegion1ApplicationContext = new AnnotationConfigApplicationContext(TestIndependentConfigurationRegion1Configuration.class);
protected ApplicationContext testRegion2ApplicationContext = new AnnotationConfigApplicationContext(TestIndependentConfigurationRegion2Configuration.class);
#RequestMapping("/sample/service")
#ResponseBody
public String testSampleService() {
TestIndependentConfigurationSampleService testSampleService = null;
if(/* region 1 */) {
testSampleService = (TestIndependentConfigurationSampleService) testRegion1ApplicationContext.getBean("testSampleService");
}
if(/* region 2 */) {
testSampleService = (TestIndependentConfigurationSampleService) testRegion2ApplicationContext.getBean("testSampleService");
}
testSampleService.executeSampleService();
return "SUCCESS";
}
}
I don't think you can do that with properties. BUT, you should look at (netflix) ribbon client that is integrated with spring. Some of the ribbon's features allow you to load balance request's between regions. You could customize the ribbon client to do what you want.
Some readings here :
https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html

Spring Boot auto-configured metrics not arriving to Librato

I am using Spring Boot with auto-configure enabled (#EnableAutoConfiguration) and trying to send my Spring MVC metrics to Librato. Right now only my own created metrics are arriving to Librato but auto-configured metrics (CPU, file descriptors, etc) are not sent to my reporter.
If I access a metric endpoint I can see the info generated there, for instance http://localhost:8081/actuator/metrics/system.cpu.count
I based my code on this post for ConsoleReporter. so I have this:
public static MeterRegistry libratoRegistry() {
MetricRegistry dropwizardRegistry = new MetricRegistry();
String libratoApiAccount = "xx";
String libratoApiKey = "yy";
String libratoPrefix = "zz";
LibratoReporter reporter = Librato
.reporter(dropwizardRegistry, libratoApiAccount, libratoApiKey)
.setPrefix(libratoPrefix)
.build();
reporter.start(60, TimeUnit.SECONDS);
DropwizardConfig dropwizardConfig = new DropwizardConfig() {
#Override
public String prefix() {
return "myprefix";
}
#Override
public String get(String key) {
return null;
}
};
return new DropwizardMeterRegistry(dropwizardConfig, dropwizardRegistry, HierarchicalNameMapper.DEFAULT, Clock.SYSTEM) {
#Override
protected Double nullGaugeValue() {
return null;
}
};
}
and at my main function I added Metrics.addRegistry(SpringReporter.libratoRegistry());
For the Librato library I am using in my compile("com.librato.metrics:metrics-librato:5.1.2") build.gradle. Documentation here. I used this library before without any problem.
If I use the ConsoleReporter as in this post the same thing happens, only my own created metrics are printed to the console.
Any thoughts on what am I doing wrong? or what am I missing?
Also, I enabled debug mode to see the "CONDITIONS EVALUATION REPORT" printed in the console but not sure what to look for in there.
Try to make your MeterRegistry for Librato reporter as a Spring #Bean and let me know whether it works.
UPDATED:
I tested with ConsoleReporter you mentioned and confirmed it's working with a sample. Note that the sample is on the branch console-reporter, not the master branch. See the sample for details.

Spring Cloud - HystrixCommand - How to properly enable with shared libraries

Using Springboot 1.5.x, Spring Cloud, and JAX-RS:
I could use a second pair of eyes since it is not clear to me whether the Spring configured, Javanica HystrixCommand works for all use cases or whether I may have an error in my code. Below is an approximation of what I'm doing, the code below will not actually compile.
From below WebService lives in a library with separate package path to the main application(s). Meanwhile MyWebService lives in the application that is in the same context path as the Springboot application. Also MyWebService is functional, no issues there. This just has to do with the visibility of HystrixCommand annotation in regards to Springboot based configuration.
At runtime, what I notice is that when a code like the one below runs, I do see "commandKey=A" in my response. This one I did not quite expect since it's still running while the data is obtained. And since we log the HystrixRequestLog, I also see this command key in my logs.
But all the other Command keys are not visible at all, regardless of where I place them in the file. If I remove CommandKey-A then no commands are visible whatsoever.
Thoughts?
// Example WebService that we use as a shared component for performing a backend call that is the same across different resources
#RequiredArgsConstructor
#Accessors(fluent = true)
#Setter
public abstract class WebService {
private final #Nonnull Supplier<X> backendFactory;
#Setter(AccessLevel.PACKAGE)
private #Nonnull Supplier<BackendComponent> backendComponentSupplier = () -> new BackendComponent();
#GET
#Produces("application/json")
#HystrixCommand(commandKey="A")
public Response mainCall() {
Object obj = new Object();
try {
otherCommandMethod();
} catch (Exception commandException) {
// do nothing (for this example)
}
// get the hystrix request information so that we can determine what was executed
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = hystrixExecutedCommands();
// set the hystrix data, viewable in the response
obj.setData("hystrix", executedCommands.orElse(Collections.emptyList()));
if(hasError(obj)) {
return Response.serverError()
.entity(obj)
.build();
}
return Response.ok()
.entity(healthObject)
.build();
}
#HystrixCommand(commandKey="B")
private void otherCommandMethod() {
backendComponentSupplier
.get()
.observe()
.toBlocking()
.subscribe();
}
Optional<Collection<HystrixInvokableInfo<?>>> hystrixExecutedCommands() {
Optional<HystrixRequestLog> hystrixRequest = Optional
.ofNullable(HystrixRequestLog.getCurrentRequest());
// get the hystrix executed commands
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = Optional.empty();
if (hystrixRequest.isPresent()) {
executedCommands = Optional.of(hystrixRequest.get()
.getAllExecutedCommands());
}
return executedCommands;
}
#Setter
#RequiredArgsConstructor
public class BackendComponent implements ObservableCommand<Void> {
#Override
#HystrixCommand(commandKey="Y")
public Observable<Void> observe() {
// make some backend call
return backendFactory.get()
.observe();
}
}
}
// then later this component gets configured in the specific applications with sample configuraiton that looks like this:
#SuppressWarnings({ "unchecked", "rawtypes" })
#Path("resource/somepath")
#Component
public class MyWebService extends WebService {
#Inject
public MyWebService(Supplier<X> backendSupplier) {
super((Supplier)backendSupplier);
}
}
There is an issue with mainCall() calling otherCommandMethod(). Methods with #HystrixCommand can not be called from within the same class.
As discussed in the answers to this question this is a limitation of Spring's AOP.

Spring repository without database but external api

We are building an API for our service and we would like to leverage Spring Data Rest as much as possible.
This API and the new model underneath will substitute a legacy API (and it's old model) that we still need to support.
Our idea is to build an "adapter" web app that replicates the structure of the old api and serve the old model using some internal transformations.
Also the old api is using Spring Data Rest, so here the idea:
build a repository implementation that instead of querying a database will query our brand new API, retrieve the new model, apply some transformations, and return the old model.
Unfortunately, even if I'm annotating the repository implementation with the #Repository annotation, Spring is not exposing the repository in the API.
I'm not sure if this is actually something possible to do or is just a matter of me not implementing some core functionalities.
What I would like to avoid is reimplement all spring data rest methods manually in a controller.
Here my Repository class
// Method are not implemented, this is just the backbone
#Repository
public class SampleRespositoryImpl implements ReadOnlyRepository<OldSample, String> {
NewApiClient client;
public SampleRespositoryImpl(NewApiClient client) {
this.client = client;
}
#Override
public OldSample findOne(String accession) {
NewSample newSample = client.fetch(accession)
OldSample oldSample = //apply transformation to newSample
return oldSample;
}
#Override
public boolean exists(String accession) {
return client.fetch(accession) != null;
}
#Override
public Iterable<OldSample> findAll() {
return new ArrayList<>();
}
#Override
public Iterable<OldSample> findAll(Iterable<String> var1) {
return new ArrayList<>();
}
#Override
public long count() {
return 0;
}
#Override
public Iterable<OldSample> findAll(Sort var1) {
return new ArrayList<>();
}
#Override
public Page<OldSample> findAll(Pageable var1) {
List<OldSample> OldSampleList = new ArrayList<>();
Page<OldSample> page = new PageImpl<>(OldSampleList);
return page;
}
}
Here what I would like to get back when I hit the api root (http://localhost:8080/)
{
"_links": {
"samples": {
"href": "http://localhost:8080/samples{?page,size,sort}
}
}
}
Someone else linked me to another answer in StackOverflow available here as possible duplication.
Reading through that answer, I decided that is too much effort to follow this path for our needs, so I'm more oriented to create a custom controller to expose necessary methods.
This solution was reported by Kevin as answer to Implementing methods of Spring Data repository and exposing them through REST

Feign with RibbonClient and Consul discovery without Spring Cloud

I was trying to setup Feign to work with RibbonClient, something like MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, "https://myAppProd");, where myAppProd is an application which I can see in Consul. Now, if I use Spring annotations for the Feign client (#FeignClient("myAppProd"), #RequestMapping), everything works as Spring Cloud module will take care of everything.
If I want to use Feign.builder() and #RequestLine, I get the error:
com.netflix.client.ClientException: Load balancer does not have available server for client: myAppProd.
My first initial thought was that Feign was built to work with Eureka and only Spring Cloud makes the integration with Consul, but I am unsure about this.
So, is there a way to make Feign work with Consul without Spring Cloud?
Thanks in advance.
In my opinion, it's not feign work with consul, its feign -> ribbon -> consul.
RibbonClient needs to find myAppProd's serverList from its LoadBalancer.
Without ServerList, error: 'does not have available server for client'.
This job has been done by SpringCloudConsul and SpringCloudRibbon project, of course you can write another adaptor, it's just some glue code. IMHO, you can import this spring dependency into your project, but use it in non-spring way . Demo code:
just write a new feign.ribbon.LBClientFactory, that generate LBClient with ConsulServerList(Spring's class).
public class ConsulLBFactory implements LBClientFactory {
private ConsulClient client;
private ConsulDiscoveryProperties properties;
public ConsulLBFactory(ConsulClient client, ConsulDiscoveryProperties consulDiscoveryProperties) {
this.client = client;
this.properties = consulDiscoveryProperties;
}
#Override
public LBClient create(String clientName) {
IClientConfig config =
ClientFactory.getNamedConfig(clientName, DisableAutoRetriesByDefaultClientConfig.class);
ConsulServerList consulServerList = new ConsulServerList(this.client, properties);
consulServerList.initWithNiwsConfig(config);
ZoneAwareLoadBalancer<ConsulServer> lb = new ZoneAwareLoadBalancer<>(config);
lb.setServersList(consulServerList.getInitialListOfServers());
lb.setServerListImpl(consulServerList);
return LBClient.create(lb, config);
}
}
and then use it in feign:
public class Demo {
public static void main(String[] args) {
ConsulLBFactory consulLBFactory = new ConsulLBFactory(
new ConsulClient(),
new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties()))
);
RibbonClient ribbonClient = RibbonClient.builder()
.lbClientFactory(consulLBFactory)
.build();
GitHub github = Feign.builder()
.client(ribbonClient)
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
interface GitHub {
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
}
you can find this demo code here, add api.github.com to your local consul before running this demo.

Resources