Logging important info in WebAPI 2 using Serilog - c#

Just started working with Serilog, Its quite nice but I am a bit confused.
In case of errors in my Controller's actions, I'd like to log as much useful information as possible from the request (headers, parameters, etc).
What would be the best way to go about it?

You could look at using the Enrichment feature
As per the documentation from the link, you would construct your logger similar to
var log = new LoggerConfiguration()
.Enrich.WithThreadId()
.WriteTo.Console()
.CreateLogger();
"All events written through log will carry a property ThreadId with the id of the managed thread that wrote them. (By convention, any .WithXyz() methods on Enrich create properties named Xyz.)"
An example on Github is SerilogWeb.Classic. It provides a number of enrichers to capture certain information from the Request, e.g.
var log = new LoggerConfiguration()
.WriteTo.ColoredConsole()
.Enrich.With<HttpRequestIdEnricher>()
.Enrich.With<UserNameEnricher>()
.CreateLogger();
You could follow a similar apporach, creating an enricher that captures the information you always want to output in your logs, then initialising your logger using the enricher.

Related

How to write logs to a specific sink depending on the developement environment in Serilog?

I'm using Serilog with Azure Functions v4 and .NET 7. I am trying to write logs to both the local console and Application Insights. I already spent hours trying different solutions and the situation at the time of writing seems to be that this technology stack (Azure Functions + Application Insights) is not mature or too recent, and not yet very well documented.
Setting up appsettings.{env}.json files by environment and trying to add a different Serilog config by environment does not work with Azure Functions. Depending on the solution I tried, either Microsoft logger is used or there are no logs.
The code below is working for the console but I get duplicate logs on Application Insights. And if I remove Console sink, it works well in Application Insights but I get no more local logs:
.UseSerilog((hostBuilderContext, serviceProvider, loggerConfiguration) =>
loggerConfiguration
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.ApplicationInsights(
serviceProvider.GetRequiredService<TelemetryConfiguration>(),
TelemetryConverter.Traces)
)
I also tried other ways of setting up the logger, for example in .ConfigureServices(..., injecting it differently in the function code, but no luck. Many samples talk about a Startup method that I don't have. If it helps I can post the whole Program.cs and the function code here.
So I now added .Enrich.WithEnvironmentName() to get the environment name from an environment variable, and the question is: how can I filter the logs using ByIncludingOnly in the code above (not config file) to include logs depending on the environment name (Local or Development)? Same question for ByExcluding - I guess answer will be similar.
This is the way to do it. I ended up not using the Environment enricher because it is at the time of writing not supporting the Azure environment variable for Azure Functions (AZURE_FUNCTIONS_ENVIRONMENT). See this issue:
https://github.com/serilog/serilog-enrichers-environment/issues/49
This is how I got this working:
First option enriching the log event:
// Careful: Serilog is not able to enrich with the value of this variable and will default to 'Production'
var value = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") ?? "Development";
var predicate = $"EnvironmentName = 'Local'";
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("EnvironmentName", value)
.MinimumLevel.Debug()
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(predicate)
.WriteTo.Console(outputTemplate: "{Properties} {Message}"))
.WriteTo.Logger(lc => lc
.WriteTo.File(path: Directory.GetCurrentDirectory() + #"\test.txt"))
.CreateLogger();
Second option without enriching the log event:
var value = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") ?? "Development";
var predicate = $"{value} = 'Local'";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(predicate)
.WriteTo.Console(outputTemplate: "{Properties} {Message}"))
.WriteTo.Logger(lc => lc
.WriteTo.File(path: Directory.GetCurrentDirectory() + #"\test.txt"))
.CreateLogger();

Serilog - Splitting the log files using Correlation ID

How to split the log file based on correlation ID enricher?
I have configured the correlation ID enricher but I want to create log files based on correlation ID.
(Example: fisjdbs-13727-hrjsb).
It needs to write all the logs for that ID into fjsjdbs-13727-hrjsb.log
Please suggest some approaches.
You can do something like this in .NET6
builder.Services.AddHttpContextAccessor();
Logger log = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.WithCorrelationId()
.WriteTo.Console()
.WriteTo.Map("CorrelationId", (id,wt) => wt.File($"{id}.log"))
.CreateLogger();
builder.Host.UseSerilog(log);
and you can see files are getting generated based on "CorrelationId".
Hope it helps.

Logging to Azure Log Analytics custom fields using Serilog

I'm new to azure and also a rookie in .net, so this may be a noob question :)
Anyway, I have successfully managed to log to Azure Log Analytics using this code snippet:
var loggerConfig = new LoggerConfiguration().
Enrich.WithExceptionDetails().
Enrich.WithApplicationInformation(serviceName).
MinimumLevel.Debug().
MinimumLevel.Override("Microsoft", LogEventLevel.Information).
Enrich.FromLogContext().
Destructure.ByTransforming<ExpandoObject>(JsonConvert.SerializeObject).
//Enrich.WithProperty("ErrorMsg_CF","test").
WriteTo.AzureAnalytics(workspaceId: "MyWorkSpaceID",
authenticationId: "MyAuthID",
logName: "MyCustomLog_CL",
restrictedToMinimumLevel: LogEventLevel.Debug)
And:
Logging.Logger.Initialize(serviceName: "Logging Web Test v1.1");
Logging.Logger.GetLogger().Log(LogLevel.Debug, "{ErrorMsg_CF}:{TraceID_CF}:{UserName_CF}", errorMsg,traceID,userName);
I have, in my Log Analytics custom log table, created 3 custom fields: "ErrorMsg_CF", "TraceID_CF" and "UserName_CF", and I want to either be able to directly log to these custom fields, or somehow split my errormsg into these three custom fields.
When I import a file, I see that the message is stored in the "RawData" field, and with that field I am able to use the custom field generator and create custom fields, and also let Log Analytics split the message into the fields for me. But, when I do it through my application, the message is stored in the "LogMEssage_s" field, and seems like it's not possible to create custom fields from that field.
So, anyone know how I can log to my custom fields from my application?
The default formatting configuration is a line by line event logging. ALN can be ingested using JSON so that will be the best way to format log entry in that way.
.WriteTo.DESTINATION(new CompactJsonFormatter(), OTHER OPTIONS HERE)
Serilog formatting

Botframework How to change table storage connection string in startup based on incomming request

I'm using BotFramework version(v4) integrated with LUIS. In ConfigureServices(IServiceCollection services) method in startup.cs file I'm assigning storage and LUIS in the middleware.Below is the sample code.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(configuration);
services.AddBot<ChoiceBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(configuration);
var (luisModelId, luisSubscriptionKey, luisUri) = GetLuisConfiguration(configuration, "TestBot_Dispatch");//
var luisModel = new LuisModel(luisModelId, luisSubscriptionKey, luisUri);
var luisOptions = new LuisRequest { Verbose = true };
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel, luisOptions: luisOptions));
//azure storage emulater
//options.Middleware.Add(new ConversationState<Dictionary<string, object>>(new AzureTableStorage("UseDevelopmentStorage=true", "conversationstatetable")));
IStorage dataStore = new AzureTableStorage("DefaultEndpointsProtocol=https;AccountName=chxxxxxx;AccountKey=xxxxxxxxx;EndpointSuffix=core.windows.net", "TableName");
options.Middleware.Add(new ConversationState<Dictionary<string,object>>(new MemoryStorage()));
options.Middleware.Add(new UserState<UserStateStorage>(dataStore));
}
}
My bot will be getting requests from users of different roles such as (admin,sales,etc..).I want to change the table storage connection-string passed to middleware based on the role extracted from the incoming request. I will get user role by querying DB from the user-name which is extracted from the current TurnContext object of an incoming request. I'm able to do this in OnTurn method, but as these are already declared in middleware I wanted to change them while initializing in the middleware itself.
In .NET Core, Startup logic is only executed once at, er, startup.😊
If I understand you correctly, what you need to be able to do is: at runtime, switch between multiple storage providers that, in your case, are differentiated by their underlying connection string.
There is nothing "in the box" that enables this scenario for you, but it is possible if use the correct extension points and write the correct plumbing for yourself. Specifically you can provide a customized abstraction at the IStatePropertyAccessor<T> layer and your upstream code would continue to work at that level abstraction and be none-the-wiser.
Here's an implementation I've started that includes something I'm calling the ConditionalStatePropertyAccessor. It allows you to create a sort of composite IStatePropertyAccessor<T> that is configured with both a default/fallback instance as well as N other instances that are supplied with a selector function that allows them to look at the incoming ITurnContext and, based on some details from any part of the turn, indicate that that's the instance that should be used for the scope of the turn. Take a look at the tests and you can see how I configure a sample that chooses an implementation based on the ChannelId for example.
I am a little busy at the moment and can't ship this right now, but I intend to package it up and ship it eventually. However, if you think it would be helpful, please feel free to just copy the code for your own use. 👍

serilog multiple instance per functionality

While I have a general purpose logger for errors, exceptions etc that use rolling file sink to log stuff to my-{date}.log.
However, I need another instance for audit to audit-{date}.log and another instance to write perf info to perf-{date}.log.
How do I create multiple instances of serilog with different configuration or sink?
You can do this like following:
var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext();
var fileBasePath = "<base log path>";
loggerConfiguration
.WriteTo.Console()
.WriteTo.RollingFile(fileBasePath + "log-info-{Date}.txt")
.WriteTo.Logger(fileLogger => fileLogger
.MinimumLevel.Error()
.WriteTo.RollingFile(fileBasePath + "log-error-{Date}.txt"))
.WriteTo.Logger(fileLogger => fileLogger
.Filter.ByIncludingOnly(x =>
x.Level == LogEventLevel.Information &&
x.Properties.ContainsKey("<Audit Key>"))
.WriteTo.RollingFile(fileBasePath + "log-audit-{Date}.txt"));
Log.Logger = loggerConfiguration.CreateLogger();
Note: Audit Key is a key which used in audit log
I assume you would need different interface name to write audit and perf data (to be injected to your main business code). If that's the case just implement your IAudit, IPerfData with a new Serilog instance which you can totally customize where/how you save the log.

Categories

Resources