I'm initializing Serilog in Program.cs, reading the configuration from appsettings.json, adding middleware in Startup.cs -> Configure and Use Request Middleware. Some snippets:
Starup Extension
public static void UseSerilogRequestMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware<RequestMiddleware>();
app.UseSerilogRequestLogging();
}
Create Logger
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.FromLogContext()
.WriteTo.UDPSink(configuration["Serilog:LogstashUrl"], int.Parse(configuration["Serilog:LogstashPort"]), new JsonFormatter())
.CreateLogger();
Middleware
public async Task InvokeAsync(HttpContext httpContext)
{
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
try
{
using (LogContext.PushProperty("CorrelationId", httpContext.GetCorrelationId()))
using (LogContext.PushProperty("UserName", httpContext.Request.Headers["UserName"]))
using (LogContext.PushProperty("Path", httpContext.Request.Path))
using (LogContext.PushProperty("QueryString", httpContext.Request.Query))
{
await _next(httpContext).ConfigureAwait(false);
}
}
catch (Exception ex)
{
await HandleExceptionAsync(httpContext, ex).ConfigureAwait(false);
}
}
appSettings
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"WriteTo": [ { "Name": "Console" } ],
"Enrich": [ "FromLogContext", "WithMachineName", "WithExceptionDetails" ],
"Filter": [
{
"Name": "ByExcluding",
"Args": {
"expression": "RequestPath like '/health%'"
}
},
{
"Name": "ByExcluding",
"Args": {
"expression": "RequestPath like '/swagger%'"
}
}
],
"LogstashUrl": "logstash-teste-log",
"LogstashPort": "5045"
}
In the middleware I want to push some properties to the logs. When I configure Serilog as written above the properties are not visible in the logs
Related
I want log to sentry.io Information log level using serilog.
In appsettings.json I made this config:
"Sentry": {
"Dsn": "url",
"MaxRequestBodySize": "Always",
"SendDefaultPii": true,
"IncludeActivityData": true,
"AttachStackTrace": true,
"Debug": true,
"DiagnosticLevel": "Info"
},
"Serilog": {
"Using": [
"Serilog.Sinks.Console"
],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Error",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Properties} {SourceContext} [{Level}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Sentry",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Properties} {SourceContext} [{Level}] {Message:lj}{NewLine}{Exception}"
}
}
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithThreadId",
"WithHtpContextData",
"WithExceptionDetails"
]
}
I registered serilog and sentry into my Program.cs class:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseSentry();
})
.UseSerilog((hostingContext, loggerConfig) => loggerConfig.ReadFrom.Configuration(hostingContext.Configuration));
In my class i make code like this:
using System.Threading.Tasks;
using Quartz;
using Serilog;
//
private readonly ILogger _logger;
public QueuedJob(ILogger logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.Information("Hello World!");
return Task.CompletedTask;
}
Why from this configuration in sentry.io portal i see only logs that i logged as Error level? Why i cant log into sentry.io Information level? All levels of logs are printed to my console but only Errors are printed into console and sentry.io
By default, the Sentry Serilog integration only sends events for log level Error or higher.
For Info logs, the SDK keeps a ring buffer so when an error happens all related logs are included with that event.
This can be configured though, you can send everything (Debug or higher for example): https://docs.sentry.io/platforms/dotnet/guides/serilog/#configuration
In fact, I use this exact setup on NuGet Trends to capture any Warning or higher as event, and include any Debug or higher as breadcrumb:
Here's the configuration:
https://github.com/dotnet/nuget-trends/blob/dac67d1bd4707a94063b843571127eb055a4cc4f/src/NuGetTrends.Scheduler/appsettings.Production.json#L33-L34
I have configured Serilog using (I have attempted this with the dispose option both true and false)
services.AddLogging(
loggingBuilder =>
{
//Create Serilog logger from AppSettings.json properties.
_logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
loggingBuilder.AddSerilog(_logger, true);
});
Appsettings configuration
"Logging": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Debug",
"System": "Warning",
"Microsoft": "Warning"
},
"Debug": {
"LogLevel": {
"Default": "Debug"
}
},
"Console": {
"LogLevel": {
"Default": "Information"
}
}
},
"Serilog": {
"Using": [
"Serilog.Sinks.Console",
"Serilog.Sinks.SumoLogic",
"Serilog.Formatting.Compact"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"System": "Warning",
"Microsoft": "Warning"
}
},
"Filter": [
{
"Name": "ByExcluding",
"Args": {
"expression": "RequestPath = '/api/health'"
}
},
{
"Name": "ByExcluding",
"Args": {
"expression": "RequestPath = '/api/info'"
}
}
],
"WriteTo": [
{
"Name": "SumoLogic",
"Args": {
"endpointUrl": "todo-setSumoLogEndpointUrl",
"sourceName": "sourceName",
"sourceCategory": "sourceCategory",
"textFormatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
}
},
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}",
"restrictedToMinimumLevel": "Debug"
}
}
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithThreadId"
],
I am using the Sumologic sink
https://www.nuget.org/packages/Serilog.Sinks.SumoLogic/
I have configured my DbContext to use Serilog as well
private void ConfigureDbContext(IServiceCollection services)
{
ServiceProvider? provider = services.BuildServiceProvider();
ILoggerFactory? loggerFactory = provider.GetService<ILoggerFactory>();
services.AddDbContext<ApiDbContext>(options =>
{
string? connectionString = Configuration.GetConnectionString("ApiDatabase");
// NodaTime allows the use of zoned times.
options
.UseNpgsql(connectionString, npgsqlOptions => npgsqlOptions.UseNodaTime())
// Using snake case avoids the need for quoting tables and columns.
.UseSnakeCaseNamingConvention();
if (loggerFactory != null)
{
options.UseSerilog(loggerFactory);
}
if (!IsDevelopment)
{
return;
}
options.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine);
});
}
When running database migrations, I get a fatal exception that causes application startup to fail like:
Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint \"pk_pdt_users\"
at Npgsql.NpgsqlConnector.<ReadMessage>g__ReadMessageLong|194_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlDataReader.NextResult()
at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteNonQuery(Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteNonQuery()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteNonQuery(RelationalCommandParameterObject parameterObject)
at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at Precisely.Pdx.Api.Web.Startup.UpdateDatabase(IApplicationBuilder app) in /Users/richardcollette/Documents/code/sdm/pdx_api/Precisely.Pdx.Api/Precisely.Pdx.Api.Web/Startup.cs:line 190
at Precisely.Pdx.Api.Web.Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider apiVersionDescriptionProvider, ILogger`1 log) in /Users/richardcollette/Documents/code/sdm/pdx_api/Precisely.Pdx.Api/Precisely.Pdx.Api.Web/Startup.cs:line 121
If I do nothing else, the error logged by EF, which contains the failed SQL statement is not logged, nor is the resulting exception logged.
Instead, I must keep the Serilog Logger as a private field, catch the exception, log it and then manually dispose of the Logger.
try
{
UpdateDatabase(app);
}
catch (Exception ex)
{
log.LogError(ex, "Error applying migrations.");
// This is a fatal error so we dispose the logger to ensure that it's messages are written.
_logger?.Dispose();
throw;
}
After doing so, the exception is logged as well as the sql error.
Why does Serilog and/or the sink not write the logs on its own. Is the provider not disposed in a fatal error scenario?
It's your responsibility to flush the logs before your application ends. See Lifecycle of Loggers.
You do that by calling Log.CloseAndFlush() or by disposing your logger.
public static int Main(string[] args)
{
ConfigureLogging(); // set up Serilog
try
{
Log.Logger.Information("Custom.API Server is starting.");
var webHost = BuildWebHost(args);
webHost.Run();
Log.Logger.Information("Custom.API Server is starting.");
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly.");
return 1;
}
finally
{
Log.CloseAndFlush(); // <##<##<##<##<##<##
}
}
I've set Serilog to read its config from appsettings.json:
return WebHost.CreateDefaultBuilder(args)
.UseSerilog((ctx, config) => { config.ReadFrom.Configuration(ctx.Configuration); })
The appsettings.json has the following relevant info that specifies enrichers:
{
"Serilog": {
"WriteTo": [
{
"Name": "RollingFile",
"Args": {
"pathFormat": "%WIDGETSAPIBASEDIR%\\logs\\log-{Date}.txt"
}
},
{
"Name": "Debug"
}
],
"Enrich": [ "CorrelationId" ]
}
}
The resulting log doesn't contain any of the data specified in the Enrich property.
I've imported Serilog.Enrichers.CorrelationId, but still get nothing.
I've also tried "Enrich": [ "WithCorrelationId" ]. I've also tried other enrichers ("FromLogContext", "WithMachineName", "WithThreadId"), but still get nothing.
What am I missing?
Here is an example of my config file that works. Notice I have output templates with the enrichment ?
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "File",
"Args": {
"path": ".//Logs//app.log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"outputTemplate": "[{Timestamp :HH:mm:ss} {Level:u3} {SourceContext, -20} {ProcessId} {ProcessName} {ThreadId}] {Message}\n{Exception}",
,
"shared": true
}
},
{
"Name": "Console"
}
]
}
},
{
"Name": "SpectreConsole",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} [{Level:u3} {ProcessId}] {Message:lj}{NewLine}{Exception}",
"minLevel": "Verbose"
}
}
],
"Enrich": [
"FromLogContext",
"WithMemoryUsage",
"WithProcessId",
"WithProcessName",
"WithThreadId",
"WithThreadName"
]
}
I am trying to add username to my logs. The documentation shows this:
class ThreadIdEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"ThreadId", Thread.CurrentThread.ManagedThreadId));
}
}
However, I've read somewhere else that I need to use middleware to add the usernames to the logs.
This is what I have so far:
Middleware to get the current users username
public class UserNameEnricher
{
private readonly RequestDelegate next;
public UserNameEnricher(RequestDelegate next)
{
this.next = next;
}
public Task Invoke(HttpContext context)
{
LogContext.PushProperty("UserName", context.User.Identity.Name);
return next(context);
}
}
Startup.cs
app.UseAuthentication();
app.UseMiddleware<UserNameEnricher>();
Program.cs
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.Filter.ByExcluding(Matching.FromSource("Microsoft"))
.Filter.ByExcluding(Matching.FromSource("System"))
.Enrich.FromLogContext()
.CreateLogger();
However despite all these configurations, the usernames still aren't appearing. What am I missing?
Serilog Configuration:
"Serilog": {
"MinimumLevel": "Information",
"Override": {
"Microsoft": "Critical",
},
"WriteTo": [
{
"Name": "MSSqlServer",
"Args": {
"connectionString": "Server=.\\SQLEXPRESS;Database=App-7661DEE4-C53F-4E49-B140-2AFAA2C85927;Integrated Security=True;Trusted_Connection=True;MultipleActiveResultSets=true;",
"schemaName": "App",
"tableName": "EventLogs"
}
}
]
},
For making it work, you need to specify outputTemplate.
Properties from events, including those attached using enrichers, can also appear in the output template.
Output templates
Here is a demo code which is used for RollingFile.
var output = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message} {ActionName} {UserName} {NewLine}{Exception}";
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext() // Populates a 'User' property on every log entry
.WriteTo.RollingFile("Logs/app-{Date}.txt",outputTemplate: output)
.CreateLogger();
Note
{UserName} in output should map UserName in LogContext.PushProperty("UserName", context.User.Identity.Name);.
Update:
configure by appsetting.json
"Serilog": {
"MinimumLevel": "Information",
"Override": {
"Microsoft": "Critical"
},
"WriteTo": [
{
"Name": "RollingFile",
"Args": {
"pathFormat": "Logs/app-{Date}.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message} {UserName} {ActionName} {NewLine} {Exception}"
}
}
]
}
I need to use Serilog.Exceptions package to catch exceptions.
Serilog is read from appsettings.json
{
"Serilog": {
"Using": [
"Serilog.Sinks.RollingFile",
"Serilog.Sinks.Seq"
],
"WriteTo": [
{
"Name": "RollingFile",
"Args": {
"restrictedToMinimumLevel": "Debug",
"pathFormat": "myPath\\log-{Date}.log"
}
},
{
"Name": "RollingFile",
"Args": {
"restrictedToMinimumLevel": "Error",
"pathFormat": "myPath\\error-{Date}.log"
}
},
{
"Name": "Seq",
"Args": {
"serverUrl": "myUrl",
"apiKey": "myApiKey"
}
}
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithThreadId"
],
"Properties": {
"Application": "myApplicationName"
}
}
}
And in my startup.cs
var logger = new LoggerConfiguration()
.Enrich.WithExceptionDetails()
.ReadFrom.Configuration(Configuration)
.CreateLogger();
Log.Logger = logger;
But it doesn't work. Do I need to add some other properties in appsettings.json for Serilog.Exceptions package? Or is the appsettings.json configure correctly? What am I doing wrong?
Thanks
The following is how I setup Serilog in ASP.NET Core 2.1. You can take it as checklist and see what you have missed.
Install packages from Nuget: Serilog.AspNetCore, Serilog.Exceptions, Serilog.Settings.Configuration and other sinks you want to use.
Setup Serilog in your main program:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog((hostingContext, loggerConfiguration) =>
loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)
);
}
Put WithExceptionDetails in your Serilog section in appsettings.json:
{
"Serilog": {
...
"Enrich": [
"FromLogContext",
"WithExceptionDetails"
],
...
}
}