I am considering using log4net as my logging framework for a new project starting soon. One of the problems that I encountered during prototyping, which I cannot find a definitive answer to, is how you can clear or mask the contents of a message in a custom and neat way.
Presumably, let me say that I want several cleaners to be put in place, but I also want to follow the principle of single responsibility. Some cleaner examples:
- Card Cleaner / PAN Cleaner
- Password cleaner
- Clearing personal data
I know that you should never record this information in plain text, and the code executing the logs will never consciously do this. I want to have the last level of protection, however, if the data is distorted and the confidential data somehow glides to where it should not; magazines are the worst case scenario.
Option 1:
I found this StackOverflow article that details a possible solution, but it involves using reflection. This is undesirable for performance, but also seems to be hacked for manipulating internal storage mechanisms. Editing-log4net-messages-before-they-reach-the-appenders
Option 2:
The proposed answer to the same question involves the use of PatternLayoutConverter. This is normal for a single cleanup operation, but you cannot use multiple operations such as:
public class CardNumberCleanerLayoutConverter : PatternLayoutConverter { protected override void Convert(TextWriter writer, LoggingEvent loggingEvent) { string message = loggingEvent.RenderedMessage;
<layout type="log4net.Layout.PatternLayout"> <converter> <name value="cleanedMessage" /> <type value="Log4NetPrototype.CardNumberCleanerLayoutConverter, Log4NetPrototype" /> </converter> <converter> <name value="cleanedMessage" /> <type value="Log4NetPrototype.PasswordCleanerLayoutConverter, Log4NetPrototype" /> </converter> <conversionPattern value="%cleanedMessage" /> </layout>
In the event of a name clash, as shown above, the converter loaded last will be the one that will act. Using the above example, this means that passwords will be cleared, but not card numbers.
Option 3:
The third option I've tried is to use chained ForwarderAppender instances, but this quickly complicates the configuration, and I would not consider this an ideal solution. Since the LoggingEvent class has an immutable RenderedMessage property, we cannot change it without creating a new instance of the LoggingEvent class and passing it as shown below:
public class CardNumberCleanerForwarder : ForwardingAppender { protected override void Append(LoggingEvent loggingEvent) {
Conformity (very difficult to follow):
<log4net> <appender name="LocatedAsyncForwardingAppender" type="Log4NetPrototype.LocatedAsyncForwardingAppender, Log4NetPrototype"> <appender-ref ref="CardNumberCleanerForwarder" /> </appender> <appender name="CardNumberCleanerForwarder" type="Log4NetPrototype.CardNumberCleanerForwarder, Log4NetPrototype"> <appender-ref ref="PasswordCleanerForwarder" /> </appender> <appender name="PasswordCleanerForwarder" type="Log4NetPrototype.PasswordCleanerForwarder, Log4NetPrototype"> <appender-ref ref="LogFileAppender" /> </appender> <appender name="LogFileAppender" type="Log4NetPrototype.LogFileAppender, Log4NetPrototype"> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%m" /> </layout> </appender> <root> <level value="DEBUG" /> <appender-ref ref="LocatedAsyncForwardingAppender" /> </root> </log4net>
Does anyone have any other suggestion on how this can be implemented when theoretically the number of cleaners can be tuned due to performance?