You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by sw...@apache.org on 2026/03/11 23:58:43 UTC

(logging-log4cxx) 01/01: Add 'fallback-ref' attribute support to the 'appender-ref' element of a configuration file

This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch fallback_appender_configuration
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git

commit cd63c8f50a5ad64fde2b331e7a4bf6d23040b777
Author: Stephen Webb <sw...@gmail.com>
AuthorDate: Thu Mar 12 10:58:13 2026 +1100

    Add 'fallback-ref' attribute support to the 'appender-ref' element of a configuration file
---
 src/examples/config/async-example.xml              |  7 ++-
 src/examples/config/async-fall-back-example.xml    | 38 ++++++++++++++++
 src/main/cpp/domconfigurator.cpp                   | 48 +++++++++++++++++---
 src/main/cpp/fallbackerrorhandler.cpp              | 52 +++++++++++++++-------
 src/main/include/log4cxx/appenderskeleton.h        |  2 +
 src/main/include/log4cxx/spi/errorhandler.h        | 11 ++++-
 .../include/log4cxx/varia/fallbackerrorhandler.h   | 16 +++++--
 src/site/markdown/concepts.md                      |  4 ++
 8 files changed, 147 insertions(+), 31 deletions(-)

diff --git a/src/examples/config/async-example.xml b/src/examples/config/async-example.xml
index 07a9357d..e56ef2ba 100644
--- a/src/examples/config/async-example.xml
+++ b/src/examples/config/async-example.xml
@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
   <appender name="A1" class="RollingFileAppender">
-    <param name="File"   value="${TEMP}/SomeApplicationName.log" />
-    <param name="Append" value="true" />
+    <param name="File" value="${PROGRAM_FILE_PATH.PARENT_PATH}/${PROGRAM_FILE_PATH.STEM}.log" />
     <layout class="PatternLayout">
       <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
     </layout>
   </appender>
   <appender name="SENDMAIL" class="SMTPAppender">
-    <param name="from"   value="service_name@example.org" />
+    <param name="from" value="${PROGRAM_FILE_PATH.STEM}@example.org" />
     <param name="to" value="some_support_group@example.org" />
-    <param name="subject" value="Service error detected" />
+    <param name="subject" value="Service error detected in ${PROGRAM_FILE_PATH.STEM}" />
     <param name="SMTPHost" value="smtp.example.com"/>
     <layout class="PatternLayout">
 		<param name="ConversionPattern" value="%-5p %c{2} - %m%n"/>
diff --git a/src/examples/config/async-fall-back-example.xml b/src/examples/config/async-fall-back-example.xml
new file mode 100644
index 00000000..5968222a
--- /dev/null
+++ b/src/examples/config/async-fall-back-example.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+  <appender name="Primary" class="RollingFileAppender">
+    <rollingPolicy class="TimeBasedRollingPolicy">
+      <param name="FileNamePattern" value="${LOG_SERVER_UNC_PATH}/${PROGRAM_FILE_PATH.STEM}-%d{yyyy-MM-dd-HH-mm-ss-SSS}.log"/>
+    </rollingPolicy>
+    <triggeringPolicy class="SizeBasedTriggeringPolicy">
+      <param name="MaxFileSize" value="5MB"/>
+    </triggeringPolicy>
+    <param name="File" value="${PROGRAM_FILE_PATH.PARENT_PATH}/${PROGRAM_FILE_PATH.STEM}.log" />
+	<param name="BufferedIO" value="true"/>
+	<param name="BufferedSeconds" value="1"/>
+    <layout class="PatternLayout">
+      <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
+    </layout>
+  </appender>
+  <appender name="Backup" class="RollingFileAppender">
+    <rollingPolicy class="TimeBasedRollingPolicy">
+      <param name="FileNamePattern" value="${PROGRAM_FILE_PATH.PARENT_PATH}/${PROGRAM_FILE_PATH.STEM}-%d{yyyy-MM-dd-HH-mm-ss-SSS}.log"/>
+    </rollingPolicy>
+    <triggeringPolicy class="SizeBasedTriggeringPolicy">
+      <param name="MaxFileSize" value="5MB"/>
+    </triggeringPolicy>
+    <param name="File" value="${PROGRAM_FILE_PATH.PARENT_PATH}/${PROGRAM_FILE_PATH.STEM}.log" />
+	<param name="BufferedIO" value="true"/>
+	<param name="BufferedSeconds" value="1"/>
+    <layout class="PatternLayout">
+      <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
+    </layout>
+  </appender>
+  <appender name="ASYNC" class="AsyncAppender">
+    <appender-ref ref="Primary" fallback-ref="Backup"/>
+  </appender>
+  <root>
+    <priority value ="INFO" />
+    <appender-ref ref="ASYNC" />
+  </root>
+</log4j:configuration>
diff --git a/src/main/cpp/domconfigurator.cpp b/src/main/cpp/domconfigurator.cpp
index 6e1ad59f..82b0d0d9 100644
--- a/src/main/cpp/domconfigurator.cpp
+++ b/src/main/cpp/domconfigurator.cpp
@@ -27,7 +27,7 @@
 #include <log4cxx/helpers/loader.h>
 #include <log4cxx/helpers/optionconverter.h>
 #include <log4cxx/config/propertysetter.h>
-#include <log4cxx/spi/errorhandler.h>
+#include <log4cxx/varia/fallbackerrorhandler.h>
 #include <log4cxx/spi/loggerfactory.h>
 #if LOG4CXX_ABI_VERSION <= 15
 #include <log4cxx/defaultloggerfactory.h>
@@ -100,10 +100,14 @@ public: // ...structor
 public: // Methods
 	AppenderPtr findAppenderByName(apr_xml_elem* elem, const LogString& appenderName);
 
-	AppenderPtr findAppenderByReference(apr_xml_elem* appenderRef);
+	AppenderPtr findAppenderByReference(apr_xml_elem* appenderRef, const char* optionalAttributeName = nullptr);
 
 	AppenderPtr parseAppender(apr_xml_elem* appenderElement);
 
+	void parseFallbackAppender(apr_xml_elem* element, const LogString& holderName, const AppenderAttachablePtr& holder, const AppenderPtr& primary, const AppenderSkeletonPtr& aSkel);
+
+	void parseFallbackAppender(apr_xml_elem* element, const LoggerPtr& l, const AppenderSkeletonPtr& primary);
+
 	void parseErrorHandler(apr_xml_elem* element, const AppenderPtr& appender);
 
 	FilterStore parseFilters(apr_xml_elem* element);
@@ -194,6 +198,7 @@ IMPLEMENT_LOG4CXX_OBJECT(DOMConfigurator)
 #define FILTER_TAG "filter"
 #define ERROR_HANDLER_TAG "errorHandler"
 #define REF_ATTR "ref"
+#define FALLBACK_REF_ATTR "fallback-ref"
 #define ADDITIVITY_ATTR "additivity"
 #define ASYNCHRONOUS_ATTR "asynchronous"
 #define THRESHOLD_ATTR "threshold"
@@ -239,10 +244,12 @@ AppenderPtr DOMConfigurator::DOMConfiguratorPrivate::findAppenderByName(apr_xml_
 /**
  Used internally to parse appenders by IDREF element.
 */
-AppenderPtr DOMConfigurator::DOMConfiguratorPrivate::findAppenderByReference(apr_xml_elem* appenderRef)
+AppenderPtr DOMConfigurator::DOMConfiguratorPrivate::findAppenderByReference(apr_xml_elem* appenderRef, const char* optionalAttributeName)
 {
 	AppenderPtr appender;
-	LogString appenderName(subst(getAttribute(appenderRef, REF_ATTR)));
+	LogString appenderName = subst(getAttribute(appenderRef, optionalAttributeName ? optionalAttributeName : REF_ATTR));
+	if (optionalAttributeName && appenderName.empty())
+		return appender;
 	if (appenderName.empty())
 	{
 		LogString msg(LOG4CXX_STR("["));
@@ -381,6 +388,8 @@ AppenderPtr DOMConfigurator::DOMConfiguratorPrivate::parseAppender(apr_xml_elem*
 								+ LOG4CXX_STR(" named [") + appender->getName() + LOG4CXX_STR("]"));
 						}
 						aa->addAppender(delegateAppender);
+						if (auto appSkeleton = LOG4CXX_NS::cast<AppenderSkeleton>(appender))
+							parseFallbackAppender(currentElement, appender->getName(), aa, delegateAppender, appSkeleton);
 					}
 				}
 				else
@@ -414,6 +423,30 @@ AppenderPtr DOMConfigurator::DOMConfiguratorPrivate::parseAppender(apr_xml_elem*
 	}
 }
 
+void DOMConfigurator::DOMConfiguratorPrivate::parseFallbackAppender(apr_xml_elem* element, const LogString& holderName, const AppenderAttachablePtr& holder, const AppenderPtr& primary, const AppenderSkeletonPtr& aSkel)
+{
+	if (auto fallbackAppender = findAppenderByReference(element, FALLBACK_REF_ATTR))
+	{
+		auto fb = std::make_shared<varia::FallbackErrorHandler>();
+		fb->setAppender(primary);
+		fb->setBackupAppender(fallbackAppender);
+		fb->addAppenderHolder(holderName, holder);
+		aSkel->setErrorHandler(fb);
+	}
+}
+
+void DOMConfigurator::DOMConfiguratorPrivate::parseFallbackAppender(apr_xml_elem* element, const LoggerPtr& l, const AppenderSkeletonPtr& primary)
+{
+	if (auto fallbackAppender = findAppenderByReference(element, FALLBACK_REF_ATTR))
+	{
+		auto fb = std::make_shared<varia::FallbackErrorHandler>();
+		fb->setAppender(primary);
+		fb->setBackupAppender(fallbackAppender);
+		fb->setLogger(l);
+		primary->setErrorHandler(fb);
+	}
+}
+
 /**
 Used internally to parse an {@link ErrorHandler} element.
 */
@@ -640,7 +673,12 @@ void DOMConfigurator::DOMConfiguratorPrivate::parseChildrenOfLoggerElement(apr_x
 				}
 				newappenders.push_back(appender);
 				if (async)
+				{
 					async->addAppender(appender);
+					parseFallbackAppender(currentElement, async->getName(), async, appender, async);
+				}
+				else if (auto appSkeleton = LOG4CXX_NS::cast<AppenderSkeleton>(appender))
+					parseFallbackAppender(currentElement, logger, appSkeleton);
 			}
 		}
 		else if (tagName == LEVEL_TAG)
@@ -1349,4 +1387,4 @@ LogString DOMConfigurator::getAttribute(
 { return LogString{}; }
 LogString DOMConfigurator::subst(const LogString& value)
 { return LogString{}; }
-#endif
\ No newline at end of file
+#endif
diff --git a/src/main/cpp/fallbackerrorhandler.cpp b/src/main/cpp/fallbackerrorhandler.cpp
index 3d5f2bef..9e364faf 100644
--- a/src/main/cpp/fallbackerrorhandler.cpp
+++ b/src/main/cpp/fallbackerrorhandler.cpp
@@ -15,15 +15,15 @@
  * limitations under the License.
  */
 
-#include <log4cxx/logstring.h>
-#include <log4cxx/appender.h>
-#include <log4cxx/logger.h>
 #include <log4cxx/varia/fallbackerrorhandler.h>
 #include <log4cxx/helpers/loglog.h>
 #include <log4cxx/helpers/stringhelper.h>
-#include <log4cxx/spi/loggingevent.h>
 #include <log4cxx/hierarchy.h>
 #include <log4cxx/logmanager.h>
+#if LOG4CXX_ABI_VERSION <= 15
+#include <log4cxx/logger.h>
+#include <log4cxx/asyncappender.h>
+#endif
 
 using namespace LOG4CXX_NS;
 using namespace LOG4CXX_NS::helpers;
@@ -36,7 +36,7 @@ struct FallbackErrorHandler::FallbackErrorHandlerPrivate
 {
 	AppenderWeakPtr backup;
 	AppenderWeakPtr primary;
-	std::vector<LoggerPtr> loggers;
+	std::map<LogString, spi::AppenderAttachableWeakPtr> appenderHolders;
 	bool errorReported = false;
 };
 
@@ -47,6 +47,16 @@ FallbackErrorHandler::FallbackErrorHandler()
 
 FallbackErrorHandler::~FallbackErrorHandler() {}
 
+void FallbackErrorHandler::addAppenderHolder(const LogString& name, const spi::AppenderAttachablePtr& clx)
+{
+	if (LogLog::isDebugEnabled())
+	{
+		LogLog::debug(((LogString) LOG4CXX_STR("FB: Adding appender holder ["))
+			+ name + LOG4CXX_STR("]."));
+	}
+	m_priv->appenderHolders.emplace(name, clx);
+}
+
 void FallbackErrorHandler::setLogger(const LoggerPtr& logger)
 {
 	if (LogLog::isDebugEnabled())
@@ -54,7 +64,7 @@ void FallbackErrorHandler::setLogger(const LoggerPtr& logger)
 		LogLog::debug(((LogString) LOG4CXX_STR("FB: Adding logger ["))
 			+ logger->getName() + LOG4CXX_STR("]."));
 	}
-	m_priv->loggers.push_back(logger);
+	m_priv->appenderHolders.emplace(logger->getName(), logger);
 }
 
 void FallbackErrorHandler::error(const LogString& message,
@@ -69,11 +79,7 @@ void FallbackErrorHandler::error(const LogString& message,
 	int, const spi::LoggingEventPtr&) const
 {
 	if (LogLog::isDebugEnabled())
-	{
-		LogLog::debug(((LogString) LOG4CXX_STR("FB: The following error reported: "))
-			+  message, e);
-		LogLog::debug(LOG4CXX_STR("FB: INITIATING FALLBACK PROCEDURE."));
-	}
+		LogLog::debug(LOG4CXX_STR("FB: The following error reported: ") + message, e);
 
 	AppenderPtr primaryLocked = m_priv->primary.lock();
 	AppenderPtr backupLocked = m_priv->backup.lock();
@@ -83,21 +89,33 @@ void FallbackErrorHandler::error(const LogString& message,
 		return;
 	}
 
-	for (LoggerPtr l : m_priv->loggers)
+	for (auto& item : m_priv->appenderHolders)
 	{
+		auto holderLocked = item.second.lock();
+		if (!holderLocked)
+			continue;
 		if (LogLog::isDebugEnabled())
 		{
 			LogLog::debug(LOG4CXX_STR("FB: Replacing [")
 				+ primaryLocked->getName() + LOG4CXX_STR("] with [")
-				+ backupLocked->getName() + LOG4CXX_STR("] in logger [")
-				+ l->getName() + LOG4CXX_STR("]."));
+				+ backupLocked->getName() + LOG4CXX_STR("] in [")
+				+ item.first + LOG4CXX_STR("]."));
 		}
-		if (!l->replaceAppender(primaryLocked, backupLocked))
+#if LOG4CXX_ABI_VERSION <= 15
+		bool ok{ false };
+		if (auto logger = LOG4CXX_NS::cast<Logger>(holderLocked))
+			ok = logger->replaceAppender(primaryLocked, backupLocked);
+		else if (auto asyncAppender = LOG4CXX_NS::cast<AsyncAppender>(holderLocked))
+			ok = asyncAppender->replaceAppender(primaryLocked, backupLocked);
+		if (!ok)
+#else
+		if (!holderLocked->replaceAppender(primaryLocked, backupLocked))
+#endif
 		{
 			LogLog::debug(LOG4CXX_STR("FB: Failed to replace [")
 				+ primaryLocked->getName() + LOG4CXX_STR("] with [")
-				+ backupLocked->getName() + LOG4CXX_STR("] in logger [")
-				+ l->getName() + LOG4CXX_STR("]."));
+				+ backupLocked->getName() + LOG4CXX_STR("] in [")
+				+ item.first + LOG4CXX_STR("]."));
 		}
 	}
 	m_priv->errorReported = true;
diff --git a/src/main/include/log4cxx/appenderskeleton.h b/src/main/include/log4cxx/appenderskeleton.h
index 5bc1a7db..b63ca46e 100644
--- a/src/main/include/log4cxx/appenderskeleton.h
+++ b/src/main/include/log4cxx/appenderskeleton.h
@@ -189,6 +189,8 @@ class LOG4CXX_EXPORT AppenderSkeleton :
 		void setThreshold(const LevelPtr& threshold);
 
 }; // class AppenderSkeleton
+
+LOG4CXX_PTR_DEF(AppenderSkeleton);
 }  // namespace log4cxx
 
 #endif //_LOG4CXX_APPENDER_SKELETON_H
diff --git a/src/main/include/log4cxx/spi/errorhandler.h b/src/main/include/log4cxx/spi/errorhandler.h
index 89683123..9eabd6e3 100644
--- a/src/main/include/log4cxx/spi/errorhandler.h
+++ b/src/main/include/log4cxx/spi/errorhandler.h
@@ -20,7 +20,7 @@
 
 #include <log4cxx/spi/optionhandler.h>
 #include <log4cxx/helpers/exception.h>
-#include <log4cxx/appender.h>
+#include <log4cxx/spi/appenderattachable.h>
 #include <log4cxx/spi/loggingevent.h>
 
 namespace LOG4CXX_NS
@@ -66,6 +66,15 @@ class LOG4CXX_EXPORT ErrorHandler : public virtual OptionHandler
 
 		virtual ~ErrorHandler() {}
 
+#if 15 < LOG4CXX_ABI_VERSION
+		/**
+		Add \c clx to the list of collections to search for the failed appender.
+		@param name the collection name.
+		@param clx has a collection of appenders.
+		*/
+		virtual void addAppenderHolder(const LogString& name, const AppenderAttachablePtr& clx) {};
+#endif
+
 		/**
 		Add a reference to a logger to which the failing appender might
 		be attached to. The failing appender will be searched and
diff --git a/src/main/include/log4cxx/varia/fallbackerrorhandler.h b/src/main/include/log4cxx/varia/fallbackerrorhandler.h
index a2ac5b6b..5c218831 100644
--- a/src/main/include/log4cxx/varia/fallbackerrorhandler.h
+++ b/src/main/include/log4cxx/varia/fallbackerrorhandler.h
@@ -19,10 +19,9 @@
 #define _LOG4CXX_VARIA_FALLBACK_ERROR_HANDLER_H
 
 #include <log4cxx/spi/errorhandler.h>
-#include <log4cxx/helpers/object.h>
-#include <log4cxx/appender.h>
+#if LOG4CXX_ABI_VERSION <= 15
 #include <log4cxx/logger.h>
-#include <vector>
+#endif
 
 namespace LOG4CXX_NS
 {
@@ -36,6 +35,9 @@ whatever reason.
 
 <p>The error message is printed on <code>System.err</code>, and
 logged in the new secondary appender.
+
+Here is a sample configuration file that installs this error handler:
+\include async-fall-back-example.xml
 */
 class LOG4CXX_EXPORT FallbackErrorHandler :
 	public virtual spi::ErrorHandler
@@ -53,13 +55,19 @@ class LOG4CXX_EXPORT FallbackErrorHandler :
 		FallbackErrorHandler();
 		~FallbackErrorHandler();
 
+		/**
+		Add \c clx to the list of collections to search for the failed appender.
+		@param name used in log messages.
+		@param clx has a collection of appenders.
+		*/
+		void addAppenderHolder(const LogString& name, const spi::AppenderAttachablePtr& clx) LOG4CXX_16_VIRTUAL_SPECIFIER;
+
 		/**
 		<em>Adds</em> the logger passed as parameter to the list of
 		loggers that we need to search for in case of appender failure.
 		*/
 		void setLogger(const LoggerPtr& logger) override;
 
-
 		/**
 		\copybrief spi::OptionHandler::activateOptions()
 
diff --git a/src/site/markdown/concepts.md b/src/site/markdown/concepts.md
index cb2b3284..5fc62f64 100644
--- a/src/site/markdown/concepts.md
+++ b/src/site/markdown/concepts.md
@@ -462,6 +462,10 @@ Log4cxx provides appenders to write to:
 The [addAppender](@ref log4cxx.Logger.addAppender)
 method adds an appender to a given logger.
 More than one appender can be attached to a logger.
+A backup appender can be attached to an appender
+(by using [FallbackErrorHandler](@ref log4cxx.varia.FallbackErrorHandler))
+to ensure continuation of output in the event that
+the primary destination can no longer be used.
 
 If the same file receives log requests concurrently from multiple process,
 use [this appender](@ref log4cxx.rolling.MultiprocessRollingFileAppender)