Better exception logging with Serilog.Exceptions library

This commit is contained in:
Robert McRackan 2022-03-03 16:54:43 -05:00
parent 424d939c15
commit 0a0f60192b
5 changed files with 159 additions and 29 deletions

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>6.6.8.1</Version> <Version>6.6.9.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -56,7 +56,7 @@ namespace AppScaffolding
// migrations go below here // migrations go below here
// //
Migrations.migrate_to_v6_5_2(config); Migrations.migrate_to_v6_6_9(config);
} }
public static void PopulateMissingConfigValues(Configuration config) public static void PopulateMissingConfigValues(Configuration config)
@ -107,29 +107,12 @@ namespace AppScaffolding
if (config.GetObject("Serilog") != null) if (config.GetObject("Serilog") != null)
return; return;
// "Serilog": {
// "MinimumLevel": "Information"
// "WriteTo": [
// {
// "Name": "Console"
// },
// {
// "Name": "File",
// "Args": {
// "rollingInterval": "Day",
// "outputTemplate": ...
// }
// }
// ],
// "Using": [ "Dinah.Core" ],
// "Enrich": [ "WithCaller" ]
// }
var serilogObj = new JObject var serilogObj = new JObject
{ {
{ "MinimumLevel", "Information" }, { "MinimumLevel", "Information" },
{ "WriteTo", new JArray { "WriteTo", new JArray
{ {
new JObject { {"Name", "Console" } }, // new JObject { {"Name", "Console" } }, // this has caused more problems than it's solved
new JObject new JObject
{ {
{ "Name", "File" }, { "Name", "File" },
@ -144,14 +127,16 @@ namespace AppScaffolding
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] Begin Libation // output example: 2019-11-26 08:48:40.224 -05:00 [DBG] Begin Libation
// - with class and method info: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}"; // - with class and method info: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation // output example: 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation
{ "outputTemplate", "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}" } // {Properties:j} needed for expanded exception logging
{ "outputTemplate", "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception} {Properties:j}" }
} }
} }
} }
} }
}, },
{ "Using", new JArray{ "Dinah.Core" } }, // dll's name, NOT namespace // better exception logging with: Serilog.Exceptions library -- WithExceptionDetails
{ "Enrich", new JArray{ "WithCaller" } }, { "Using", new JArray{ "Dinah.Core", "Serilog.Exceptions" } }, // dll's name, NOT namespace
{ "Enrich", new JArray{ "WithCaller", "WithExceptionDetails" } },
}; };
config.SetObject("Serilog", serilogObj); config.SetObject("Serilog", serilogObj);
} }
@ -166,9 +151,9 @@ namespace AppScaffolding
// capture most Console.WriteLine() and write to serilog. See below tests for details. // capture most Console.WriteLine() and write to serilog. See below tests for details.
// Some dependencies print helpful info via Console.WriteLine. We'd like to log it. // Some dependencies print helpful info via Console.WriteLine. We'd like to log it.
// //
// Serilog also writes to Console so this might be asking for trouble. ie: infinite loops. // If Serilog also writes to Console, this might be asking for trouble. ie: infinite loops.
// SerilogTextWriter needs to be more robust and tested. Esp the Write() methods. // To use that way, SerilogTextWriter needs to be more robust and tested. Esp the Write() methods.
// Empirical testing so far has shown no issues. // However, empirical testing so far has shown no issues.
Console.SetOut(new MultiTextWriter(origOut, new SerilogTextWriter())); Console.SetOut(new MultiTextWriter(origOut, new SerilogTextWriter()));
#region Console => Serilog tests #region Console => Serilog tests
@ -346,9 +331,51 @@ namespace AppScaffolding
}; };
#endregion #endregion
public static void migrate_to_v6_5_2(Configuration config) public static void migrate_to_v6_6_9(Configuration config)
{ {
// example var writeToPath = $"Serilog.WriteTo";
// remove WriteTo[].Name == Console
{
if (UNSAFE_MigrationHelper.Settings_TryGetArrayLength(writeToPath, out var length1))
{
for (var i = length1 - 1; i >= 0; i--)
{
var exists = UNSAFE_MigrationHelper.Settings_TryGetFromJsonPath($"{writeToPath}[{i}].Name", out var value);
if (exists && value == "Console")
UNSAFE_MigrationHelper.Settings_RemoveFromArray(writeToPath, i);
}
}
}
// add Serilog.Exceptions -- WithExceptionDetails
{
// outputTemplate should contain "{Properties:j}"
{
// re-calculate. previous loop may have changed the length
if (UNSAFE_MigrationHelper.Settings_TryGetArrayLength(writeToPath, out var length2))
{
var propertyName = "outputTemplate";
for (var i = 0; i < length2; i++)
{
var jsonPath = $"{writeToPath}[{i}].Args";
var exists = UNSAFE_MigrationHelper.Settings_TryGetFromJsonPath($"{jsonPath}.{propertyName}", out var value);
var newValue = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception} {Properties:j}";
if (exists && value != newValue)
UNSAFE_MigrationHelper.Settings_SetWithJsonPath(jsonPath, propertyName, newValue);
}
}
}
// Serilog.Using must include "Serilog.Exceptions"
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Using", "Serilog.Exceptions");
// Serilog.Enrich must include "WithExceptionDetails"
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Enrich", "WithExceptionDetails");
}
} }
} }
} }

View File

@ -113,6 +113,108 @@ namespace AppScaffolding
return success; return success;
} }
public static bool Settings_JsonPathIsType(string jsonPath, JTokenType jTokenType)
{
JToken val = null;
process_SettingsJson(jObj => val = jObj.SelectToken(jsonPath), false);
return val?.Type == jTokenType;
}
public static bool Settings_TryGetFromJsonPath(string jsonPath, out string value)
{
JToken val = null;
process_SettingsJson(jObj => val = jObj.SelectToken(jsonPath), false);
if (val?.Type == JTokenType.String)
{
value = val.Value<string>();
return true;
}
else
{
value = null;
return false;
}
}
public static void Settings_SetWithJsonPath(string jsonPath, string propertyName, string newValue)
{
if (!Settings_TryGetFromJsonPath($"{jsonPath}.{propertyName}", out _))
return;
process_SettingsJson(jObj =>
{
var token = jObj.SelectToken(jsonPath);
if (token is null
|| token is not JObject o
|| o[propertyName] is null)
return;
var oldValue = token.Value<string>(propertyName);
if (oldValue != newValue)
token[propertyName] = newValue;
});
}
public static bool Settings_TryGetArrayLength(string jsonPath, out int length)
{
length = 0;
if (!Settings_JsonPathIsType(jsonPath, JTokenType.Array))
return false;
JArray array = null;
process_SettingsJson(jObj => array = (JArray)jObj.SelectToken(jsonPath));
length = array.Count;
return true;
}
public static void Settings_AddToArray(string jsonPath, string newValue)
{
if (!Settings_JsonPathIsType(jsonPath, JTokenType.Array))
return;
process_SettingsJson(jObj =>
{
var array = (JArray)jObj.SelectToken(jsonPath);
array.Add(newValue);
});
}
/// <summary>Do not add if already exists</summary>
public static void Settings_AddUniqueToArray(string arrayPath, string newValue)
{
if (!Settings_TryGetArrayLength(arrayPath, out var qty))
return;
for (var i = 0; i < qty; i++)
{
var exists = Settings_TryGetFromJsonPath($"{arrayPath}[{i}]", out var value);
if (exists && value == newValue)
return;
}
Settings_AddToArray(arrayPath, newValue);
}
/// <summary>only remove if not exists</summary>
public static void Settings_RemoveFromArray(string jsonPath, int position)
{
if (!Settings_JsonPathIsType(jsonPath, JTokenType.Array))
return;
process_SettingsJson(jObj =>
{
var array = (JArray)jObj.SelectToken(jsonPath);
if (position < array.Count)
array.RemoveAt(position);
});
}
/// <summary>only insert if not exists</summary> /// <summary>only insert if not exists</summary>
public static void Settings_Insert(string key, string value) public static void Settings_Insert(string key, string value)
=> process_SettingsJson(jObj => jObj.TryAdd(key, value)); => process_SettingsJson(jObj => jObj.TryAdd(key, value));

View File

@ -270,7 +270,7 @@ namespace LibationFileManager
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString()); var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
if (!valueWasChanged) if (!valueWasChanged)
{ {
Log.Logger.Information("LogLevel.set attempt. No change"); Log.Logger.Debug("LogLevel.set attempt. No change");
return; return;
} }

View File

@ -6,6 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>