Skip to content

Instantly share code, notes, and snippets.

@nblumhardt
Last active June 3, 2016 01:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nblumhardt/bd74fafc61d0c50ec07e0f3715df0d00 to your computer and use it in GitHub Desktop.
Save nblumhardt/bd74fafc61d0c50ec07e0f3715df0d00 to your computer and use it in GitHub Desktop.
Visitor pattern implementation for exploring/printing/rewriting structured data from Serilog
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);
}
}
}
@IanMercer
Copy link

Suggestions:

  1. 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).

  2. Use an interface for the visitor so someone can provide their own implementation without being forced to inherit from this one.

  3. 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment