Remove Log4net (null): Conditional Output - log4net

I'm trying to suppress "(null)" output. If I have this conversionPattern:
%property{MyProp}
...and if MyProp is null, then the output is:
(null)
In most cases that is good. But what if I don't want the "(null)" to be output? Can this be done?
The problem becomes more interesting if my conversionPattern looks like this:
MY_PROP=%property{MyProp}
In this case, even if I find a way to suppress the "(null)", my output will still be:
MY_PROP=
Ideally, I'd like to suppress everything related to "my prop" if the value is null. If I invented my own syntax, it might be:
%(MyProp,MY_PROP=%MyProp)
This would make log4net suppress the conversion pattern specified in the second parameter, if the first parameter was null.
My musings aside, is there a real way to do this in log4net?

One way to do this is to create a custom PatternLayoutConverter. If the property value is null write the customNullReplacement (that can even be based on the Option key if you want some dynamism).
namespace LogUtils
{
public class CustomNullProperty : PatternLayoutConverter
{
private static string customNullReplacement = "????";
override protected void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
if (Option != null)
{
var prop = loggingEvent.LookupProperty(Option);
if(prop == null)
{
prop = customNullReplacement;
}
WriteObject(writer, loggingEvent.Repository, prop);
}
else
{
WriteDictionary(writer, loggingEvent.Repository, loggingEvent.GetProperties());
}
}
}
}
You then define this converter – using the <converter> tag – on the Log4Net Configuration (XML based config example bellow) and use it on your conversionPattern:
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="customNullProp" />
<type value="LogUtils.CustomNullProperty, DLLNAME" />
</converter>
<conversionPattern value="customNullProp{PropertyName} %message%newline"/>
</layout>
There are other approaches like creating a renderer for System.Object but I find this approach more explicit.
For your second requirement, using the custom converter, you could simply move the prefix inside the custom converter writer.

Related

Values converted by custom converter not displayed

I am using a PrimeFaces' pickList with a custom converter.
JSF:
<p:pickList converter="costsConverter" value="#{offerController.costsAsDualListModel}" var="cost" itemLabel="#{cost}" itemValue="#{cost}" />
offerController.costsAsDualListModel looks like this:
public DualListModel<Cost> getCostsAsDualListModel() {
DualListModel<Cost> costsDualList;
List<Cost> costsSource = new ArrayList<Cost>();
List<Cost> costsTarget = new ArrayList<Cost>();
for (Cost c : costs) {
costsSource.add(c);
}
costsDualList = new DualListModel<Cost>(costsSource, costsTarget);
return costsDualList;
}
And my custom converter looks like this:
public String getAsString(FacesContext context, UIComponent component, Object object) {
if (object == null) {
return "";
}
Integer no = ((Cost) object).getNo();
String valueOf = String.valueOf(no);
return valueOf;
}
getAsString() is called and valueOf is correct but inside my picklist I still see the objects and not the return value fo getAsString().
I tried to use <f:converter converterId="costsConverter"/> within the picklist element. Same issue. Also I registered the converter in faces-config.xml:
<converter>
<converter-id>costsConverter</converter-id>
<converter-class>com.my.converter.CostsConverter</converter-class>
</converter>
What could be the problem?
You have a wrong understanding of values in components like picklists, selectonemenus, etc. These values are never displayed there but the labels are. And since converters are for values, not labels, you'll never see the converted value but the labels and everything behaves as it should. Just use itemLabel="#{cost.no}" and everything should be fine (display wise).
See e.g. how it is used in these two Q/A that also use a converter
How to write a custom converter for <p:pickList>
Primefaces Picklist Converter

Message Handler Chain in Spring Boot

Since Spring Boot recommends Java based configuration, I'm having trouble to convert the following xml based message handler chain config to Java based. Any help is appreciated.
<chain input-channel="incomingChannel" output-channel="completeChannel">
<splitter ref="itemSplitter" />
<transformer ref="transformer1" />
<transformer ref="transformer2" />
<aggregator ref="requestProcessor" />
<transformer ref="transformer3" />
<transformer ref="transformer4" />
I have tried to use IntegrationFlows to achieve the same as above.
#Bean
public IntegrationFlow incomingFlow(){
return IntegrationFlows.from(incomingChannel())
.split("itemSplitter","split")
.transform("transformer1")
.transform("transformer2")
.aggregate()//? need to figure out how to initialize this?
.transform("transformer3")
.transform("transformer4")
.channel(completeChannel())
.get();
}
But I got the following error
Failed to transform Message; nested exception is org.springframework.messaging.MessageHandlingException: Expression evaluation failed: locateItemEnrichmentTransformer; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 0): Property or field 'transformer1' cannot be found on object of type 'org.springframework.messaging.support.GenericMessage' - maybe not public?
Hence I'm not sure if this is the equivalent way in Java code to translate the original chain xml config.
RequestProcessor (aggregator) implementation:
#Component
public class RequestProcessor {
/** The log. */
private static Log log = LogFactory.getLog(RequestProcessor.class);
#Aggregator
public Message<Requests> aggregate(#Headers Map<String, ?> headers, List<Request> requests) {
try {
return MessageBuilder.withPayload(new Requests(service.submit(requests, false, true)))
.copyHeaders(headers)
.build();
} catch (ClientException e) {
log.error(e.toString());
return null;
}
}
}
There is no obligation to convert the flow from XML to Java - you can use #ImportResource to pull in the XML.
It is certainly possible to wire up a MessageHandlerChain in java but as you have found, it's easier to use the Java DSL to replace a chain.
The
.transform("transformer1")
form of .transform() (1 String parameter) expects an expression, not a bean name.
You can use
.transform(transformer1())
Where transformer1() is your transformer #Bean.
EDIT
For the aggregator, if you are using Java 8...
.aggregate(a -> a.processor(requestProcessor()))
...for Java 7 or 6...
.aggregate(new Consumer<AggregatorSpec>() {
public void accept(AggregatorSpec a) {
a.processor(requestProcessor());
}
})

Logging only top-most ThreadContext stack item

We're using Log4net's ThreadContext.Stacks and it is mostly working well. My problem comes if there have been multiple ThreadContext.Stacks["key"].Push(...).
With a simple ConversionPattern:
<param name="ConversionPattern value="... topProp=%properties{key} ..."/>
I see log entries like:
... topProp=first second third ...
I'd really like to see only the most recently pushed value rather than all the values. I had hoped I could put something like the following in my appender/layout/ConversionPattern:
<param name="ConversionPattern value="... topProp=%properties{key}{1} ..."/>
but that doesn't work. I can kludge it by assuming/requiring all values to be the same length (say 5) and doing:
<param name="ConversionPattern value="... topProp=%5.5properties{key} ..."/>
But that isn't real attractive. Any ideas?
Thanks!
[Edit to add very simple example]
using System;
using System.IO;
using log4net;
using log4net.Config;
namespace ThreadLocalExample {
class Program {
private const string PropJobId = "Example:JobId";
static void Main() {
XmlConfigurator.Configure(new FileInfo("log4net.cfg"));
var log = LogManager.GetLogger(typeof(Program));
ThreadContext.Stacks[PropJobId].Push("Old");
log.Debug("Enter using");
using (ThreadContext.Stacks[PropJobId].Push("New")) {
log.Debug("stuff");
}
log.Debug("Out of using");
log.Debug("done.");
Console.ReadKey();
}
}
}
With the log4net configuration:
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<threshold value="ALL" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="[jobId=%P{Example:JobId}]: %m%n" />
</layout>
</appender>
Produces:
[jobId=Old]: Enter using
[jobId=Old New]: stuff
[jobId=Old]: Out of using
[jobId=Old]: done.
But I'd like:
[jobId=Old]: Enter using
[jobId=New]: stuff
[jobId=Old]: Out of using
[jobId=Old]: done.
I had the same problem, and it was not only about 'bad formatting', because I used database appender (AdoNetAppender) which expects integers. So after joining all stacked values together the result is not an integer any more. Consider an appender like this:
<appender name="DbAppender" type="log4net.Appender.AdoNetAppender">
...
<commandText value="INSERT INTO Log ([Id]) VALUES (#Id)" />
<parameter>
<parameterName value="#Id" />
<dbType value="Int32" />
<layout type="log4net.Layout.PatternLayout" value="%P{someId}" />
</parameter>
This appender will not accept any log message, where 'someId' is stacked twice or more - no logs in database ...
So, to solve this problem I gave up with stacks and turned back to flat properties.
I've coded some short extension:
public static class Log4NetExt {
public static IDisposable ThreadContextPush(string key, object value) {
object oldVal = ThreadContext.Properties[key];
ThreadContext.Properties[key] = value;
var topMostCleaner = new DispCleaner();
topMostCleaner.EvDispose += () => {
// Pop = restore old value
ThreadContext.Properties[key] = oldVal;
};
return topMostCleaner;
}
private class DispCleaner : IDisposable {
public event Action EvDispose;
public void Dispose() {
if (EvDispose != null) EvDispose();
}
}
}
And now, instead of:
using (ThreadContext.Stacks[PropJobId].Push("New")) {
write:
using (Log4NetExt.ThreadContextPush(PropJobId, "New")) {
and it works ok ;)
(This short code does not follow all best practises of building disposable objects, deleting event handlers and all this stuff, but it is short and I think it is safe in this simple case).
I have Done it via Filip solution modified as
public static class Log4NetExt
{
public static IDisposable ThreadContextSet(string key, object value)
{
//object oldVal = ThreadContext.Properties[key];
ThreadContext.Properties[key] = value;
var topMostCleaner = new DispCleaner();
topMostCleaner.EvDispose += () => {
// Pop = restore old value
//ThreadContext.Properties[key] = oldVal;
ThreadContext.Properties[key] = null;
};
return topMostCleaner;
}
private class DispCleaner : IDisposable
{
public event Action EvDispose;
public void Dispose()
{
if (EvDispose != null)
{
EvDispose();
}
}
}
}
And used it like
using (Log4NetExt.ThreadContextSet("ContextId", "Feed"))
using (Log4NetExt.ThreadContextPush("ContextValue", objFeed.FeedId))
{
}

Write Log4J to jTextArea

I'm an inexperienced Java developer writing an application that is handling a backup.
My application opens a gui (StepTwoBackup written using NetBeans Template), which gathers some information from the user, then when I press on the "Next" button, the gui passes these information to an object (BackupRestore which is logging all the operation using Log4J), and then opens another window (StepThreeBackup) and passes the object to it.
In this new window(StepThreeBackup), I automatically launch a method on the object passed (BackupRestore.execute()) which performs the backup.
In this last window (StepThreeBackup) I created a JTextArea where I would like to show the output of the Log4J (which currently writes to a log file and outputs to console).
Is there a way to do this? I've read that I should use an appender, but cannot figure out how to do this correctly.
For the time being I've created the following entry in my working Log4J property file:
<appender name="guiAppender" class="BackupAppGui.StatusMessageAppender">
<param name="Threshold" value="INFO" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy HH:mm:ss} %5p %c{1} - %m%n"/>
</layout>
</appender>
Then the following Class in my Package (following another post):
package BackupAppGui;
/**
*
* #author MSTPA
*/
import javax.swing.JTextArea;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
public class StatusMessageAppender extends AppenderSkeleton {
private final JTextArea jTextA;
public StatusMessageAppender() {
jTextA = StepThreeBackup.getJTextA();
}
protected void append(LoggingEvent event)
{
if(event.getLevel().equals(Level.INFO)){
jTextA.append(event.getMessage().toString());
}
}
public void close()
{
}
public boolean requiresLayout()
{
return false;
}
}
But nothing is written to the jTextArea.
What am I doing wrong? Can someone help me solving this? Thank you for all the help you can give me.
You need to make sure that the instance of JTextArea is not null. Yoy can try adding the appender programmatically (e.g. in the constructor of StepThreeBackup after create the components):
StatusMessageAppender appender = new StatusMessageAppender();
LogManager.getRootLogger().addAppender(appender);
Don't forget delete the entry in the log4j.xml file.

MULE 3.2 - How to access Flow Session Property Values from a java component

This Question relates to Mule ESB 3.2.
If I have read values from JMS, transform to JSON and store the values in my session like this:
<message-properties-transformer scope="session" doc:name="save values to session">
<add-message-property key="id" value="#[json-node://id]"/>
<add-message-property key="name" value="#[json-node://name]"/>
</message-properties-transformer>
<component class="org.mule.example.echo.Echo"/>
How do I access these property values from a java component?
First of all, your class must implement the org.mule.api.lifecycle.Callable interface. Then you can get the properties values inside the onCall method like this
#Override
public Object onCall(MuleEventContext eventContext) throws Exception {
String id = eventContext.getMessage().getProperty("id", PropertyScope.SESSION);
String name = eventContext.getMessage().getProperty("name", PropertyScope.SESSION);
//Do the rest of your stuff
}
If the properties are added with OutboundScope then the simple
public void process(#Payload String payload, #OutboundHeaders Map headers ){
String id = headers.get("name")
}
will return you flow property

Resources