We would like to send actuator metrics to Cloudwatch. Using the provided micrometer cloudwatch MeterRegistry solutions makes to many assumptions about how our project is setup, for example you need to depend on cloud AWS which then makes even more assumptions. We would like to write a more lightweight implementation which just get a CloudWatchAsyncClient injected and makes no other assumptions about our project.
However im not sure how. Is there any example on how to make a custom implementation insted of having to depend on the available metrics registry?
So far I have done some experimenting with the following:
public interface CloudWatchConfig extends StepRegistryConfig {
int MAX_BATCH_SIZE = 20;
#Override
default String prefix() {
return "cloudwatch";
}
default String namespace() {
String v = get(prefix() + ".namespace");
if (v == null)
throw new MissingRequiredConfigurationException("namespace must be set to report metrics to CloudWatch");
return v;
}
#Override
default int batchSize() {
String v = get(prefix() + ".batchSize");
if (v == null) {
return MAX_BATCH_SIZE;
}
int vInt = Integer.parseInt(v);
if (vInt > MAX_BATCH_SIZE)
throw new InvalidConfigurationException("batchSize must be <= " + MAX_BATCH_SIZE);
return vInt;
}
}
#Service
#Log
public class CloudWatchMeterRegistry extends StepMeterRegistry {
public CloudWatchMeterRegistry(CloudWatchConfig config, Clock clock) {
super(config, clock);
}
#Override
protected void publish() {
getMeters().stream().forEach(a -> {
log.warning(a.getId().toString());
});
}
#Override
protected TimeUnit getBaseTimeUnit() {
return TimeUnit.MILLISECONDS;
}
}
#Configuration
public class MetricsPublisherConfig {
#Bean
public CloudWatchConfig cloudWatchConfig() {
return new CloudWatchConfig() {
#Override
public String get(String key) {
switch (key) {
case "cloudwatch.step":
return props.getStep();
default:
return "testtest";
}
}
};
}
}
However when I run the publish method is never called and no metrics are ever logged. What am I missing to get this working?
Here's an example project. I don't use cloudwatch myself so not had a chance to test it integrating with AWS. Leave a comment if there are any issues and we can try to resolve them
https://github.com/michaelmcfadyen/spring-boot-cloudwatch
I am trying to do something similar, and avoid using Spring Cloud. The simplest solution I have found so far is:
import io.micrometer.cloudwatch2.CloudWatchConfig;
import io.micrometer.cloudwatch2.CloudWatchMeterRegistry;
import io.micrometer.core.instrument.Clock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
#Configuration
public class MetricsConfiguration {
#Bean
public CloudWatchMeterRegistry cloudWatchMeterRegistry(CloudWatchConfig config, Clock clock) {
return new CloudWatchMeterRegistry(config, clock, CloudWatchAsyncClient.create());
}
#Component
public static class MicrometerCloudWatchConfig
extends StepRegistryPropertiesConfigAdapter<StepRegistryProperties>
implements CloudWatchConfig {
private final String namespace;
private final boolean enabled;
public MicrometerCloudWatchConfig(
#Value("${CLOUDWATCH_NAMESPACE}") String namespace,
#Value("${METRICS_ENABLED}") boolean enabled) {
super(new StepRegistryProperties() {
});
this.namespace = namespace;
this.enabled = enabled;
}
#Override
public String namespace() {
return namespace;
}
#Override
public boolean enabled() {
return enabled;
}
#Override
public int batchSize() {
return CloudWatchConfig.MAX_BATCH_SIZE;
}
}
}
Dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-cloudwatch2</artifactId>
</dependency>
Related
i migrate from activiti to camunda. in camunda I want to do something when any task create, for example when any task create i want to set a variable to created task. in activiti i use activitieventlistener to do it but now in camunda how can i do it?
my previous code is
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.valz.framework.common.utility.StringUtility;
public class ValzActivitiEventListener implements ActivitiEventListener {
#Override
public boolean isFailOnException() {
return false;
}
#Override
public void onEvent(ActivitiEvent event) {
switch (event.getType()) {
case TASK_CREATED: {
ActivitiEntityEvent activityEntityEvent = (ActivitiEntityEvent) event;
TaskEntity taskEntity = (TaskEntity) activityEntityEvent.getEntity();
ExecutionEntity exEntity = taskEntity.getProcessInstance();
String prevTaskId =(String) exEntity.getVariable("prevTaskId");
if(StringUtility.isNullOrEmpty( prevTaskId ))
prevTaskId=taskEntity.getId();
taskEntity.setVariableLocal("prevTaskId", prevTaskId);
}
break;
default:
}
}
}
I think TaskListener should work for you
#Component
#Slf4j
public class HumanTaskListener implements TaskListener {
#Override
public void notify(final DelegateTask delegateTask) {
LOGGER.debug("Notify... {}", delegateTask);
}
}
and register the Listener
#Component
public class HumanTaskBpmnListener extends AbstractBpmnParseListener {
#Autowired
private HumanTaskListener humanTaskListener;
#Override
public void parseUserTask(final Element humanTaskElement, final ScopeImpl scope, final ActivityImpl activity) {
final TaskDefinition taskDefinition = ((UserTaskActivityBehavior) activity.getActivityBehavior()).getTaskDefinition();
taskDefinition.addBuiltInTaskListener(TaskListener.EVENTNAME_CREATE, humanTaskListener);
}
}
using Log4j2 library version 2.9.1.
I am trying to create RollingFileAppender programmatically:
RollingFileAppender appender = RollingFileAppender.newBuilder()
.withName(name)
.withLayout(...some layout...)
.withStrategy(...some strategy...)
.build();
And I couldn't compile it because it says there is no method withStrategy in that builder.
If I reorder method calls:
RollingFileAppender appender = RollingFileAppender.newBuilder()
.withStrategy(...some strategy...)
.withName(name)
.withLayout(...some layout...)
.build();
It couldn't compile because it says there is no build() method now.
So it looks like this builder methods return some base builder instead of the same one.
Temporary workaround was to create separate method with generic parameter:
private <B extends RollingFileAppender.Builder<B>> RollingFileAppender createAppender() {
return RollingFileAppender.<B>newBuilder()
.withName("name")
.withStrategy(...some strategy...)
.withLayout(...some layout...)
.build();
}
Then it works fine. But this is not the usual way of using Builder.
So the question is: is this a bug and is there a better way to create RollingFileAppender without this workaround?
Moving replies to answer since I can't paste code into comment.
My Ivy import:
<dependency org="org.apache.logging.log4j" name="log4j-core" rev="2.9.1"/>
So here is my code and it worked fine:
package org.sandbox.log4j;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
import org.apache.logging.log4j.core.appender.rolling.RolloverDescription;
import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
import java.util.Map;
public class RollingAppenderNew {
public static void main(String[] args) {
Layout<String> myLayout = new Layout<String>() {
#Override
public byte[] getFooter() {
return new byte[0];
}
#Override
public byte[] getHeader() {
return new byte[0];
}
#Override
public byte[] toByteArray(LogEvent event) {
return new byte[0];
}
#Override
public String toSerializable(LogEvent event) {
return null;
}
#Override
public String getContentType() {
return null;
}
#Override
public Map<String, String> getContentFormat() {
return null;
}
#Override
public void encode(LogEvent source, ByteBufferDestination destination) {
}
};
RolloverStrategy myStrategy = new RolloverStrategy() {
#Override
public RolloverDescription rollover(RollingFileManager manager) throws SecurityException {
return null;
}
};
RollingFileAppender appender = RollingFileAppender.newBuilder()
.withName("MyAppender")
.withLayout(myLayout)
.withStrategy(myStrategy)
.build();
}
}
I am thinking that the strategy object may not be using the correct base class and confusing the compiler. What are the base classes are you using for your layout and strategy?
Thymeleaf has a number of useful utilities like #strings.capitalize(...) or #lists.isEmpty(...). I'm trying to add a custom one but have no idea how to register this.
Have made a custom Util class:
public class LabelUtil {
public String[] splitDoubleWord(String str) {
return str.split("[A-Z]", 1);
}
}
Now I'm going to use it like this:
<span th:each="item : ${#labels.splitDoubleWord(name)}" th:text="${item}"></span>
Of course, it won't work because I haven't registered the Util and defined #labels var.
So, the question is how and where to do it?
This answer is for thymeleaf 2.x.
If you use thymeleaf 3.x or later, please see other answers.
public class MyDialect extends AbstractDialect implements IExpressionEnhancingDialect {
public MyDialect() {
super();
}
#Override
public String getPrefix() {
// #see org.thymeleaf.dialect.IDialect#getPrefix
return "xxx";
}
#Override
public boolean isLenient() {
return false;
}
#Override
public Map<String, Object> getAdditionalExpressionObjects(IProcessingContext ctx) {
Map<String, Object> expressions = new HashMap<>();
expressions.put("labels", new LabelUtil());
return expressions;
}
}
and register your dialect.
#Configuration
public class ThymeleafConfig {
#Bean
public MyDialect myDialect() {
return new MyDialect();
}
}
thymeleaf-extras-java8time source code is good reference for creating custom thymeleaf expressions.
The API for registering a custom expression object has changed in Thymeleaf 3, for example:
public class MyDialect extends AbstractDialect implements IExpressionObjectDialect {
MyDialect() {
super("My Dialect");
}
#Override
public IExpressionObjectFactory getExpressionObjectFactory() {
return new IExpressionObjectFactory() {
#Override
public Set<String> getAllExpressionObjectNames() {
return Collections.singleton("myutil");
}
#Override
public Object buildObject(IExpressionContext context,
String expressionObjectName) {
return new MyUtil();
}
#Override
public boolean isCacheable(String expressionObjectName) {
return true;
}
};
}
}
I have a spring bean annotated with #Cacheable annotations defined like so
#Service
public class MyCacheableBeanImpl implements MyCacheableBean {
#Override
#Cacheable(value = "cachedData")
public List<Data> getData() { ... }
}
I need this class to be capable of disabling caching and working only with data from original source. This should happen based on some event from the outside. Here's my approach to this:
#Service
public class MyCacheableBeanImpl implements MyCacheableBean, ApplicationListener<CacheSwitchEvent> {
//Field with public getter to use it in Cacheable condition expression
private boolean cacheEnabled = true;
#Override
#Cacheable(value = "cachedData", condition = "#root.target.cacheEnabled") //exression to check whether we want to use cache or not
public List<Data> getData() { ... }
#Override
public void onApplicationEvent(CacheSwitchEvent event) {
// Updating field from application event. Very schematically just to give you the idea
this.cacheEnabled = event.isCacheEnabled();
}
public boolean isCacheEnabled() {
return cacheEnabled;
}
}
My concern is that the level of "magic" in this approach is very high. I'm not even sure how I can test that this would work (based on spring documentation this should work but how to be sure). Am I doing it right? If I'm wrong then how to make it right?
What I was looking for was NoOpCacheManager:
To make it work I switched from xml bean creation to a factory
I did something as follows:
#Bean
public CacheManager cacheManager() {
final CacheManager cacheManager;
if (this.methodCacheManager != null) {
final EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
ehCacheCacheManager.setCacheManager(this.methodCacheManager);
cacheManager = ehCacheCacheManager;
} else {
cacheManager = new NoOpCacheManager();
}
return cacheManager;
}
Inspired by SimY4 last comment, here is my working solution overloading SimpleCacheManager in order to provide runtime switch.
Just use switchableSimpleCacheManager.setEnabeld(false/true) to switch off/on.
package ch.hcuge.dpi.lab.cache;
import org.springframework.cache.Cache;
import org.springframework.cache.support.NoOpCache;
import org.springframework.cache.support.SimpleCacheManager;
/**
* Extends {#link SimpleCacheManager} to allow to disable caching at runtime
*/
public class SwitchableSimpleCacheManager extends SimpleCacheManager {
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
/**
* If the enabled value changes, all caches are cleared
*
* #param enabled true or false
*/
public void setEnabled(boolean enabled) {
if (enabled != this.enabled) {
clearCaches();
}
this.enabled = enabled;
}
#Override
public Cache getCache(String name) {
if (enabled) {
return super.getCache(name);
} else {
return new NoOpCache(name);
}
}
protected void clearCaches() {
this.loadCaches().forEach(cache -> cache.clear());
}
}
Configuration ( using Caffeine ):
#Bean
public SwitchableSimpleCacheManager cacheManager() {
SwitchableSimpleCacheManager cacheManager = new SwitchableSimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
buildCache(RESULT_CACHE, 24, 5000)
));
return cacheManager;
}
private CaffeineCache buildCache(String name, int hoursToExpire, long maxSize) {
return new CaffeineCache(
name,
Caffeine.newBuilder()
.expireAfterWrite(hoursToExpire, TimeUnit.HOURS)
.maximumSize(maxSize)
.build()
);
}
Hello I am building an application using dropwizard, that is using jersey 2.16 internally as REST API framework.
For the whole application on all resource methods I need some information so to parse that information I defined a custom filter like below
#java.lang.annotation.Target(ElementType.PARAMETER)
#java.lang.annotation.Retention(RetentionPolicy.RUNTIME)
public #interface TenantParam {
}
The tenant factory is defined below
public class TenantFactory implements Factory<Tenant> {
private final HttpServletRequest request;
private final ApiConfiguration apiConfiguration;
#Inject
public TenantFactory(HttpServletRequest request, #Named(ApiConfiguration.NAMED_BINDING) ApiConfiguration apiConfiguration) {
this.request = request;
this.apiConfiguration = apiConfiguration;
}
#Override
public Tenant provide() {
return null;
}
#Override
public void dispose(Tenant tenant) {
}
}
I haven't actually implemented the method but structure is above. There is also a TenantparamResolver
public class TenantParamResolver implements InjectionResolver<TenantParam> {
#Inject
#Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
private InjectionResolver<Inject> systemInjectionResolver;
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> serviceHandle) {
if(Tenant.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, serviceHandle);
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return true;
}
}
Now in my resource method I am doing like below
#POST
#Timed
public ApiResponse create(User user, #TenantParam Tenant tenant) {
System.out.println("resource method invoked. calling service method");
System.out.println("service class" + this.service.getClass().toString());
//DatabaseResult<User> result = this.service.insert(user, tenant);
//return ApiResponse.buildWithPayload(new Payload<User>().addObjects(result.getResults()));
return null;
}
Here is how I am configuring the application
#Override
public void run(Configuration configuration, Environment environment) throws Exception {
// bind auth and token param annotations
environment.jersey().register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(TenantFactory.class).to(Tenant.class);
bind(TenantParamResolver.class)
.to(new TypeLiteral<InjectionResolver<TenantParam>>() {})
.in(Singleton.class);
}
});
}
The problem is during application start I am getting below error
WARNING: No injection source found for a parameter of type public void com.proretention.commons.auth.resources.Users.create(com.proretention.commons.api.core.Tenant,com.proretention.commons.auth.model.User) at index 0.
and there is very long stack error stack and description
Below is the declaration signature of user pojo
public class User extends com.company.models.Model {
No annotations on User class. Model is a class that defines only single property id of type long and also no annotations on model class
When I remove the User parameter from above create resource method it works fine and when I removed TenantParam it also works fine. The problem only occurs when I use both User and TenantParam
What I am missing here ? how to resolve this error ?
EDITED
I just tried with two custom method param injection, that is also not working
#POST
#Path("/login")
#Timed
public void validateUser(#AuthParam AuthToken token, #TenantParam Tenant tenant) {
}
What I am missing here ? Is this a restriction in jersey ?
Method parameters are handled a little differently for injection. The component we need to implement for this, is the ValueFactoryProvider. Once you implement that, you also need to bind it in your AbstractBinder.
Jersey has a pattern that it follows for implementing the ValueFactoryProvider. This is the pattern used to handle parameters like #PathParam and #QueryParam. Jersey has a ValueFactoryProvider for each one of those, as well as others.
The pattern is as follows:
Instead of implementing the ValueFactoryProvider directly, we extend AbstractValueFactoryProvider
public static class TenantValueProvider extends AbstractValueFactoryProvider {
#Inject
public TenantValueProvider(MultivaluedParameterExtractorProvider mpep,
ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
if (!parameter.isAnnotationPresent(TenantParam.class)
|| !Tenant.class.equals(parameter.getRawType())) {
return null;
}
return new Factory<Tenant>() {
#Override
public Tenant provide() {
...
}
};
}
In this component, it has a method we need to implement that returns the Factory that provides the method parameter value.
The InjectionResolver is what is used to handle the custom annotation. With this pattern, instead of directly implementing it, as the OP has, we just extend ParamInjectionResolver passing in our AbstractValueFactoryProvider implementation class to super constructor
public static class TenantParamInjectionResolver
extends ParamInjectionResolver<TenantParam> {
public TenantParamInjectionResolver() {
super(TenantValueProvider.class);
}
}
And that's really it. Then just bind the two components
public static class Binder extends AbstractBinder {
#Override
public void configure() {
bind(TenantParamInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<TenantParam>>(){})
.in(Singleton.class);
bind(TenantValueProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
}
}
Below is a complete test using Jersey Test Framework. The required dependencies are listed in the javadoc comments. You can run the test like any other JUnit test
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Stack Overflow https://stackoverflow.com/q/29145807/2587435
*
* Run this like any other JUnit test. Dependencies required are as the following
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>2.22</version>
* <scope>test</scope>
* </dependency>
* <dependency>
* <groupId>org.glassfish.jersey.media</groupId>
* <artifactId>jersey-media-json-jackson</artifactId>
* <version>2.22</version>
* <scope>test</scope>
* </dependency>
*
* #author Paul Samsotha
*/
public class TenantInjectTest extends JerseyTest {
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public static #interface TenantParam {
}
public static class User {
public String name;
}
public static class Tenant {
public String name;
public Tenant(String name) {
this.name = name;
}
}
public static class TenantValueProvider extends AbstractValueFactoryProvider {
#Inject
public TenantValueProvider(MultivaluedParameterExtractorProvider mpep,
ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
if (!parameter.isAnnotationPresent(TenantParam.class)
|| !Tenant.class.equals(parameter.getRawType())) {
return null;
}
return new AbstractContainerRequestValueFactory<Tenant>() {
// You can #Inject things here if needed. Jersey will inject it.
// for example #Context HttpServletRequest
#Override
public Tenant provide() {
final ContainerRequest request = getContainerRequest();
final String name
= request.getUriInfo().getQueryParameters().getFirst("tenent");
return new Tenant(name);
}
};
}
public static class TenantParamInjectionResolver
extends ParamInjectionResolver<TenantParam> {
public TenantParamInjectionResolver() {
super(TenantValueProvider.class);
}
}
public static class Binder extends AbstractBinder {
#Override
public void configure() {
bind(TenantParamInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<TenantParam>>(){})
.in(Singleton.class);
bind(TenantValueProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
}
}
}
#Path("test")
#Produces("text/plain")
#Consumes("application/json")
public static class TestResource {
#POST
public String post(User user, #TenantParam Tenant tenent) {
return user.name + ":" + tenent.name;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(new TenantValueProvider.Binder())
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
#Test
public void shouldReturnTenantAndUserName() {
final User user = new User();
user.name = "peeskillet";
final Response response = target("test")
.queryParam("tenent", "testing")
.request()
.post(Entity.json(user));
assertEquals(200, response.getStatus());
assertEquals("peeskillet:testing", response.readEntity(String.class));
}
}
See Also:
Jersey 2.x Custom Injection Annotation With Attributes
My Comment in the Dropwizard issue: "No injection source found for a parameter"