Serilog override settings in appsettings.production with Net Core 2.2 - asp.net-core-2.0

I have an issue with serilog when i want to write to a database based on different environments.
Serilog override my appsettings.production.json and always take the settings in appsettings.json instead even in a production mode !
My code in my local appsettings.json
"Serilog": {
"Using": [ "Serilog.Sinks.MSSqlServer" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": {
"Console" : {"Name": "Console"},
"Sql": {
"Name": "MSSqlServer",
"Args": {
"connectionString": "Server=MyServerInDev\\SQLSERVER;Database=MyDBDev;user id=ui;password=pw_;ConnectRetryCount=0;MultipleActiveResultSets=true",
"tableName": "Log",
"autoCreateSqlTable": true
}
}
},
appsettings.production.json
"Serilog": {
"Using": [ "Serilog.Sinks.MSSqlServer" ],
"MinimumLevel": {
"Default": "Warning",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": {
"Console" : {"Name": "Console"},
"Sql": {
"Name": "MSSqlServer",
"Args": {
"connectionString": "Server=MyServerInProd\\SQLSERVER;Database=MyDBProdv;user id=ui;password=pw_;ConnectRetryCount=0;MultipleActiveResultSets=true",
"tableName": "Log",
"autoCreateSqlTable": true
}
}
},
My program.cs
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddEnvironmentVariables()
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
I am missing something ? and why serilog take the appsettings.json settings and not appsettings.production.json even in prod mode
Note that the other settings in the appsettings.production.json work fine. Only the settings in serilog is causing the problem

Found it. The reason is that Serilog adds sink configs to array, and each of two files of appsettings just adds another sinks instead of replacing them. Solution is explicitly indicate array indexes for each sink. Credits for answer: nblumhardt. Tested, works.
"WriteTo": {
"0": {
"Name": "File",
"Args": {
"path": "./logs/myapp-.txt",
"rollingInterval": "Day"
}
}
},

Related

Overriding NLog settings with Azure Configuration

I have an app running in Azure and logging to a database. I've got the NLog settings coming from the appsettings.json file and everything seems to be working well. However, now I'd like to override the logging rules by adding an application setting to the app configuration in Azure and it's failing.
This is the NLog entry in the app settings.json:
"NLog": {
"autoReload": true,
"throwConfigExceptions": true,
//"internalLogLevel": "Info",
//"internalLogFile": "x:/internal-nlog.txt",
"extensions": [
{ "assembly": "NLog.Extensions.Logging" },
{ "assembly": "NLog.Web.AspNetCore" },
{ "assembly": "NLog.Database" }
],
"targets": {
"async": true,
"database": {
"type": "Database",
"dbProvider": "System.Data.SqlClient",
"connectionString": "<Connection String>",
"keepConnection": "true",
"commandText": "insert into LoggingMessages(Created, MessageType, Message, CallSite, ExceptionDetail) values(getutcdate(), #level, #message, #callsite, #exception);",
"parameters": [
{
"name": "#level",
"layout": "${level}"
},
{
"name": "#message",
"layout": "${message}"
},
{
"name": "#logger",
"layout": "${logger}"
},
{
"name": "#callsite",
"layout": "${callsite}"
},
{
"name": "#exception",
"layout": "${exception:tostring}"
}
]
},
"logconsole": {
"type": "Console"
}
},
"rules": [
{
"logger": "microsoft.*",
"maxLevel": "Debug",
"final": true
},
{
"logger": "*",
"minLevel": "Trace",
"writeTo": "database"
}
]
}
This line is in the Configuration method of my startup.cs:
var logger = LogManager.Setup()
.LoadConfigurationFromAppSettings()
.GetCurrentClassLogger();
And my CreateHostBuilder method in program.cs looks like this:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseNLog();
Within the App Configuration screen on the Azure Portal I've then created the following Application Setting entry:
Name
Value
NLog:rules
[{"logger": "microsoft.","maxLevel": "Debug","final": true},{"logger": "","minLevel": "Warning","writeTo": "database"}]
I'm thinking that either the call to LoadConfigurationFromAppSettings() either happens too early or it ignores the updated values, but of course, I could just be completely wrong in what I'm trying to do.
Any help would be great
Maybe add an Application Setting to the Azure App Configuration:
Name
Value
LoggingLevel
Debug
And use ${configsetting:LoggingLevel} in the NLog Logging Rule:
"NLog": {
"autoReload": true,
"throwConfigExceptions": true,
"extensions": [
{ "assembly": "NLog.Extensions.Logging" },
{ "assembly": "NLog.Web.AspNetCore" },
{ "assembly": "NLog.Database" }
],
"targets": {
"async": true,
"database": {
"type": "Database",
"dbProvider": "System.Data.SqlClient",
"connectionString": "<Connection String>",
"keepConnection": "true",
"commandText": "insert into LoggingMessages(Created, MessageType, Message, CallSite, ExceptionDetail) values(getutcdate(), #level, #message, #callsite, #exception);",
"parameters": [
{
"name": "#level",
"layout": "${level}"
},
{
"name": "#message",
"layout": "${message}"
},
{
"name": "#logger",
"layout": "${logger}"
},
{
"name": "#callsite",
"layout": "${callsite}"
},
{
"name": "#exception",
"layout": "${exception:tostring}"
}
]
},
"logconsole": {
"type": "Console"
}
},
"rules": [
{
"logger": "microsoft.*",
"maxLevel": "Debug",
"final": true
},
{
"logger": "*",
"minLevel": "${configsetting:LoggingLevel:whenEmpty=Trace}",
"writeTo": "database"
}
]
}
See also: https://github.com/NLog/NLog/wiki/Filtering-log-messages#semi-dynamic-routing-rules
See also: https://github.com/NLog/NLog/wiki/ConfigSetting-Layout-Renderer
The key-value ("NLog:rules") you created in Azure App Configuration is a JSON object. Make sure you set JSON content-type for it. Otherwise, it will be loaded as a string. For more information, see Use content type to store JSON key-values in App Configuration.
I don't see your code that loads data from Azure App Configuration.
Update your code to use App Configuation by following Quickstart: Create an ASP.NET Core app with Azure App Configuration.
I'm not very familiar with NLog, but you need to make sure NLog can load configuration from the system IConfiguration.
So I couldn't get this to work the way I thought it should - just replacing the current rules - but I got it to work in the way that I need it. All I really wanted to do was change the minimum level for the database rule so that I could adjust this without having to update the code.
What I ended up doing was adding an Application Setting in the Azure App Configuration:
|Name|Value|
|----|-----|
|LoggingLevel|Debug|
then adding some code similar to the following to the Configuration method of the startup.cs after the existing LogManager setup line.
var loggingLevel = Configuration.GetValue<string>("LoggingLevel");
var haveNewLoggingLevel = !string.IsNullOrWhiteSpace(loggingLevel);
// only interested in the 'database' target
var databaseTarget = LogManager.Configuration.FindTargetByName("database") as DatabaseTarget;
if ((databaseTarget != null) && haveNewLoggingLevel)
{
var rules = LogManager.Configuration.LoggingRules;
foreach (var rule in rules)
{
if (rule.Targets.Contains(databaseTarget))
{
rule.SetLoggingLevels(NLog.LogLevel.FromString(loggingLevel), NLog.LogLevel.Fatal);
}
}
LogManager.ReconfigExistingLoggers();
}
With this I was able to achieve what I needed, butt I'm not sure it answers the question I originally asked. I also added similar code to update the connection string to the database from App Settings.
To override a nested value inside the appsettings.json using Azure App Configuration then one must use __.
For example ApplicationInsights__InstrumentationKey will override this value in appsettings.json
"ApplicationInsights": {
"InstrumentationKey": "Will be overrridden"
}
See also: https://learn.microsoft.com/en-us/azure/app-service/configure-common?tabs=portal
Notice to override nested value inside json-array requires extra magic (Ex. a specific NLog-Rule). You need to decorate with json-array-index-name. See also: https://github.com/NLog/NLog.Extensions.Logging/wiki/NLog-configuration-with-appsettings.json#logging-rule-override

Using outputTemplate argument in Serilog with Azure Application Insights

I'm currently implementing Azure Application Insights logging with Serilog which is working fine except for when I use an output template in my Serilog configuration. It seems like the template is ignored when passing the Serilog data to Application insights.
My serilog config in appsetting.json:
"Serilog": {
"Using": [ "Serilog.Sinks.ApplicationInsights" ],
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "Console" },
{
"Name": "RollingFile",
"Args": {
"pathFormat": "logs\\log-{Date}.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
}
},
{
"Name": "ApplicationInsights",
"Args": {
"restrictedToMinimumLevel": "Information",
"telemetryConverter": "Serilog.Sinks.ApplicationInsights.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights",
"outputTemplate": "Test Template - {Message}"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "app"
}
},
The logging statement:
logger.Error("Test Serilog Error For AI - " + DateTime.Now);
The output within application insights:
Is this the correct approach to customising an error message for Application insights?
After checking the source code serilog-sinks-applicationinsights, you will find it did not read the outputTemplate from appsetting.json.
For a workaround, you may implement custom TemplateTraceTelemetryConverter.
TemplateTraceTelemetryConverter
public class TemplateTraceTelemetryConverter : TraceTelemetryConverter
{
public override IEnumerable<ITelemetry> Convert(LogEvent logEvent, IFormatProvider formatProvider)
{
var templateParser = new MessageTemplateParser();
var template = templateParser.Parse($"Test Template - {logEvent.MessageTemplate.Text}");
LogEvent newLogEvent = new LogEvent(logEvent.Timestamp
, logEvent.Level
, logEvent.Exception
, template
, logEvent.Properties.Select(p => new LogEventProperty(p.Key, p.Value)));
return base.Convert(newLogEvent, formatProvider);
}
}
Use TemplateTraceTelemetryConverter
"Serilog": {
"Using": [
"Serilog.Sinks.ApplicationInsights",
],
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "Test Template - {Message}"
}
},
{
"Name": "RollingFile",
"Args": {
"pathFormat": "logs\\log-{Date}.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
}
},
{
"Name": "ApplicationInsights",
"Args": {
"restrictedToMinimumLevel": "Error",
"telemetryConverter": "YourProjectNamespace.TemplateTraceTelemetryConverter, YourProjectNamespace"
//"outputTemplate": "Test Template - {Message}"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "app"
}
}

Azure Function failing to initialise Serilog Sink when using appsettings.json

I've created a simple Azure Function with the intension of using Serililog for logging to Azure Blob Storage.
When using inline configuration for the Serilog sink, that works perfectly fine, the sink is created and Serilog happily wirtes to Blob storage.
This works:
Log.Logger = new LoggerConfiguration()
.WriteTo.AzureBlobStorage(
"STORAGEACCOUNT CONNECTION STRING", // <-- inline config
LogEventLevel.Information,
null,
"{yyyy}/{MM}/{dd}/MyLogFile.txt")
.CreateLogger();
The problem is that I would like to configure this all via appsettings.json, but when I try this (both locally running in the emulator & in the cloud), it fails to bind the sink settings, and as a result its failing to log to to the Blog storage.
If I debug, I can see that the config values are being loaded as expected into the configuration object, but they are not being applied to the logging configuration.
This doesnt work:
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
appsettings.json in this snippet:
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information"
}
},
"WriteTo": [{
"Name": "AzureBlobStorage",
"Args": {
"connectionString": " --- REPLACE WITH STORAGEACCOUNT BLOB CONNECTION STRING --- ",
"formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact",
"storageFileName": "{yyyy}/{MM}/{dd}/MyLogFile.txt",
"retainedFileCountLimit": 31
}
}
],
"Properties": {
"Application": "int-test-logging",
"Environment": "int"
}
}
}
I'm not sure what I'm doing incorrectly but any help would be appreciated. The following github repo contains code to implement the above (both approaches), and reproduces this behaviour. https://github.com/oneiltomlinson/AzureFunctionsLogging
Just add
"Using": [ "Serilog.Sinks.AzureBlobStorage" ] //under Serilog Config
and it will work!
Working Config
{
"Serilog": {
"Using": [
"Serilog.Sinks.AzureBlobStorage"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information"
}
},
"WriteTo": [
{
"Name": "AzureBlobStorage",
"Args": {
"connectionString": " --- REPLACE WITH STORAGEACCOUNT BLOB CONNECTION STRING --- ",
"formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact",
"storageFileName": "{yyyy}/{MM}/{dd}/MyLogFile.txt",
"retainedFileCountLimit": 31
}
}
],
"Properties": {
"Application": "int-test-logging",
"Environment": "int"
}
}
}
I've the same issue and even with all the required "using" of the used sinks, nothing change. This is my configuration file (just about the Serilog part):
"Serilog": {
"Using": [
"Serilog.Sinks.RollingFile",
"Serilog.Sinks.AzureTableStorage"
],
"MinimumLevel": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
},
"WriteTo": [
{
"Name": "Logger",
"Args": {
"configureLogger": {
"MinimumLevel": "Information",
"WriteTo": [
{
"Name": "Console",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [TID:{ThreadId}] - {Message:lj}{NewLine}{Exception}"
},
{
"Name": "Debug",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [TID:{ThreadId}] - {Message:lj}{NewLine}{Exception}"
},
{
"Name": "AzureTableStorage",
"Args": {
"storageTableName": "HeroFunctionsLogs",
"connectionString": "DefaultEndpointsProtocol=https;AccountName=herobugari;AccountKey=NYJcdoCg9mQxqbQaTUdIxOHYQGdcKAwMsuWwzDub29UweHR1c+wd6Sh3IDQNB+eCx3DAe/ccobxu67sJvqQB5g==",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [TID:{ThreadId}] - {Message:lj}{NewLine}{Exception}",
"restrictedToMinimumLevel": "Information",
"writeInBatches": true,
"batchPostingLimit": 100
}
},
{
"Name": "RollingFile",
"Args": {
"pathFormat": "logs\\Bulgari.Hero.Functions.log",
"retainedFileCountLimit": 10,
"buffered": true,
"flushToDiskInterval": 5,
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [TID:{ThreadId}] - {Message:lj}{NewLine}{Exception}"
}
}
],
"Filter": [
{
"Name": "ByExcludingOnly",
"Args": {
"expression": "SourceContext like '%Microsoft.EntityFrameworkCore%'"
}
}
]
}
}
}
]
}
and this the Startup.cs code that reads and try to use it:
var config = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables().Build();
// Registering Serilog provider
logger = new LoggerConfiguration()
//.WriteTo.Console()
//.WriteTo.File("logs\\herofunctions.log", rollingInterval: RollingInterval.Day)
.ReadFrom.Configuration(config)
.CreateLogger();
builder.Services.AddLogging(lb => lb.AddSerilog(logger));
The logger object has no "sinks" registered as it happens in the question.
I have solved exactly the same issue here by adding entries in local.settings.json.
"Serilog:Using:0": "Serilog.Sinks.Console",
"Serilog:MinimumLevel": "Information",
"Serilog:Override:Microsoft": "Warning",
"Serilog:Override:System": "Warning",
"Serilog:WriteTo:Console:Name": "Console"
then in StartUp.cs:
var provider = builder.Services.BuildServiceProvider();
var config = provider.GetRequiredService<IConfiguration>();
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(config).CreateLogger();
then the function:
public class TestFunction
{
private readonly ILogger _logger;
public TestFunction()
{
_logger = Log.Logger;
}
[FunctionName("TestFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req)
{
_logger.Information("Hello");
return new OkResult();
}
}
}
I can see the config entries registered and entries are logged!
To write to SQL Server I had to add:
"Using": [
"Serilog.Sinks.MSSqlServer"
],

Configure log4js logger to log only INFO to console and everything to file

With below-mentioned log4js configuration, I intend to log everything to a file in logs/test/log-timestamp.txt file and only INFO level to console, but I see that the console prints all the levels. How do I fix this?
{
"appenders": {
"test": {
"type": "dateFile",
"filename": "logs/test/log",
"pattern": "-yyyy-MM-dd-hh-mm-ss.txt",
"alwaysIncludePattern": true,
"keepFileExt": true
},
"test-filter": {
"type": "logLevelFilter",
"appender": "test",
"level": "trace",
"maxLevel": "fatal"
},
"default": {
"type": "dateFile",
"filename": "logs/default",
"pattern": "-yyyy-MM-dd.txt",
"alwaysIncludePattern": true,
"keepFileExt": true
},
"console": {
"type": "console",
"level": "info",
"maxLevel": "info"
}
},
"categories": {
"default": {
"appenders": [
"default"
],
"level": "info"
},
"test": {
"appenders": [
"test-filter",
"console"
],
"level": "debug"
}
}
}

The .Net core extension SeriLog filtering is not working

I'm using .net Core 2.0.9 and Serilog.Filters.Expressions 2.0.0.
I configured my appsettings.json to write in a log table in the Database.The data are recorded successfuly in the database but the RequestPath property is always null :
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Debug"
}
},
"WriteTo": [
{
"Name": "MSSqlServer",
"Args": {
"connectionString": "myconnectionString",
"tableName": "Log"
}
}
],
"WriteTo:Async": {
"Name": "Async",
"Args": {
"configure": [
{
"Name": "File",
"Args": {
"path": "..\\output\\log.txt",
"rollingInterval": "Day"
}
}
]
}
},
"Using": [ "Serilog.Settings.Configuration" ]
"Filter": [
{
"Name": "ByIncludingOnly",
"Args": {
"expression": "RequestPath like '%/api/book%'"
}
}
]
},
But i want to filter and save only log entries that have a specific api path. In this case, just entries that contain the api/user path in the RequestPath. But no data are saved anymore and i have no log errors, any idea why ?
Here are working steps for me, check the difference:
appsettings.json
"Serilog": {
"MinimumLevel": "Information",
"Override": {
"Microsoft": "Critical"
},
"WriteTo": [
{
"Name": "MSSqlServer",
"Args": {
"connectionString": "Data Source=xx",
"autoCreateSqlTable ": true,
"tableName": "Logs",
"autoCreateSqlTable": true,
"columnOptionsSection": {
"removeStandardColumns": [ "Properties" ],
"customColumns": [
{
"ColumnName": "Release",
"DataType": "varchar",
"DataLength": 32
},
{
"ColumnName": "RequestPath",
"DataType": "varchar"
},
{
"ColumnName": "ConnectionId",
"DataType": "varchar"
}
]
}
}
},
{
"Name": "RollingFile",
"Args": {
"pathFormat": "Logs/app-{Date}.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message} {UserName} {ActionName} {NewLine} {Exception}"
}
}
],
"Using": [ "Serilog.Settings.Configuration" ],
"Filter": [
{
"Name": "ByIncludingOnly",
"Args": {
"expression": "RequestPath like '%/api%'"
}
}
]
},
Startup.cs
Log.Logger = new LoggerConfiguration()
.ReadFrom.ConfigurationSection(Configuration.GetSection("Serilog"))
.CreateLogger();
For checking serilog error, adding code below:
Log.Logger = new LoggerConfiguration()
.ReadFrom.ConfigurationSection(Configuration.GetSection("Serilog"))
.CreateLogger();
Serilog.Debugging.SelfLog.Enable(msg =>
{
Debug.Print(msg);
Debugger.Break();
});

Resources