Log4net: Why is Pattern Layout ignored in RenderedMessage? - log4net

I'm debugging a custom log4net Appender that I've written. My setup uses this standard PatternLayout in my config file:
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout
In my Append() method implementation if I call RenderLoggingEvent() it returns a properly formatted message. The loggingEvent's Renderedmessage, however, only contains the %message bit.
RenderLoggingEvent() returns:
"2015-06-09 14:09:37,382 [Main Thread] INFO MyConsole.Program
[(null)] - Thread test\r\n"
loggingEvent.RenderedMessage contains:
"Thread test"
Is this how RenderedMessage is supposed to work?
I need to render the message outside of the Appender so I'd rather not use its RenderLoggingEvent() method. Is there a more direct way to get the rendered message from the LoggingEvent instance?

You say you need to 'render the message outside of the Appender', but the Layout is associated with the appender.
Assuming you can work around this and access the layout somehow, then you can call the Format method on the layout:
PatternLayout layout = … ;
string result;
using (StringWriter writer = new StringWriter())
{
layout.Format(writer, loggingEvent);
result = writer.ToString();
}
Test output:
2015-06-09 14:06:43,357 [14] ERROR test [NDC] - Test Message

When you are extending the AppenderSkeleton to write your custom Appender, you already have the layout as a property at hand. You can then write a method in your Appender:
Here's my code (in TextBoxAppender.cs), inheriting AppenderSkeleton:
private string GetLayoutedMessage(LoggingEvent loggingEvent) {
using (StringWriter writer = new StringWriter())
{
Layout.Format(writer, loggingEvent);
return writer.ToString();
}
}

Related

Error during retrieving netsuite search result

I am trying to search a contact from netsuite. what i basically do is, creating a search criteria with email as parameter and trying to retrieve the result and then map it to object to xml transformer. But I am getting following error as :
Could not call java.util.concurrent.CopyOnWriteArrayList.writeObject() : Cannot marshal the XStream instance in action
The mule flow i am using as follows:
<flow name="contact_searchFlow">
<http:listener config-ref="HTTP_Request_Configuration" path="/basicContactSearch" doc:name="HTTP"/>
<logger message="Test1" level="INFO" doc:name="Logger"/>
<component class="netsuitews.ContactBasicSearchComponent" doc:name="Search Contact Basic criteria"/>
<logger message="Test 2" level="INFO" doc:name="Logger"/>
<netsuite:search config-ref="NetSuite__Request_Level_Authentication" searchRecord="CONTACT_BASIC" fetchSize="5" doc:name="Contact Basic Search"/>
<logger message="Test3" level="INFO" doc:name="Logger"/>
<mulexml:object-to-xml-transformer doc:name="Object to XML"/>
<logger message="Test4" level="INFO" doc:name="Logger"/>
The search component i am using as follows:
public class ContactBasicSearchComponent implements Callable {
public Object onCall(MuleEventContext eventContext) throws Exception {
ContactSearchBasic searchCriteria = new ContactSearchBasic();
SearchStringField nameFilter = new SearchStringField();
nameFilter.setOperator(SearchStringFieldOperator.IS);
nameFilter.setSearchValue("test_shutterFly#gmail.com");
searchCriteria.setEmail(nameFilter);
return searchCriteria;
}
#Override
public Object call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
I am using NetSuite Connector (Mule 3.5+) version 7.1.0.201603151241 and running in mule 3.8.0 EE version.
Here is the stack trace i got from mule logs.
2016-08-18 20:41:26,181 [netsuitews].HTTP_Request_Configuration.worker.01] INFO org.mule.api.processor.LoggerMessageProcessor - Test1
2016-08-18 20:41:26,194 [[netsuitews].HTTP_Request_Configuration.worker.01] INFO org.mule.api.processor.LoggerMessageProcessor - Test 2
2016-08-18 20:41:37,668 [[netsuitews].HTTP_Request_Configuration.worker.01] INFO org.mule.api.processor.LoggerMessageProcessor - Test3
2016-08-18 20:41:43,761 [[netsuitews].HTTP_Request_Configuration.worker.01] ERROR org.mule.exception.DefaultMessagingExceptionStrategy -
********************************************************************************
Message : Could not call java.util.concurrent.CopyOnWriteArrayList.writeObject() : Cannot marshal the XStream instance in action
-------------------------------
message : Could not call java.util.concurrent.CopyOnWriteArrayList.writeObject()
cause-exception : com.thoughtworks.xstream.converters.ConversionException
cause-message : Cannot marshal the XStream instance in action
(com.thoughtworks.xstream.converters.ConversionException).
Payload : org.mule.streaming.ConsumerIterator#7dbf934e
Element XML : <mulexml:object-to-xml-transformer doc:name="Object to XML"></mulexml:object-to-xml-transformer>
Payload Type : org.mule.streaming.ConsumerIterator
Element : /contact_searchFlow/processors/5 # netsuitews:customer_crud.xml:61 (Object to XML)
Root Exception stack trace:
com.thoughtworks.xstream.converters.ConversionException: Cannot marshal the XStream instance in action
at com.thoughtworks.xstream.core.util.SelfStreamingInstanceChecker.marshal(SelfStreamingInstanceChecker.java:59)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller.convert(AbstractReferenceMarshaller.java:69)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller$1.convertAnother(AbstractReferenceMarshaller.java:84)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:250)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:226)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.<init>(AbstractReflectionConverter.java:189)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:135)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:83)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller.convert(AbstractReferenceMarshaller.java:69)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller$1.convertAnother(AbstractReferenceMarshaller.java:84)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:250)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:226)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.<init>(AbstractReflectionConverter.java:189)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:135)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:83)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller.convert(AbstractReferenceMarshaller.java:69)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller$1.convertAnother(AbstractReferenceMarshaller.java:88)
at
.
.
.
Could you please help on this!
That conversion exception occurs because the NetSuite Search operation is a processor that implements Pagination. Therefore, the resulting payload is of type ConsumerIterator. The Object to XML transformer cannot directly convert that iterator to XML. You need to extract its contents first in order to manipulate it. One option is to convert the data to a List and then apply the XML transformer. For example:
#[org.apache.commons.collections.IteratorUtils.toList(payload)]
Then your flow would look like this:
<flow>
...
<netsuite:search config-ref="NetSuite__Request_Level_Authentication" searchRecord="CONTACT_BASIC" fetchSize="5" doc:name="NetSuite"/>
<set-payload value="#[org.apache.commons.collections.IteratorUtils.toList(payload)]" doc:name="Set Payload"/>
<mulexml:object-to-xml-transformer doc:name="Object to XML"/>
</flow>
Additionally, I suggest you use DataWeave to build the input data for NetSuite instead of using the Java component to set the search criteria.
<dw:transform-message doc:name="Transform Message">
<dw:set-payload><![CDATA[%dw 1.0
%output application/java
---
{
email: {
operator: "IS",
searchValue: "test_shutterFly#gmail.com"
}
} as :object {
class : "com.netsuite.webservices.platform.common.ContactSearchBasic"
}
]]></dw:set-payload>
</dw:transform-message>
The final flow would look similar to:

How can I include xml configuration in logback.groovy

I'm writing a Spring Boot app and need the flexibility of controlling my logback configuration using Groovy. In Spring Boot all I have to do is create src/main/resources/logback.groovy and it is automatically used for configuration.
What I would like to do though is start with Spring Boot's default logback configuration, and just override or modify settings as needed.
If I were using logback.xml instead of logback.groovy I could do something like the following.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework.web" level="DEBUG"/>
</configuration>
Is there something similar to the include line above that I can use in logback.groovy? I can look at the contents of base.xml and it's other included files to see how to replicate this manually, but it would add a bit of boilerplate code I'd like to avoid.
Thanks for any help you can provide.
There's an online tool that translates given logback.xml file to equivalent logback.groovy. In your case it resulted in:
//
// Built on Thu Jul 16 09:35:34 CEST 2015 by logback-translator
// For more information on configuration files in Groovy
// please see http://logback.qos.ch/manual/groovy.html
// For assistance related to this tool or configuration files
// in general, please contact the logback user mailing list at
// http://qos.ch/mailman/listinfo/logback-user
// For professional support please see
// http://www.qos.ch/shop/products/professionalSupport
import static ch.qos.logback.classic.Level.DEBUG
logger("org.springframework.web", DEBUG)
When it comes to <include> it's not supported for groovy configurations.
How do you feel about instead of adding/overriding your configuration, you reload it again?
You can create a Spring Bean that will see if a logback file is in a location you specify, and if it is, reload using that file
Example
#Component
public class LoggingHelper {
public static final String LOGBACK_GROOVY = "logback.groovy";
#PostConstruct
public void resetLogging() {
String configFolder = System.getProperty("config.folder");
Path loggingConfigFile = Paths.get(configFolder, LOGBACK_GROOVY);
if (Files.exists(loggingConfigFile) && Files.isReadable(loggingConfigFile)) {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ContextInitializer ci = new ContextInitializer(loggerContext);
loggerContext.reset();
try {
ci.configureByResource(loggingConfigFile.toUri().toURL());
} catch (JoranException e) {
// StatusPrinter will handle this
} catch (MalformedURLException e) {
System.err.println("Unable to configure logger " + loggingConfigFile);
}
StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
}
}
}
I am using this snippet to start my logback.groovy file
import ch.qos.logback.classic.joran.JoranConfigurator
import org.xml.sax.InputSource
def configurator = new JoranConfigurator()
configurator.context = context
def xmlString = '<?xml version="1.0" encoding="UTF-8"?>\n<configuration>\n <include resource="org/springframework/boot/logging/logback/base.xml"/>\n</configuration>'
configurator.doConfigure(new InputSource(new StringReader(xmlString)))
Contrary to the documentation stating that:
Everything you can do using XML in configuration files, you can do in
Groovy with a much shorter syntax.
include is not possible with Groovy out-of-the-box. However, thanks to a bug ticket that was opened in 2014, there are a couple of workarounds. I am including them here (slightly edited), but all credit goes to "Yih Tsern" from the original JIRA bug:
logback.groovy
include(new File('logback-fragment.groovy'))
root(DEBUG, ["CONSOLE"])
def include(File fragmentFile) {
GroovyShell shell = new GroovyShell(
getClass().classLoader,
binding,
new org.codehaus.groovy.control.CompilerConfiguration(scriptBaseClass: groovy.util.DelegatingScript.name))
Script fragment = shell.parse(fragmentFile.text)
fragment.setDelegate(this)
fragment.run()
}
logback-fragment.groovy:
// NOTE: No auto-import
import ch.qos.logback.core.*
import ch.qos.logback.classic.encoder.*
appender("CONSOLE", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%d [%thread] %level %mdc %logger{35} - %msg%n"
}
}
Given the workaround and a pull-request to add the feature, I'm not sure why the functionality hasn't been added to Logback core yet.

How to avoid CRLF (Carriage Return and Line Feed) in Logback - CWE 117

I'm using Logback and I need to avoid CRLF(Carriage Return and Line Feed) when I log a user parameter.
I tried to add my class, which extends ClassicConverter, on the static map PatternLayout.defaultConverterMap but It didn't work.
Thank you,
You should create a custom layout as described in logback documentation
Custom layout:
package com.foo.bar;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
public class RemoveCRLFLayout extends PatternLayout {
#Override
public String doLayout(ILoggingEvent event) {
return super.doLayout(event).replaceAll("(\\r|\\n)", "");
}
}
Logback configuration:
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="com.foo.bar.RemoveCRLFLayout">
<pattern>%d %t %-5p %logger{16} - %m%n</pattern>
</layout>
</encoder>
For a quick solution we used a %replace expression in our pattern, to replace line feed and carraige returns found in the message.
Note this example is using a Spring Boot property to set the pattern, but you can use %replace in your Logback config file the same way.
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %replace(%msg){'\n|\r', '_'}%n"
(A custom converter would have been my first choice, but I had trouble getting it to work with Spring Boot and Spring Cloud Config. If you want to learn more about that approach, search the logback docs for conversionRule.)
ch.qos.logback.core.CoreConstants;
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
ch.qos.logback.classic.pattern.LineSeparatorConverter:
public String convert(ILoggingEvent event) {
return CoreConstants.LINE_SEPARATOR;
}
package ch.qos.logback.classic.PatternLayout:
defaultConverterMap.put("n", LineSeparatorConverter.class.getName());
So the proper way to ensure fixed line ending is the property line.separator.
The same implementation is for java.lang.System.lineSeparator():
lineSeparator = props.getProperty("line.separator");

Define Logging Based on packages using BasicConfigurator

I am trying to figure out how to seperate my log files based on the packages using BasicConfigurator
Like in my log4j.properties I used to have appenders like
log4j.logger.com.cambiahealth.engine.common.aspect=,memberservices
log4j.logger.com.cambiahealth.engine.rest.family=,familyservice
I tried the following but doesnt seem to seperate out the requests going to a particular file
FileAppender fa = new FileAppender();
fa.setName("abc");
fa.setFile("/usr/regence/mylog.log");
fa.setLayout(new PatternLayout("%d %-5p [%c{1}] %m%n"));
fa.setThreshold(Level.INFO);
fa.setAppend(true);
fa.activateOptions();
BasicConfigurator.configure(fa);
System.out.println("The logger abc is initialized");
Logger log= Logger.getLogger("com.cambiahealth.engine.rest.family");
log.addAppender(fa);
FileAppender xyz= new FileAppender();
xyz.setName("claims");
xyz.setFile("/usr/regence/myClaims.log");
xyz.setLayout(new PatternLayout("%d %-5p [%c{1}] %m%n"));
xyz.setThreshold(Level.INFO);
xyz.setAppend(true);
xyz.activateOptions();
BasicConfigurator.configure(claims);
System.out.println("The logger xyz is initialized");
BasicConfigurator.configure(xyz);
Logger.getLogger("com.xyz.claim").addAppender(xyz);
I figured it out. I had to removed the basicConfigurator.Configure out! and it all works now

Getting values from Log4Net configuration

I have implement a custom log4net appender by extending the AppenderSkeleton-class. It was as simple as anyone could ask for and works perfectly.
My problem is that I had to hardcode a few values and I'd like to remove them from my code to the configuration of the appender. Since log4net knows how it is configured I think there should be a way to ask log4net for it's configuraion.
My appender could look something like this:
<appender name="MyLogAppender" type="xxx.yyy.zzz.MyLogAppender">
<MyProperty1>property</MyProperty1>
<MyProperty2>property</MyProperty2>
<MyProperty3>property</MyProperty3>
</appender>
How to get the value of MyProperty1-3 so I can use it inside my Appender?
Thanks in advance
Roalnd
It depends a bit on the type but for simple types you can do the following:
Define a property like this:
// the value you assign to the field will be the default value for the property
private TimeSpan flushInterval = new TimeSpan(0, 5, 0);
public TimeSpan FlushInterval
{
get { return this.flushInterval; }
set { this.flushInterval = value; }
}
This you can configure as follows:
<appender name="MyLogAppender" type="xxx.yyy.zzz.MyLogAppender">
<flushInterval value="02:45:10" />
</appender>
This certainly works for string, bool, int and TimeSpan.
Note: If your settings requires some logic to be activated (e.g. create a timer) then you can implement this in the ActivateOptions method.

Resources