If you have an application that deals with payment processing, social-security numbers, etc., there’s a possibility that such sensitive information can be written to log by a component in the application. While you need to take appropriate measures to ensure that the log files are sufficiently protected, you may also want to prevent such information from being logged altogether. And if that’s the case, here’s something that can help.
TraceCheckingListener is a custom TraceListener which checks each incoming trace message for the presence of credit-card numbers and causes the trace to fail by throwing an exception if one is found.
public class TraceCheckingListener : TraceListener
{
private const string CreditCardRegEx = @”(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})”;
private Regex regex;
public TraceCheckingListener()
{
this.regex = new Regex(CreditCardRegEx, RegexOptions.Compiled);
}
private bool ContainsCreditCardNumber(string text)
{
return (!String.IsNullOrEmpty(text)) && this.regex.IsMatch(text);
}
public override void Write(string message)
{
if (this.ContainsCreditCardNumber(message))
throw new InvalidOperationException(“Sensitive credit-card information cannot be logged.”);
}
public override void WriteLine(string message)
{
if (this.ContainsCreditCardNumber(message))
throw new InvalidOperationException(“Sensitive credit-card information cannot be logged.”);
}
}
It currently checks for Visa, MasterCard, American Express, Discover, Diners Club and JCB credit-cards.
TraceChecker is a small utility class which can be instantiated in a C# using block to install the TraceCheckingListener while the using block executes and remove it when it’s done.
public class TraceChecker : IDisposable
{
private TraceCheckingListener traceCheckingListener;
public TraceChecker()
{
this.traceCheckingListener = new TraceCheckingListener();
Trace.Listeners.Insert(0, this.traceCheckingListener);
}
#region IDisposable Members
public void Dispose()
{
if (this.traceCheckingListener != null)
{
Trace.Listeners.Remove(this.traceCheckingListener);
this.traceCheckingListener = null;
}
}
#endregion
}
One way to make use of these classes is to introduce the following class in your unit-test suite assembly.
[TestClass]
public class TraceCheckerInstaller
{
private static TraceChecker traceChecker;
[AssemblyInitialize]
public static void InstallTraceChecker(TestContext context)
{
traceChecker = new TraceChecker();
}
[AssemblyCleanup]
public static void UninstallTraceChecker()
{
if (traceChecker != null)
{
traceChecker.Dispose();
traceChecker = null;
}
}
}
InstallTraceChecker will be called only once when the assembly containing the unit-tests is loaded so we use the opportunity to install the custom trace-listener. UninstallTraceChecker will be called once when the assembly is being unloaded which we use to remove the custom trace-listener.
Alternately, you may also utilize the following approach in individual unit-tests.
[TestMethod]
public void MyTestMethod()
{
using (new TraceChecker())
{
// Write code to test whatever you want to test here.
// This test will fail if any method called from here
// attemps to trace sensitive information to the log.
}
}




