Skip to content

Commit

Permalink
add property filter (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaceWindu authored Jun 29, 2023
1 parent c2dda2d commit 1ce1efb
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 84 deletions.
16 changes: 0 additions & 16 deletions src/Seq.App.Teams.AdaptiveCard/Payload.cs

This file was deleted.

63 changes: 50 additions & 13 deletions src/Seq.App.Teams.AdaptiveCard/TeamsApp.OnEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Seq.Apps;
using Seq.Apps.LogEvents;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
Expand All @@ -28,20 +30,55 @@ public sealed partial class TeamsApp

private HttpClientHandler _httpClientHandler = default!;

private Payload BuildPayload(Event<LogEventData> evt)
private Dictionary<string, object?> BuildPayload(Event<LogEventData> evt)
{
var data = new Payload(
evt.Id,
evt.TimestampUtc.ToString("O"),
evt.Data.Level.ToString(),
evt.Data.MessageTemplate,
evt.Data.RenderedMessage,
evt.Data.Exception,
evt.Data.Properties,
evt.EventType,
App.Title,
Host.InstanceName,
Host.BaseUri);
var data = new Dictionary<string, object?>()
{
{ "Id", evt.Id },
{ "TimeStamp", evt.TimestampUtc.ToString("O") },
{ "Level", evt.Data.Level.ToString() },
{ "MessageTemplate", evt.Data.MessageTemplate },
{ "Message", evt.Data.RenderedMessage },
{ "Exception", evt.Data.Exception },
{ "Properties", evt.Data.Properties },
{ "EventType", evt.EventType },
{ "AppTitle", App.Title },
{ "InstanceName", Host.InstanceName },
{ "BaseUri", Host.BaseUri },
};

foreach (var propPath in ExcludedProperties)
{
var abort = false;
var currentData = data;
for (var i = 0; i < propPath.Count && !abort; i++)
{
var name = propPath[i];
if (i == propPath.Count - 1)
{
_ = currentData.Remove(name);
}
else if (currentData.TryGetValue(name, out var value))
{
if (value is Dictionary<string, object?> dict)
{
currentData = dict;
}
else if (value is IReadOnlyDictionary<string, object?> roDict)
{
currentData[name] = currentData = roDict.ToDictionary(_ => _.Key, _ => _.Value);
}
else
{
abort = true;
}
}
else
{
abort = true;
}
}
}

if (TraceEnabled)
{
Expand Down
16 changes: 11 additions & 5 deletions src/Seq.App.Teams.AdaptiveCard/TeamsApp.Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ public sealed partial class TeamsApp
HelpText = "When set, only events with specified levels are sent to Teams. Valid Values: Verbose,Debug,Information,Warning,Error,Fatal (comma-separated)")]
public string? LogEventLevels { get; set; }


[SeqAppSetting(
DisplayName = "AdaptiveCard template",
HelpText = "You can use AdaptiveCard designer at https://adaptivecards.io/designer/ to design your card",
InputType = SettingInputType.LongText,
IsOptional = true)]
DisplayName = "AdaptiveCard template",
HelpText = "You can use AdaptiveCard designer at https://adaptivecards.io/designer/ to design your card",
InputType = SettingInputType.LongText,
IsOptional = true)]
public string? CardTemplate { get; set; }

[SeqAppSetting(
DisplayName = "Excluded Properties",
HelpText = "Specify properties to exclude from template model. Each property should be specified on separate line. Format: [property-name]+. `\\`, `]`, `\\r` and `\\n` symbols in property name should be escaped with \\: `\\\\`, `\\]`, `\\r`, `\\n`",
InputType = SettingInputType.LongText,
IsOptional = true)]
public string? PropertiesToExclude { get; set; }
}
121 changes: 119 additions & 2 deletions src/Seq.App.Teams.AdaptiveCard/TeamsApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;

namespace Seq.App.Teams;

Expand All @@ -14,7 +15,7 @@ public sealed partial class TeamsApp : SeqApp, ISubscribeToAsync<LogEventData>
private ILogger _log = default!;
private string _defaultTemplate = default!;
private HashSet<LogEventLevel>? _loggedLevels;

private IReadOnlyList<IReadOnlyList<string>>? _excludedProperties;

private HashSet<LogEventLevel> LogEventLevelList
{
Expand All @@ -25,7 +26,6 @@ private HashSet<LogEventLevel> LogEventLevelList
var result = new HashSet<LogEventLevel>();
if (!string.IsNullOrEmpty(LogEventLevels))
{

var strValues = LogEventLevels!.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (strValues?.Length > 0)
{
Expand All @@ -45,4 +45,121 @@ private HashSet<LogEventLevel> LogEventLevelList
return _loggedLevels;
}
}

private IReadOnlyList<IReadOnlyList<string>> ExcludedProperties
{
get
{
if (_excludedProperties == null)
{
var result = new List<IReadOnlyList<string>>();
if (!string.IsNullOrWhiteSpace(PropertiesToExclude))
{

var lines = PropertiesToExclude!.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

foreach (var line in lines)
{
var propertyPath = ParsePropertyPath(line.Trim());
if (propertyPath != null)
{
result.Add(propertyPath);
}
}
}

_excludedProperties = result;
}

return _excludedProperties;
}
}

public static List<string>? ParsePropertyPath(string path)
{
path = path.Trim();

if (string.IsNullOrWhiteSpace(path) || path.Length < 2 || path[0] != '[' || path[^1] != ']')
{
return null;
}

List<string>? properties = null;

var isInName = true;
var valid = true;
var sb = new StringBuilder();
for (var i = 1; i < path.Length && valid; i++)
{
switch (path[i])
{
case ']':
if (isInName)
{
(properties ??= new()).Add(sb.ToString());
sb.Length = 0;
isInName = false;
}
else
{
valid = false;
}
break;
case '[':
if (isInName)
{
_ = sb.Append('[');
}
else
{
isInName = true;
}
break;
case '\\':
if (path.Length < i + 2)
{
valid = false;
}
else
{
switch (path[i + 1])
{
case 'r':
_ = sb.Append('\r');
i++;
break;
case 'n':
_ = sb.Append('\n');
i++;
break;
case ']':
case '\\':
_ = sb.Append(path[i + 1]);
i++;
break;
default:
valid = false;
break;
}
}
break;
case '\r':
case '\n':
valid = false;
break;
default:
if (isInName)
{
_ = sb.Append(path[i]);
}
else
{
valid = false;
}
break;
}
}

return valid ? properties : null;
}
}
29 changes: 29 additions & 0 deletions tests/Seq.App.Teams.AdaptiveCard.Tests/SettingsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NUnit.Framework;

namespace Seq.App.Teams.Tests;

public sealed class SettingsTests
{
[TestCase("", null)]
[TestCase(" ", null)]
[TestCase("]", null)]
[TestCase("[]]", null)]
[TestCase("[\r]", null)]
[TestCase("[\n]", null)]
[TestCase("[\\ ]", null)]
[TestCase("[]", new[] { "" })]
[TestCase(" [] ", new[] { "" })]
[TestCase(" [][] ", new[] { "", "" })]
[TestCase("[Properties][LogName]", new[] { "Properties", "LogName" })]
[TestCase("[Prope\\\\rties]", new[] { "Prope\\rties" })]
[TestCase("[Prope\\rrties]", new[] { "Prope\rrties" })]
[TestCase("[Prope\\nrties]", new[] { "Prope\nrties" })]
[TestCase("[Prope\\]rties]", new[] { "Prope]rties" })]
[TestCase("[Prope[rties]", new[] { "Prope[rties" })]
public void TestJsonPathParser(string jsonPath, string[]? expected)
{
var result = TeamsApp.ParsePropertyPath(jsonPath);

Assert.That(result, Is.EqualTo(expected));
}
}
Loading

0 comments on commit 1ce1efb

Please sign in to comment.