Better exception logging with Serilog.Exceptions library
This commit is contained in:
parent
424d939c15
commit
0a0f60192b
@ -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>
|
||||||
|
|||||||
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user