I've written a log4j appender that will take a certain message type and dump it to a MongoDB cluster. Let's call it "Audit"
I want to be able to do something like this:
logger.debug (new AuditEntry (...));
from ANY class and have log4j automatically route any log messages of type "AuditEntry" to my Audit appender. Is this possible? The only way I've been able to do this so far is to add it the appender to the rootLogger chain:
log4j.rootLogger=DEBUG, Console, Audit
But then of course the AuditEntry also is toString()'d out to the Console -- I'd like to avoid this if possible.
I have thought of one solution but I'm not sure if it's the best solution. Make the Audit filter the first in the chain:
log4j.rootLogger=DEBUG, Audit, Console
and then inside my log4j appender call "clearFilterChain()" I haven't tried this yet... nor have I found any documentation on this function.
Any other solutions?
Related
I am migrating a large codebase from log4j to log4j2. Log4j has a method to set the SyslogHost.
syslog.setSyslogHost(syslogDomainName);
I am looking for an equivalent in log4j2.
For completeness, the original log4J code looked like this:
SyslogAppender syslog = (SyslogAppender)Logger.getLogger("available-loggers").getAppender("syslog");
syslog.setSyslogHost(syslogDomainName);
The new code is looking to be a bit more complicated:
org.apache.logging.log4j.Logger logger
= org.apache.logging.log4j.LogManager.getLogger("available-loggers");
org.apache.logging.log4j.core.Logger coreLogger
= (org.apache.logging.log4j.core.Logger)logger;
org.apache.logging.log4j.core.LoggerContext context
= (org.apache.logging.log4j.core.LoggerContext)coreLogger.getContext();
org.apache.logging.log4j.core.config.Configuration configuration = (Configuration)context.getConfiguration();
Appender appender = configuration.getAppender("syslog");
SyslogAppender syslog = (SyslogAppender) appender;
...
plus the replacement for
syslog.setSyslogHost(syslogDomainName);
Edit to add - I am wondering if I can use the AppenderBuilder to create a syslog appender with the correct hostname.
syslog.newSyslogAppenderBuilder().withHost(syslogDomainName);
EDITED -
I am still having trouble making this work.
The log4J2.xml socket configuration below works for basic logging and is what I'm using:
<Socket name="syslog" port="514" host="${sys:log4j.syslog.host}" protocol="UDP" ReconnectionDelay="200">
<JsonLayout2/>
</Socket>
We have a log collector in AWS that can go away at any time, then scale back up with a new IP address. I need to figure out how to get it to switch to that to that new IP.
Log4J1 was pretty simple to do with the syslog.setSyslogHost during runtime. Any ideas on what I am missing in Log4J2?
The first question I would ask is "Why do you want to do this"? If it is because you need to determine the host name dynamically I would point you at Log4j's Lookup mechanism. If you are wanting to log events in RFC 3164 (BSD) format, the SyslogAppender is normally configured as
<Syslog name="bsd" host="targetHost" port="514" protocol="TCP"/>
If you want to set targetHost dynamically then do something like:
<Syslog name="bsd" host="${sys:syslogHost}" port="514" protocol="TCP"/>
which would get the hostname from a system property named "syslogHost". See Lookups for more lookup options or you can write your own custom Lookup.
But if, for some reason, you really need to programatically configure the Appender then there are multiple ways to do it which are described on the Log4j web site. Which you choose depends on whether you are programmatically creating the configuration or whether you want to modify and existing configuration.
To programmatically create a configuration Log4j recommends using a ConfigurationBuilder. This follows Log4j's normal mechanism of creating a configuration in two passes. In the first pass whatever the configuration dialect is (including the ConfigurationBuilder) is converted into a node tree. In the second pass the node tree is converted into the actual configuration. The second pass is generally common to all configurations built this way. So when you look at the ConfigurationBuilder API you won't be building specific classes. Instead you will be creating configuration components, which will later cause the corresponding classes to be created.
Modifying an Appender of an existing configuration cannot be done in a thread-safe manner so it is not allowed. Instead, you can create a new Appender with the same name and start it, remove the existing appender from the Configuration, add the new Appender, modify all the LoggerConfig objects to reference the new Appender instead of the old one, then stop the old Appender. You can find an example of this on Log4j's custom configuration page.
I should also note that Log4j's SocketAppender, on which the SyslogAppender is built, accepts a DNS name that returns multiple ip addresses. If the connection fails the SocketAppender will try to reconnect at any one of the ip addresses.
I want to write a testng test case to verify that the logs (log4j) generated are following exactly the given conversion pattern from the log4j.properties file. As an example if I have conversion pattern : [%d] %-5p [%t]: %m%n a sample log would looks like [2015-07-07 16:42:09,937] DEBUG [main]: Message 1 I want to make sure that the log follows the exact pattern.
So for now what I'm doing is first read all the logs in to a String array and loop that array to find whether the expected log without the date i.e. DEBUG [main]: Message 1 contains in the recorded.
Is this way of testing log records is correct ?
If you have any idea about a good way to test logs rather than this please point out.
There are any solutions. The first in my mind is:
Configure your log4j to write on System.out and use the SystemOutRule.
public void MyTest {
#Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
#Test
public void writesTextToSystemOut() {
System.out.print("hello world");
assertEquals("hello world", systemOutRule.getLog());
}
}
See: https://stefanbirkner.github.io/system-rules/index.html
This solution is logger implementation independent if the logger writes to System.out and/or System.err.
More log4J aware:
Use a mock framework - #slartidan says - and mock the appender implementation.
Based on your preferred mock framework and log4J 1.x or log4J 2.x the solution is quite different.
Log4J 1.2
Create a mock logger appender and add this appender on logger:
Logger.getLogger("...").addAppender( mockedAppender );
See: https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Category.html#addAppender%28org.apache.log4j.Appender%29
Log4J 2.x
Extending ConfigurationFactory and Configuration (only in test artefact) and add the mocked appender.
The easiest way to create a custom Configuration is to extend one of the standard Configuration classes (XMLConfiguration, JSONConfiguration) and then create a new ConfigurationFactory for the extended class. After the standard configuration completes the custom configuration can be added to it.
http://logging.apache.org/log4j/2.x/manual/customconfig.html (includes complete example)
Im using Log4j and i have the following problem: is there a way to add one more logger from your code ? (not root logger).
In my config file, i set up the following two loggers :
log4j.rootCategory=INFO, ALogFile
log4j.logger.BLog=INFO,BLog
I would like to remove second line from config file and add BLog from code. Is that possible?
The equivalent code should be:
Logger logger = Logger.getLogger("BLog");
logger.setLevel(Level.INFO);
logger.addAppender(Logger.getRootLogger().getAppender("BLog")); // see notes below
I.e. you obtain a Logger object for the name BLog, you then set the level to INFO and attach the appender you've presumable defined elsewhere in your config file, called BLog.
Note: based on the answers from this related question, you may need to attach the appender to a logger in order to be able to reference it, as my code does above. Or just define the appender in your code.
I use a property file for java.util.logging and want to log all classes under package:
aaa.bbb.ccc.*
the normal way (i.e. info, fine, finer) but class
aaa.bbb.ccc.ddd.MyClass
in its own logging file "My Class.log" with level finer.
The config should be done only via a properties file. How would this look like?
I tried various ways (e.g. different handlers) but not any succeeded: It never worked that both log files are written to.
To make the problem more concrete - the config I tried:
handler.performance.class=com.logging.handler.FileHandler
handler.performance.file=${LOGGING_ROOT}/performance.log
handler.performance.level=FINE
handler.fine.class=com.logging.handler.FileHandler
handler.fine.file=${LOGGING_ROOT}/finer.log
handler.fine.level=FINE
handler.async.class=com.logging.handler.AsyncBufferHandler
handler.async.level=ALL
handler.async.targets=fine
handler.asyncperf.class=com.logging.handler.AsyncBufferHandler
handler.asyncperf.level=ALL
handler.asyncperf.targets=performance
com.myapp.handlers=async,console
com.myapp.useParentHandlers=false
com.myapp.common.logging.handlers=asyncperf
com.myapp.common.logging.useParentHandlers=false
The class I want to log to this separate performance log is located beneath com.myapp.common.logging...
Found the solution - it was a wrong initialization:
The logger should be initialized with:
Logger.getLogger(MyClass.class.getName())
Then the config:
com.myapp.common.logging.MyClass.handlers=asyncperf
com.myapp.common.logging.MyClass.useParentHandlers=false
logs all logging messages of this class in the specified separate file as desired!
Define two File appenders for the two target files
Define one root logger to use the first appender
Define a second logger for the special class, to use the other appender
set additivity of the logger to false in order to make any message go to one but not both files
I think that you have problem because you can't configure 2 default FileHandlers, only one of them. So try to implement your personal subclass of FileHandler and configure it as a separate handler.
I don't remember if we can configure logger for separate class or only for package, so try also configure handlers to package of MyClass.
I have a log4j appender defined like:
log4j.logger.com.example = DEBUG, filelog
log4j.appender.filelog=org.apache.log4j.DailyRollingFileAppender
log4j.appender.filelog.File=c:/app.log
log4j.appender.filelog.layout=org.apache.log4j.PatternLayout
log4j.appender.filelog.layout.ConversionPattern=%d | %m%n
log4j.appender.filelog.DatePattern=.dd-MM-yyyy
In my class, I get the logger like:
Log logger = LogFactory.getLog(getClass());
This works properly. I want to have a logger that always logs certain messages (not errors, but things like how long transactions took). If I write these at DEBUG or INFO, I won't see them if the log level is changed. I think I can accomplish this using another appender that writes to the same file.
Is this possible to have two appenders write to the same file? How would I get the logger instance where I want to use the normal debug appender and the transactional appender in the same class? These messages won't all be in the same package, so I can't configure a certain package to always log. Will I have to have these appenders write to different files, or can I retrieve them both in the code and have something like:
Log alwaysLogger = LogFactory.getLog(ALWAYS);
alwaysLogger.debug("This message will always be written regardless of the level set on the filelog appender");
Update
I could write to two different log files if necessary, but how would I get the logger instance in my class? I don't want to configure one package/class to always use one appender over the other as the classes will have to log information/error messages and the transactional "always" messages during a normal run. Is there a way to accomplish what I need even if it write to two different log files?
I don't think log4j really supports two appenders writing to the same file because of synchronization issues. Your best bet would be, if possible, to make a switch to slf4j using Logback as your backend logging system since Logback is log4j's successor and also supports multiple appenders writing to one file. Check out FileAppender and it's prudent mode.
Update: from your description, it seems you should also check out markers. You only need one logger instance, and you can get more fine grained control using markers, because with them you basically say "this log statement has a special purpose, and has to be logged using an appropriate appender". You can also check log appender additivity, which is usually used when you use two or more appenders for one log statement.
As far as I'm aware, the instantiation of loggers with a package & class identifier is just a convention. You are free to create multiple logger instances in any class and give them any identifier.
Log alwaysLogger = LogFactory.getLog("a.b.c.ALWAYS");
Log sometimesLogger = LogFactory.getLog("a.b.c.SOMETIMES");
As for writing to the same file, I have not tried it but it should be possible, at least from a single thread. You may need to write your own appender rather than relying on one of the default ones.
I got this to work by using AsyncAppenders. I had my main appender which specified a file to log to, and had my other appenders refer to it.
e.g.
<appender name="top" class="org.apache.log4j.rolling.RollingFileAppender">
<param name="file" value="myLog.log" />
</appender>
<appender name="other" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="top" />
</appender>
<logger name="com.example.MyCLass">
<appender-ref ref="other" />
</logger>
You can add other filters, or whatever to the other appenders. As far as I can tell with my own app, the logs seem to roll, and the appender filters work as expected.
Yes. You can "workaround" to have 2 appenders writing to the same file.
Checkout this code + example:
Any questions welcome.