blob: 28b4cbd29d82e4f6f04e5544a479719606da80cf [file] [log] [blame] [view]
Extending Log4cxx {#extending-log4cxx}
===
<!--
Note: License header cannot be first, as doxygen does not generate
cleanly if it before the '==='
-->
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
Sometimes, you may want to extend Log4cxx, for example by creating
a new appender to write out data in a new format. The following
guide shows how you can extend Log4cxx in order to add a new appender.
The full sample application can be found at \ref custom-appender.cpp and \ref custom-appender.xml .
The first thing for our example is to create a class that extends
log4cxx.AppenderSkeleton so that we don't need to implement all of
the virtual methods that are defined in log4cxx.Appender:
~~~{.cpp}
using namespace log4cxx;
namespace com::foo {
class NullWriterAppender : public AppenderSkeleton {
};
}
~~~
Next, we need to add in a few macros in order to properly register
our new appender with Log4cxx:
~~~{.cpp}
using namespace log4cxx;
namespace com::foo {
class NullWriterAppender : public AppenderSkeleton {
public:
DECLARE_LOG4CXX_OBJECT(NullWriterAppender)
BEGIN_LOG4CXX_CAST_MAP()
LOG4CXX_CAST_ENTRY(NullWriterAppender)
LOG4CXX_CAST_ENTRY_CHAIN(AppenderSkeleton)
END_LOG4CXX_CAST_MAP()
};
IMPLEMENT_LOG4CXX_OBJECT(NullWriterAppender)
}
~~~
These macros allow Log4cxx to instantiate your class and provide log4cxx::cast.
The `DECLARE_LOG4CXX_OBJECT` macro adds method declarations and
the `IMPLEMENT_LOG4CXX_OBJECT` macro adds implementations.
The `IMPLEMENT_LOG4CXX_OBJECT` also adds a static reference value
which is initialized during program startup
by calling, for example, `NullWriterAppender::registerClass()`.
To avoid [Static Initialization Order Fiasco](https://en.cppreference.com/w/cpp/language/siof)
when configuring Log4cxx during static initialization
(as in [the recommended runtime configuration technique])
you should put your extension classes (and the `IMPLEMENT_LOG4CXX_OBJECT` macro)
in a dynamic library (DSO/DLL) that is loaded before loading the Log4cxx configuration.
Alternatively, you could call `NullWriterAppender::registerClass()`
before loading the configuration.
Multiple `registerClass()` calls do no harm.
Now, let's add some basic functionality to our class. As the name of
our class implies, we are going to do nothing with our appender here, as
this is just an example. To that end, we need to implement the following:
1. Default constructor
2. `close` method
3. `requiresLayout` method
4. `append` method, which does the actual writing of the log event
5. `activateOptions`, which causes the class to reconfigure itself
6. `setOption`, which gets called whenever we get an option in a config file
These are basically stub methods, with a few comments on their use:
~~~{.cpp}
NullWriterAppender(){}
void close() override {}
bool requiresLayout() const override {
return false;
}
void append(const spi::LoggingEventPtr& event, helpers::Pool& p) override {
// This gets called whenever there is a valid event for our appender.
}
void activateOptions(helpers::Pool& pool) override {
// Given all of our options, do something useful(e.g. open a file)
}
void setOption(const LogString& option, const LogString& value) override {
if (helpers::StringHelper::equalsIgnoreCase
( option
, LOG4CXX_STR("SOMEVALUE")
, LOG4CXX_STR("somevalue")
)
)
{
// Do something with the 'value' here.
}
}
~~~
At this point, we now have a fully functioning(if useless) custom appender. We can now
refer to this appender in our configuration file like so:
~~~{.xml}
<?xml version="1.0" encoding="UTF-8" ?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss}] %c %-5p - %m%n"/>
</layout>
</appender>
<appender name="NullAppender" class="NullWriterAppender">
<param name="SomeValue" value="Nothing"/>
</appender>
<root>
<priority value="info" />
<appender-ref ref="ConsoleAppender"/>
</root>
<logger name="NullLogger" additivity="false">
<appender-ref ref="NullAppender"/>
</logger>
</log4j:configuration>
~~~
When Log4cxx is configured, any messages that go to the `NullLogger` will
then be forwarded on to the `NullWriterAppender`.
This same technique can be used to add new classes of many different kinds
to Log4cxx, including(but not limited to):
* [Appenders](@ref log4cxx.Appender)
* [Layouts](@ref log4cxx.Layout)
* [Filters](@ref log4cxx.spi.Filter)
\example custom-appender.cpp
This example shows how to extend Log4cxx with a new appender.
\example custom-appender.xml
This example shows how to use a new appender in a configuration file.
[the recommended runtime configuration technique]:quick-start.html#configuration