Last active
June 3, 2016 01:53
-
-
Save nblumhardt/bd74fafc61d0c50ec07e0f3715df0d00 to your computer and use it in GitHub Desktop.
Visitor pattern implementation for exploring/printing/rewriting structured data from Serilog
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Serilog.Events; | |
namespace SerilogStructuredDataToolkit | |
{ | |
public abstract class LogEventPropertyValueVisitor<TResult> | |
{ | |
public virtual TResult Visit(LogEventPropertyValue value) | |
{ | |
if (value == null) throw new ArgumentNullException(nameof(value)); | |
var sv = value as ScalarValue; | |
if (sv != null) | |
return VisitScalarValue(sv); | |
var seqv = value as SequenceValue; | |
if (seqv != null) | |
return VisitSequenceValue(seqv); | |
var strv = value as StructureValue; | |
if (strv != null) | |
return VisitStructureValue(strv); | |
var dictv = value as DictionaryValue; | |
if (dictv != null) | |
return VisitDictionaryValue(dictv); | |
return VisitUnsupportedValue(value); | |
} | |
protected abstract TResult VisitScalarValue(ScalarValue scalar); | |
protected abstract TResult VisitSequenceValue(SequenceValue sequence); | |
protected abstract TResult VisitStructureValue(StructureValue structure); | |
protected abstract TResult VisitDictionaryValue(DictionaryValue dictionary); | |
protected virtual TResult VisitUnsupportedValue(LogEventPropertyValue value) | |
{ | |
if (value == null) throw new ArgumentNullException(nameof(value)); | |
throw new NotSupportedException($"The value {value} is not of a supported type."); | |
} | |
} | |
public class LogEventPropertyValueTransfomer : LogEventPropertyValueVisitor<LogEventPropertyValue> | |
{ | |
protected override LogEventPropertyValue VisitScalarValue(ScalarValue scalar) | |
{ | |
if (scalar == null) throw new ArgumentNullException(nameof(scalar)); | |
return scalar; | |
} | |
protected override LogEventPropertyValue VisitSequenceValue(SequenceValue sequence) | |
{ | |
if (sequence == null) throw new ArgumentNullException(nameof(sequence)); | |
var contents = new LogEventPropertyValue[sequence.Elements.Count]; | |
for (var i = 0; i < contents.Length; ++i) | |
{ | |
contents[i] = Visit(sequence.Elements[i]); | |
} | |
return new SequenceValue(contents); | |
} | |
protected override LogEventPropertyValue VisitStructureValue(StructureValue structure) | |
{ | |
if (structure == null) throw new ArgumentNullException(nameof(structure)); | |
var properties = new LogEventProperty[structure.Properties.Count]; | |
for (var i = 0; i < properties.Length; ++i) | |
{ | |
var property = structure.Properties[i]; | |
properties[i] = new LogEventProperty(property.Name, Visit(property.Value)); | |
} | |
return new StructureValue(properties, structure.TypeTag); | |
} | |
protected override LogEventPropertyValue VisitDictionaryValue(DictionaryValue dictionary) | |
{ | |
if (dictionary == null) throw new ArgumentNullException(nameof(dictionary)); | |
var elements = new Dictionary<ScalarValue, LogEventPropertyValue>(dictionary.Elements.Count); | |
foreach (var element in dictionary.Elements) | |
{ | |
elements[element.Key] = Visit(element.Value); | |
} | |
return new DictionaryValue(elements); | |
} | |
} | |
public class NamedPropertyFinder : LogEventPropertyValueVisitor<bool> | |
{ | |
readonly Func<string, bool> _predicate; | |
public NamedPropertyFinder(Func<string, bool> predicate) | |
{ | |
if (predicate == null) throw new ArgumentNullException(nameof(predicate)); | |
_predicate = predicate; | |
} | |
protected override bool VisitScalarValue(ScalarValue scalar) | |
{ | |
return false; | |
} | |
protected override bool VisitSequenceValue(SequenceValue sequence) | |
{ | |
return sequence.Elements.Any(Visit); | |
} | |
protected override bool VisitStructureValue(StructureValue structure) | |
{ | |
return structure.Properties.Any(p => _predicate(p.Name)) || | |
structure.Properties.Any(p => Visit(p.Value)); | |
} | |
protected override bool VisitDictionaryValue(DictionaryValue dictionary) | |
{ | |
return dictionary.Elements.Any(e => Visit(e.Value)); | |
} | |
} | |
public class NamedPropertyFilter : LogEventPropertyValueTransfomer | |
{ | |
readonly NamedPropertyFinder _finder; | |
readonly Func<string, bool> _isIncludedPropertyName; | |
public NamedPropertyFilter(Func<string, bool> isIncludedPropertyName) | |
{ | |
if (isIncludedPropertyName == null) throw new ArgumentNullException(nameof(isIncludedPropertyName)); | |
_isIncludedPropertyName = isIncludedPropertyName; | |
_finder = new NamedPropertyFinder(n => !_isIncludedPropertyName(n)); | |
} | |
public void Apply(LogEvent logEvent) | |
{ | |
List<string> toRemove = null; | |
List<KeyValuePair<string, LogEventPropertyValue>> toRewrite = null; | |
foreach (var property in logEvent.Properties) | |
{ | |
if (!_isIncludedPropertyName(property.Key)) | |
{ | |
toRemove = toRemove ?? new List<string>(); | |
toRemove.Add(property.Key); | |
} | |
else if (_finder.Visit(property.Value)) | |
{ | |
toRewrite = toRewrite ?? new List<KeyValuePair<string, LogEventPropertyValue>>(); | |
toRewrite.Add(property); | |
} | |
} | |
if (toRewrite != null) | |
{ | |
foreach (var keyValuePair in toRewrite) | |
{ | |
var rewritten = Visit(keyValuePair.Value); | |
logEvent.AddOrUpdateProperty(new LogEventProperty(keyValuePair.Key, rewritten)); | |
} | |
} | |
if (toRemove != null) | |
{ | |
foreach (var name in toRemove) | |
{ | |
logEvent.RemovePropertyIfPresent(name); | |
} | |
} | |
} | |
protected override LogEventPropertyValue VisitStructureValue(StructureValue structure) | |
{ | |
return new StructureValue( | |
structure.Properties.Where(p => _isIncludedPropertyName(p.Name)) | |
.Select(p => new LogEventProperty(p.Name, Visit(p.Value))), | |
structure.TypeTag); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Suggestions:
Implement an
.Accept
method on each value type that accepts an expression visitor and calls it back - i.e. use double dispatch instead of all the casting and checking for null (which is easy to break if you add a new value type).Use an interface for the visitor so someone can provide their own implementation without being forced to inherit from this one.
Provide a base class default visitor that walks the tree but returns the original, then someone can override just one method to handle a targeted re-write of a specific value type (e.g. censoring sensitive values).