How to wrap an existing slf4j logger into a kotlin logger? - kotlin

Given an existing slf4j logger, I would like to wrap it into an kotlin-logging logger.
The classes / methods to do that already exists in the library but are internal, so I can't call them:
mu.internal.KLoggerFactory.wrapJLogger()
Is there another way to do it, which is accessible to users of the library?

It's possible to use the extension method: Logger.toKLogger() since 1.7.6. see this issue for more details: https://github.com/MicroUtils/kotlin-logging/issues/88

Related

Display Cloudhub Logs in Json Pattern along with Custom fields

Our client is not interested in using any 3rd party Logger like JSON Logger or any other logger comnectors available. So is there any way to modify the default Anypoint Logging pattern in cloudhub in Json format like how a Json Logger does. Is there any way to add custom fields using default logger like api_name or flow_start_time, end_time? Currently I am creating a variable and defining required fields in a Json pattern and further configure the variable in the default logger which is a workaround and working fine but its just I was curious if this is possible in some easier way without writing any dwl?
You can add custom variables using Mule 4.4 MDC logging feature. Note that it is not available with previous versions.
You can also request to override the default logging configuration in CloudHub, otherwise the log4j2.xml in the application is ignored, and try to use the JsonLayout. I don't recommend it though.

Using Byte Buddy to expand Spring Boot's classpath

The AWS SDK can locate API call interceptors via classpath scanning (looking for software/amazon/awssdk/global/handlers/execution.interceptors and instantiating classes specified there).
I'm writing a Java Agent with the intention of causing my interceptors to be locatable by the AWS SDK.
My interceptor is bundled with the Java Agent.
My interceptor implements AWS's ExecutionInterceptor.
The AWS SDK is not bundled with my agent, because I'd like the end-user to provide their own AWS SDK version.
For regular standalone applications, this is a no-brainer, as the Java Agent is automatically added to the runtime classpath of the application. The AWS SDK finds my interceptors with no problem.
However, this approach completely breaks with Spring Boot applications where the AWS SDK is bundled as a dependency under BOOT-INF/lib. The reason boils down to Spring Boot's classloading hierarchy. My interceptor class can be found, but its loading fails due to inability to find AWS's ExecutionInterceptor, as it is loaded in a "lower" classloader in the hierarchy.
So I figured that my approach should be to somehow modify Spring Boot's classloader search. However, I'm facing these issues:
At the time of the agent being called, Spring Boot's "lower" classloader isn't created yet.
I am not entirely sure what it is that I need to instrument.
I've read of Byte Buddy being able to help in such "interesting" circumstances but haven't found a way to make this work yet. Any ideas?
(EDIT: I'm looking for a solution that doesn't require code/packaging changes, hence the Java Agent approach)
(EDIT: Things I've tried)
Following Rafael's answer: The method in the SDK that resolves all interceptors is in the class SdkDefaultClientBuilder, and is called resolveExecutionInterceptors.
The following, then, works for standalone JARs which are not SpringBoot applications:
public static void installAgent(Instrumentation inst) {
new AgentBuilder.Default()
.with(RedefinitionStrategy.DISABLED)
.type(ElementMatchers.nameEndsWith("SdkDefaultClientBuilder"))
.transform(
new Transformer() {
#Override
public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
return builder.visit(Advice.to(MyAdvice.class).on(ElementMatchers.named("resolveExecutionInterceptors")));
}
}
).installOn(inst);
}
For SpringBoot applications, however, it looks like the advice isn't applied at all. I am guessing that this is because the SdkDefaultClientBuilder type isn't even available at the time when the agent starts. It is available during SpringBoot's runtime, in a different classloader.
Byte Buddy allows you to inject code in any method of any class, so the first and only major thing you would need to find out would be where your interceptor is instantiated. This can typically be done by setting a breakpoint in the constructor of the interceptor in the working scenario and investigating the methods in the stack. Find out where the classes are discovered, for example the method where software/amazon/awssdk/global/handlers/execution.interceptors is read.
Once you have identified this method, you would need to find a way to manually extract the interceptors defined by your agent and to manually add them. For example, if the file-extracted interceptors are added to an argument of type List<Interceptor>, you could use Byte Buddy to modify this method to also add those of your agent.
Normally, you use Byte Buddy's AgentBuilder in conjunction with Advice to do so. Advice let's you inline code into another method as for example, assuming you find a method with an argument of type List<Interceptor>:
class MyAdvice {
#Advice.OnMethodEnter
static void enter(#Advice.Argument(0) List<Interceptor> interceptors) {
interceptors.addAll(MyAgent.loadMyInterceptors());
}
}
You can now inline this code into the method in question by:
class MyAgent {
public static void premain(String arg, Instrumentation inst) {
new AgentBuilder.Default().type(...).transform((builder, ...) -> builder
.visit(Advice.to(MyAdvice.class).on(...))).install(inst);
}
}
You might need to use AgentBuilder.Transformer.ForAdvice if the classes in question are not available on the agent's class loader where Byte Buddy resolves the advice using both the target and the agent class loader.

How do I configure the default ObjectMapper?

In light-4j config module, there is an ObjectMapper instance that is used in a lot of different places. I have a Rest/GraphQL hybrid application and want to customize the default ObjectMapper. What is the best way to do so?
The ObjectMapper instance in the config module is static variable and a static block is used to initialize it. To overwrite the configuration, you can create a startup hook to do that.
Config.getInstance().getMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.enable(MapperFeature.USE_STD_BEAN_NAMING)
.setSerializationInclusion(JsonInclude.Include.ALWAYS)
.setPropertyNamingStrategy(new CobraCase())

Class sharing between byte-buddy interceptors/advices

I am trying to pass monitoring/tracing information through all my external calls in my java application.
To make it transparent, I'm trying to use byte-buddy but have some troubles getting it to work.
To trace every incoming (http) request, I intercept HttpServlet.service(), extract the token header from the HttpServletRequest and put it in a static ThreadLocal in a class named TokenHolder.
To trace every outgoing (http) request, I intercept HttpURLConnection and add the token header I get from the same ThreadLocal (TokenHolder).
The problem I have is that TokenHolder seems to be initialized twice and my 2 interceptors are not writing-to/reading-from the same ThreadLocal and I can't find a way to do it.
I suppose the problem is that HttpURLConnection lives in the bootclasspath while the servlet API does not.
Bonus question: is it possible to intercept URL.openConnection()? That was my first idea but I never could do it because I suppose the URL class is loaded before the agent (because of URLClassLoader) but I don't know if there are workarounds to that.
Yes, you can register a RedefinitionStrategy where Byte Buddy transforms previously loaded classes. To do so, you do however need to avoid adding methods or fields. This can typically be done by using Advice only.
You are also right that classes need to live on the bootstrap loader. You can inject classes into the bootstrap loader by placing them in a jar and using the designated method in the Instrumentation interface.

FOSRESTBundle: how to alter serializer metadatadirs at controller level

How can I specify a metadata dir for the serializer used by FOSRestBundle, at controller level?
I can't set it up in config.yml because in my case it depends on the request's route.
I've seen in JMSSerializer doc that I could use the following code
$serializer = JMS\Serializer\SerializerBuilder::create()
->addMetadataDir($someDir)
->build();
But how to apply it to an already instanciated serializer (or how to replace it)?
I'm afraid this isn't possible.
Directories are set to metadata drivers when calling build() in SerializerBuilder.php.
Even though you can access the metadata factory used by the Serializer it probably won't help you because the factory has nothing to do with cache directories. Only drivers work with directories.
So the only option for you is probably to create a new instance of Serializer and use that instead of the one from DI.
Edit: Creating a new Serializer works the same way as in your question. Then your DI container should be an instance of Container that has method set() which lets you override any registered service.