Add client ip to graylog output with Gelf.Extensions.Logging package - asp.net-core

I am using the Gelf.Extensions.Logging package to send log info to graylog from an asp.net core 3 website, and I would like additional fields like current user name, client ip, user agent.
In startup.cs, I have the following, but I don't know how to add the data to AdditionalFields that I want - I'm not even sure that this would be the right place, as I can't see how to inject the http context at this early stage.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.ConfigureLogging((context, builder) => builder.AddGelf(options =>
{
options.AdditionalFields["machine_name"] = Environment.MachineName;
options.AdditionalFields["environment_name"] = context.HostingEnvironment.EnvironmentName;
options.AdditionalFields["app_version"] = Assembly.GetEntryAssembly()?.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
}));
});
My appsettings.json is like so:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"System": "Warning"
},
"GELF": {
"Host": "graylog",
"Port": 12202,
"Protocol": "HTTP",
"LogSource": "DataEntry",
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"System": "Warning"
}
}
},
"AllowedHosts": "*",
/* snip */
}

It turns out the recommended approach here it to use a middleware like this example to add information about the current request as fields:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
// Register the middleware like so:
app.UseAppEnvironmentInfo();
}
}
public static class RequestAppEnvironmentInfoMiddlewareExtensions
{
public static IApplicationBuilder UseAppEnvironmentInfo(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<AppEnvironmentInfoMiddleware>();
}
}
public class AppEnvironmentInfoMiddleware
{
private readonly RequestDelegate _next;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<AppEnvironmentInfoMiddleware> _logger;
public AppEnvironmentInfoMiddleware(RequestDelegate next,
IHttpContextAccessor httpContextAccessor, ILogger<AppEnvironmentInfoMiddleware> logger)
{
_next = next;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
using (_logger.BeginScope(("machine_name", Environment.MachineName)))
using (_logger.BeginScope(("environment_name", context.HostingEnvironment.EnvironmentName)))
using (_logger.BeginScope(("app_version", Assembly.GetEntryAssembly()?.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion)))
{
await _next.Invoke(context);
}
}
}

Related

Value cannot be null - In-Memory Database .Net

I am trying to use a In-Memory Database, but I get this message:
System.ArgumentNullException: 'Value cannot be null. (Parameter
'source')'
I read a lot some similar question related with this issue, but every article is related with the connection String, and I think I must not use a ConnectionString because is a In-Memory Database. What Do I do wrong? I leave my code:
DbInitializer.cs - In this class appears the error
public static class DbInitializer
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var _context = new AppDBContext(serviceProvider.GetRequiredService<DbContextOptions<AppDBContext>>()))
{
if (_context.AgentsRole.Any()) //In this line appears the error
return;
_context.AgentsRole.AddRange(
new Agent { Id = 1, Role_Name = "David Lopez", Is_Active = true, Role_Description = "This is a test for David Lopez" },
new Agent { Id = 2, Role_Name = "James Norris", Is_Active = false, Role_Description = "This is a test for James Norris" },
new Agent { Id = 3, Role_Name = "Jhon Norris", Is_Active = true, Role_Description = "This is a test for Jhon Norris" },
new Agent { Id = 4, Role_Name = "James Norr", Is_Active = true, Role_Description = "This is a test for James Norr" }
);
_context.SaveChanges();
}
}
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDBContext>(options=> options.UseInMemoryDatabase(databaseName: "InMemory_DB"));
services.AddControllers();
services.AddSwaggerGen();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My Test1 Api v1");
});
}
}
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<AppDBContext>();
DbInitializer.Initialize(services);
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Controller.cs:
[ApiController]
[Route("api/[controller]")]
public class AgentRoleController : ControllerBase
{
private readonly ILogger<AgentRoleController> _logger;
private readonly AppDBContext _context;
public AgentRoleController(ILogger<AgentRoleController> logger, AppDBContext context)
{
_logger = logger;
_context = context;
}
[HttpGet]
[SwaggerOperation("GetAgentsRole")]
[SwaggerResponse((int)HttpStatusCode.OK)]
[SwaggerResponse((int)HttpStatusCode.NotFound)]
public IEnumerable<Agent> Get()
{
return _context.AgentsRole;
}
}
AppDBContext.cs:
public class AppDBContext : DbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options)
:base(options)
{
}
public DbSet<Agent> AgentsRole;
}
The solution is in AppDBContext.cs, I missed the initialization of AgentsRole, the solution is:
public class AppDBContext : DbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options)
:base(options)
{
}
public DbSet<Agent> AgentsRole { get; set; } // I added this Part!
}

How to log exception to appinsights using serilog?

Net core application. I am trying to log exceptions but this is not working as expected. Below is my configuration
Program.cs
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((hostContext, loggerConfiguration) =>
{
loggerConfiguration.ReadFrom.Configuration(hostContext.Configuration);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]);
services.AddApplicationInsightsTelemetry();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<ExceptionMiddleware>();
app.UseSerilogRequestLogging();
}
}
ExceptionMiddleware.cs
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
//private readonly ILogger _logger;
private readonly ILogger _logger = Serilog.Log.ForContext<ExceptionMiddleware>();
public ExceptionMiddleware(RequestDelegate next, ILogger logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
// _logger.Error($"Something went wrong: {ex}");
_logger.Error(ex.Message, $"Something went wrong:");
await HandleExceptionAsync(httpContext, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var user = string.Empty;
if (context.User.Claims.Any())
user = context.User.Claims?.FirstOrDefault(cl => cl.Type.Contains("preferred_username"))?.Value ?? "Anonymous User";
context.Response.ContentType = "application/json";
context.Response.StatusCode = ConfigurateExceptionTypes(exception);
await context.Response.WriteAsync(new Models.ErrorDetails()
{
UserName = user,
StatusCode = context.Response.StatusCode,
Message = exception.Message
}.ToString());
}
private static int ConfigurateExceptionTypes(Exception exception)
{
int httpStatusCode;
switch (exception)
{
case var _ when exception is ValidationException:
httpStatusCode = (int)HttpStatusCode.BadRequest;
break;
default:
httpStatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
return httpStatusCode;
}
}
AppSettings.json
"Serilog": {
"Using": [],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console"
},
{
"Name": "ApplicationInsights",
"Args": {
"instrumentationKey": "",
"restrictedToMinimumLevel": "Information",
"telemetryConverter": "Serilog.Sinks.ApplicationInsights.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
}
}
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithProcessId",
"WithThreadId"
]
}
This is not logging exceptions as expected. I can see status code 500 in app insights but I want to see exception message logged as well. Can someone help me to understand what could be I am missing here. Any help would be appreciated. Thanks
Try adding this values in your appsettings.json:
"Serilog":
{
"Using":
["Serilog",
"Serilog.Sinks.ApplicationInsights",
"Serilog.Sinks.Console"],
...
}
Just try to configure Logger in Startup.cs
var log = new LoggerConfiguration()
.WriteTo
.ApplicationInsights(serviceProvider.GetRequiredService<TelemetryConfiguration>(), TelemetryConverter.Traces)
.CreateLogger();
Whether you choose Events or Traces, if the LogEvent contains any exceptions it will always be sent as ExceptionTelemetry.
In Application Insights you can configure whether the exceptions appear as Exceptions vs Traces
See here

How do I name and add Sendgrid api key and name to appSetting.json

How do I name and add Sendgrid api key and name to appSetting.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"AuthDbContextConnection": "Server=(localdb)\\mssqllocaldb;Database=Authorization;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"SendGridUser": "RickAndMSFT",
"SendGridKey": "<key removed>"
}
How do I name and add Sendgrid api key and name to appSetting.json
As you did in your example appsettings.json, you can add SendGrid user secrets under root section of JSON configuration file.
To read above SendGrid user secrets related configuration values, we can use the options pattern, like below.
Create the following AuthMessageSenderOptions class
public class AuthMessageSenderOptions
{
public string SendGridUser { get; set; }
public string SendGridKey { get; set; }
}
Register the AuthMessageSenderOptions configuration instance
services.Configure<AuthMessageSenderOptions>(Configuration);
Inject the instance of IOptions<AuthMessageSenderOptions>, and read the AuthMessageSenderOptions configuration data in custom service(s) or controller etc
private readonly AuthMessageSenderOptions _optionsAccessor;
public HomeController(ILogger<HomeController> logger, IOptions<AuthMessageSenderOptions> optionsAccessor)
{
_logger = logger;
_optionsAccessor = optionsAccessor.Value;
}
public IActionResult Index()
{
//...
var sender = _optionsAccessor.SendGridUser;
//...
Test Result

How do I redirect to a different Razor page without page name string?

In my ASP.NET Razor Pages application the user often has to click on a button and becomes redirected to a different (internal) page of the application. To keep the code maintainable I want to avoid using a string for the page name. Is there a way I could get the page name from a static property or class name?
public class SomeModel : PageModel
{
public ActionResult OnPostLogin()
{
// Don't want to use string because it's hard to maintain!
// return new RedirectResult("AnotherPage");
// The below is giving a compile error
// return new RedirectResult(nameof(_Pages_AnotherPage));
// How to get page name directly from class or property?
return new RedirectResult(AnotherPage....);
}
}
You can get the url from appsettings.json.Here is a demo worked:
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"RedirectUrl": "Index"
}
cshtml.cs:
public class RedirectPageModel : PageModel
{
private readonly IConfiguration _configuration;
public RedirectPageModel(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult OnGet()
{
return Page();
}
public IActionResult OnPost()
{
var redirectUrl = _configuration["RedirectUrl"];
return Redirect(redirectUrl);
}
}
result:

ASP NET CORE GetConnectionString doesn't work

I'm new to ASP NET CORE and I'm writing new web API. I'm not able to retrieve connection string from the appsettings.json file and I get an
ArgumentNullException.
This is the code in my Startup class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<MyDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDBConnection")));
...
}
}
This is my appsettings.json file:
{
"ConnectionStrings": {
"MyDBConnection": "..."
},
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
What am I missing?
EDIT:
This is also my Program.cs class:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>();
}
Just to answer this. Directory.GetCurrentDirectory() will get the bin/debug or release location.
This means that it will not be the project location. So it can not find the application.json and also it would also not be able to find your MVC views later on.