How to Mask Sensitive Data in NLog | by Jeffery Cheng | Jul, 2022

Make the profile data of users more secure in the log file

0*ghiKeMUM cAG8uCD
Photo by Dan Nelson on Unsplash

The sensitive data unavoidably exist in our log content, such as phone numbers, identity card numbers, credit card number, etc.

We must mask the sensitive data in our log to secure users’ profile data.

No one wants his phone or credit card number leaked by the log file, right?

There are two ways to mask sensitive data.

I will explain what’s their advantages and disadvantages.

There are various scenarios in different log content.

  1. Structured log

The LogTargetclass is a POCO including two properties PhoneNoand IdentityNo.

var target = new LogTarget();

_logger.LogInformation("---Structured {@Target}---",target);

We will get the following text in the log file.

---Structured {"PhoneNo":"12345678", "IdentityNo":"A33123123"}---

2. Pure text content (for example, query string or JSON serialized content)

var queryString = QueryString.Create(new List<KeyValuePair<string, string?>>

_logger.LogInformation($"---Other {queryString}---");

Get the following text in the log file.

---Other ?PhoneNo=12345678&IdentityNo=A33123123---

Our goal is to mask the PhoneNOto 123***78 and IdentityNoto A33***123.

In the next two-part, I will explain how to do that differently.

In NLog 4.7 introduced the extension method RegisterObjectTransformation.

We can easily use the method to mask our log content by the following code.

We get the following content.

---Structured {"PhoneNo":"123***78", "IdentityNo":"A33***123"}---
---Other ?PhoneNo=12345678&IdentityNo=A33123123---

As you can see, the approach only works for structured logs, not for pure text.

Although this approach is simple, it doesn’t work for the general scenario.

Let’s try another approach.

Getting your wanted masked value is the biggest problem if you don’t use approach 1.

I will write a string manipulation logic to deal with the tricky problem.

My first thought is to treat all log content as pure text and find the common rule in different kinds of log content.

And then split the log content by the particular rule string for getting the actual value.

For example, the particular rule string of the structured log or JSON serialized text is " ; the particular rule of the URL query string is & .

The following is a demonstration JSON serialized text.


When we split the text by ", we get the following string array.

{ key1 : value1 , key2 : value2 }

Assuming our masked target is the value of the key key1 .

Find the index of key1 is 1, plus 2 to 3, the index 3 in the string array is the value of the key key1 .

Replace the index 3 in the string array with your wanted masked value.

{ key1 : maskedvalue1 , key2 : value2 }

Rejoin the string array and the split rule to get the complete log content.


When we replaced the log content, we wrote a custom SensitiveMaskLayoutRendererto render the log before the log wrote in the log file.

Let’s take a look at the implementation code

1. We need a dictionary to store what kinds of strings are our masked targets (In this example, is PhoneNoand IdentiyNo).

The dictionary is generic <string, Func<string,string>> .

The dictionary’s key is our masked target, and the value is the Func of what kind of the masked value we wanted.

Creating the class NLogSensitiveDataMaskOptions and creating a property IDicionary<string, Func<string,string>> MaskDataDic.

2. Creating a class SensitiveMaskLayoutRendererand inheriting LayoutRenderer.

Remark:In this code example, I don't implement the code of MaskSensitiveData.Only append the *masked* string behind the original log content.

3. Creating an extension method to register the layout render to the NLog.

4. Register your masked targets into the service container

Register into container

5. Add the sensitive-maskrender outside the Inner message in the nlog.config

Printing the log!

Check your log file, and we will get the masked log; whether your log content is structured or other, the approach could achieve the goal.

---Structured {"PhoneNo":"12345678", "IdentityNo":"A33123123"}--- *masked*---Other ?PhoneNo=12345678&IdentityNo=A33123123--- *masked*

In approach 1, the advantage is that you can mask your sensitive data quickly, but the disadvantage only applies to a structured log.

In approach 2, the advantage is that you can make it applicable to every situation, but the disadvantage is that when adding a new rule, you need to write a new code.

You can choose approach 1. without hesitation if your log content is always structured.

If your log content is complicated, maybe you can reference my approach 2.

I hope this article can solve your problem.

News Credit

%d bloggers like this: