I am having an issues with my asp.net core web api application. I am using docker-compose to use elasticsearch and kibana to read logs. I do not get any errors when I build the program. However, once I hit the run button it gives me System.Text.Json.JsonException error message and it is on the line
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
But the line it says it is on is a blank line. Could I be looking at the incorrect line? Not sure where the line is missing what it is saying because when I try to click on it, nothing happens.
Program.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Sinks.Elasticsearch;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ElasticSearchLogs
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((context, configuration) =>
{
configuration.Enrich.FromLogContext()
.Enrich.WithMachineName()
.WriteTo.Console()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(node: new Uri(context.Configuration["ElasticConfiguration:Uri"]))
{
IndexFormat = $"{context.Configuration["ApplicationName"]}-logs-{context.HostingEnvironment.EnvironmentName?.ToLower().Replace(".", "-")}-{DateTime.UtcNow:yyyy-MM}",
AutoRegisterTemplate = true,
NumberOfShards = 2,
NumberOfReplicas = 1
})
.Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
.ReadFrom.Configuration(context.Configuration);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
WeatherForecastController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ElasticSearchLogs.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
try
{
var rng = new Random();
if (rng.Next(0, 5) < 2)
{
throw new Exception("Oops what happened??");
}
return Ok(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray());
}
catch(Exception ex)
{
_logger.LogError(ex, "Something bad happened");
return new StatusCodeResult(500);
}
}
}
}
appsettings.json
{
"ApplicationName": "elastic-search-app",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
}
"AllowedHosts": "*"
}
I think that you are missing a comma in the configuration json
{
"ApplicationName": "elastic-search-app",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
},
"AllowedHosts": "*"
}
Related
I am logging with Serilog and have a (text) file sink with an output template and a console logger.
The output template of the text file logger:
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}|{Level:u3}|{SourceContext}|{Message}{NewLine:1}{Exception:1}"
The output template of the console logger:
"outputTemplate": "{Timestamp:HH:mm:ss.fff} {Level:u3} {SourceContext} {Message:lj}{NewLine:1}{Exception:1}"
Now we try (for the sake of easiness) to log some class data in JSON format. We want it as JSON beacuse we try to extract the data later from the log files.
The sample logging code is easy enough:
protected void DoTestLogs()
{
Logger.LogInformation("HERE IT IS");
Logger.LogInformation("HERE IT IS {#Foo}", new { Foo = "bar" });
int x = 1;
string y = "2";
Logger.LogInformation("HERE IS ANOTHER ONE {#Other}", new { x, y });
var data = new My.Data
{
Name = "my data",
IsValid = true,
Value = 27.82859584
};
Logger.LogInformation("HERE IS DATA {#Data}", data);
}
The console output looks valid as expected (except the serialzed type of data which I could ignore)
08:09:38.880 INF My.Test HERE IT IS
08:09:38.884 INF My.Test HERE IT IS {"Foo": "bar"}
08:09:38.888 INF My.Test HERE IS ANOTHER ONE {"x": 1, "y": "2"}
08:09:38.889 INF My.Test HERE IS DATA {"Name": "my data", "Value": 27.82859584, "IsValid": true, "$type": "Data"}
But the json in the text file log seems to be "randomly" invalid. I can't explain why quotes are there or not
2023-01-27 08:09:38.880|INF|My.Test|HERE IT IS
2023-01-27 08:09:38.884|INF|My.Test|HERE IT IS { Foo: "bar" }
2023-01-27 08:09:38.888|INF|My.Test|HERE IS ANOTHER ONE { x: 1, y: "2" }
2023-01-27 08:09:38.889|INF|My.Test|HERE IS DATA Data { Name: "my data", Value: 27.82859584, IsValid: True }
Does anyone have a hint how I could trick the file sink to put quotes around the names like Foo, x, y ... as the console sink does?
In case anyone want's to reproduce it and doesn't have a serilog setup at hand: here is the setup.
First create a new project from the "ASP.NET Core Web API" template. Then delete the WeatherForecast.cs and the WeatherForecastController.
Add the Serilog nuget to the cs.proj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
</ItemGroup>
</Project>
Configure Serilog via the appsettings.json
{
"AllowedHosts": "*",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext" ],
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "{Timestamp:HH:mm:ss.fff} {Level:u3} {SourceContext} {Message:lj}{NewLine:1}{Exception:1}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/log.log",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}|{Level:u3}|{SourceContext}|{Message}{NewLine:1}{Exception:1}"
}
}
]
}
}
Integrate Serilog in the Program.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Stackoverflow.Question75255058
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog
(
(context, services, configuration) =>
configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
No need for changes in the StartUp.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Stackoverflow.Question75255058
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Stackoverflow.Question75255058", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Stackoverflow.Question75255058 v1"));
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Create an new OutputController and the My.Data class under controllers
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Stackoverflow.Question75255058.Controllers
{
[ApiController]
[Route("[controller]")]
public class LogOutputController : ControllerBase
{
public readonly ILogger<LogOutputController> Logger;
public LogOutputController(ILogger<LogOutputController> logger)
{
Logger = logger;
}
[HttpGet]
public IActionResult Get()
{
Logger.LogInformation("HERE IT IS");
Logger.LogInformation("HERE IT IS {#Foo}", new { Foo = "bar" });
int x = 1;
string y = "2";
Logger.LogInformation("HERE IS ANOTHER ONE {#Other}", new { x, y });
var data = new My.Data
{
Name = "my data",
IsValid = true,
Value = 27.82859584
};
Logger.LogInformation("HERE IS DATA {#Data}", data);
return NoContent();
}
}
}
namespace My
{
public class Data
{
public string Name { get; set; }
public bool IsValid { get; set; }
public double Value { get; set; }
}
}
Start the application and execute the controller action provided by Swagger.
The difference is in your outputTemplate - {Message:lj} produces literal strings and JSON objects, while the default {Message} produces quoted strings and the earlier C#-like object format.
Using the same output template with each sink should produce the same result.
I'm trying to use serligo as my main logging utilitie but I can't seem to get it to work correctly.
In the Program.cs file, I configure the logger and also log a startup message, this message gets outputted to the console.
I also have, in one of my controllers, an ILogger<HomeController> instance that logs information when a certain action is called. These logs are not outputted to the console, and I cannot understand why.
Startup.cs
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting up version {version}", Defaults.Version); // WORKS
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Controller
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
_logger.LogInformation("Home/Index"); //Does not work
return View();
}
}
appsettings.json
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"System": "Error"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
}
}
]
}
}
]
},
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
The issue relates the above code, in this part of code, you should also configure it to write to the console.
Modify the code as below:
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration).WriteTo.Console())
Then, the output like this:
Searching I've not found the answer I'm looking for. I will only put code snippets here to help ask my question, supplying ALL of the code would not be executable without the entire system.
I'm using the built-in logging in C# to log to Windows Event Viewer and/or the Console. I also wanted to write to a file but not third-party logging, so I wrote my own simple logger that logs the same data to files and works.
My appsettings.json as a second for some configuration parameters like a working folder. I also have a folder path for the logger. What I would like to be able to do is use the same holder path from the setting and not have 2.
appsettings.json
{
"AppSettings": {
"WorkingFolderPath": "C:\\mypath\\",
"HeartbeatIntervalMinutes": 0, // This is seconds when in debug mode.
"FileRetryDelayMinutes": 1,
"PingTimeoutMilliseconds": 200
},
"Logging": {
"LogLevel": {
"Default": "Error",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Warning"
},
"Debug": {
"LogLevel": {
"Default": "Debug"
}
},
"Console": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Debug"
}
},
"EventLog": {
"LogLevel": {
"Default": "Warning"
}
},
"FileLog": {
"Options": {
"FolderPath": "C:\\mypath\\",
"RetentionDays": 5
},
"LogLevel": {
"Default": "Debug"
}
}
}
}
As you see in this file I have a "WorkingFolderPath" setting I'd like to use that as my path in my "FileLog" logger and not have to specify a path there as well.
Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureLogging((context, logging) =>
{
logging.ClearProviders();
logging.AddConsole();
logging.AddDebug();
logging.AddEventLog(new EventLogSettings()
{
SourceName = "FreshIQAppMessagingService"
});
logging.AddFileLogger(options =>
{
context.Configuration.GetSection("Logging").GetSection("FileLog").GetSection("Options").Bind(options);
});
})
.ConfigureAppConfiguration((hostContext, config) =>
{
config
.SetBasePath(ApplicationPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddEnvironmentVariables();
})
.ConfigureServices((hostContext, services) =>
{
services.Configure<Configuration>(hostContext.Configuration.GetSection("AppSettings"));
services.AddHostedService<Service1>();
services.AddHostedService<Service2>();
services.AddHostedService<Service3>();
services.AddHostedService<Service4>();
});
Here it's getting the options and passing them in. What I've not been able to figure out is how to change my classes so that I can send in the "WorkingFolderPath" from the top of the appsettings instead of the one with the options with the FileLog logger.
logging.AddFileLogger(options =>
{
context.Configuration.GetSection("Logging").GetSection("FileLog").GetSection("Options").Bind(options);
});
FileLoggerExtensions.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
namespace FreshIQAppMessaging.Logging
{
public static class FileLoggerExtensions
{
public static ILoggingBuilder AddFileLogger(this ILoggingBuilder loggingBuilder, Action<FileLoggerOptions> configure)
{
loggingBuilder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
loggingBuilder.Services.Configure(configure);
return loggingBuilder;
}
}
}
FileLoggerProvider.cs
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.IO;
namespace FreshIQAppMessaging.Logging
{
[ProviderAlias("FileLog")]
public class FileLoggerProvider : ILoggerProvider
{
public readonly FileLoggerOptions Options;
public FileLoggerProvider(IOptions<FileLoggerOptions> options)
{
Options = options.Value;
if (!Directory.Exists(Options.FolderPath))
{
Directory.CreateDirectory(Options.FolderPath);
}
}
public ILogger CreateLogger(string categoryName)
{
return new FileLogger(this);
}
public void Dispose() { }
}
}
FileLogger.cs
using Microsoft.Extensions.Logging;
using System;
using System.Globalization;
using System.IO;
namespace FreshIQAppMessaging.Logging
{
public class FileLogger : ILogger
{
protected readonly FileLoggerProvider _fileLoggerProvider;
public FileLogger(FileLoggerProvider fileLoggerProvider)
{
_fileLoggerProvider = fileLoggerProvider;
}
public IDisposable? BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
// Clean up old log files
var logFiles = Directory.GetFiles(_fileLoggerProvider.Options.FolderPath, "*-MyApp.log");
foreach (var logFilePath in logFiles)
{
var logFileName = new FileInfo(logFilePath).Name;
if (DateTime.TryParseExact(logFileName.Substring(0, 8), "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var logFileDate))
{
if (logFileDate.AddDays(_fileLoggerProvider.Options.RetentionDays) < DateTime.Now)
{
File.Delete(logFilePath);
}
}
}
var fullFilePath = $"{_fileLoggerProvider.Options.FolderPath}\\{DateTime.Now.ToString("yyyyMMdd")}-MyApp.log";
var logRecord = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} [{logLevel.ToString()}] {formatter(state, exception)} {(exception != null ? exception.StackTrace : "")}";
using (var streamWriter = new StreamWriter(fullFilePath, true))
{
streamWriter.WriteLine(logRecord);
}
}
}
}
FileLoggerOptions.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace FreshIQAppMessaging.Logging
{
public class FileLoggerOptions
{
public virtual string? FolderPath { get; set; }
public virtual int RetentionDays { get; set; } = 5;
}
}
You typically don't want to include custom options for your logger inside the Logging section. Most people do something like this instead:
{
"AppSettings": {
"WorkingFolderPath": "C:\\mypath\\",
...
},
"FileLog": {
"FolderPath": "C:\\mypath\\",
"RetentionDays": 5
},
"Logging": {
...
"FileLog": {
"LogLevel": {
"Default": "Debug"
}
}
}
}
You didn't share your options class but I assume you have a property on it named FolderPath. You could then configure your file logger like this:
logging.Services.Configure<FileLoggerOptions>(builder.Configuration.GetSection("FileLog"));
logging.AddFileLogger(options =>
{
options.FolderPath = builder.Configuration["AppSettings:WorkingFolderPath"];
});
You can now remove the FolderPath property from the FileLog object in the appsettings.json file since that value will be set using this line when adding the file logger:
options.FolderPath = builder.Configuration["AppSettings:WorkingFolderPath"];
The options would then look like this:
{
"AppSettings": {
"WorkingFolderPath": "C:\\mypath\\",
...
},
"FileLog": {
"RetentionDays": 5
},
"Logging": {
...
"FileLog": {
"LogLevel": {
"Default": "Debug"
}
}
}
}
I've written a similar logging provider in the Elmah.Io.Extensions.Logging repository and I copied some of the code from there. I tried mapping it to your original code in the question, but there might be something that doesn't match exactly. I hope that you still see the intent behind what I'm trying. You can look inside this repository for inspiration.
I have a small ASP.NET Core 3.1 project that should execute the Odata request and then log either info or exception into the Loggly. I'm using Serilog for this.
But for some reason, it only sends the very first message from the Program.cs only (Log.Debug($"DEBUG: Starting at:{DateTime.Now}");), but does not send anything other than that. While it works fine for the Console or File logging.
I have tried to fake an exception to trigger the Error logging. But nothing works (in the Loggly).
Any suggestions on why is that and how to make it work properly?
Here is my code:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
try
{
Log.Debug($"DEBUG: Starting at:{DateTime.Now}");
CreateHostBuilder(args).Build().Run();
Log.Debug($"DEBUG: Host created");
}
catch (Exception ex)
{
Log.Error("Oops something failed!");
throw new Exception(ex.Message);
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseSerilog();
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<OdataTestObjectContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionStrings:Database"]);
});
services.AddControllers().AddOData(opt =>
opt.Filter().Expand().Select().OrderBy().Count().SetMaxTop(100)
.AddRouteComponents("odata", GetEdmModel()));
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "OdataTest", Version = "v1" });
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});
var appSettings = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var serilogConfig = new LoggerConfiguration().ReadFrom.Configuration(appSettings);
services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(serilogConfig.CreateLogger(), true));
services.AddMvc(opts => { opts.Filters.Add(new AutoLogAttribute()); });
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger(c =>
{
c.RouteTemplate = "/swagger/{documentName}/swagger.json";
});
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "OdataTest v1"));
}
app.UseHttpsRedirection();
////Send "~/$odata" to debug routing if enable the following middleware
app.UseODataRouteDebug();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
var entitySet = builder.EntitySet<TestObject>("TestObjects");
entitySet.EntityType.HasKey(entity => entity.Id);
return builder.GetEdmModel();
}
}
Controller.cs
[ApiController]
[Route("OData")]
public class TestController : ODataController
{
private readonly OdataTestObjectContext _context;
private readonly ILogger<TestController> _logger;
public TestController(ILogger<TestController> logger, OdataTestObjectContext context)
{
_context = context;
_logger = logger;
}
[ApiExplorerSettings(IgnoreApi = true)]
[EnableQuery]
public IActionResult Get()
{
try
{
return Ok(_context.TestObjects);
}
catch (Exception ex)
{
_logger.LogError("Error happened for Default query! - {0}", ex.Message);
throw new InvalidOperationException(ex.Message);
}
}
[HttpGet("Top({top})")]
[EnableQuery]
public IEnumerable<TestObject> GetTop(int top)
{
return _context.TestObjects.OrderByDescending(x => x.Id).Take(top);
}
[HttpGet("Enumerable")]
[EnableQuery]
public IEnumerable<TestObject> Enumerable()
{
try
{
_logger.LogDebug("Executing ENUMERABLE now.");
//string a = "asd";
//int s = Convert.ToInt32(a);
var results = _context.SaleInvoicesAndProducts.AsEnumerable();
int z = 1;
if (results.Any())
z = 0;
int i = 10 / z;
return results;
}
catch (Exception ex)
{
_logger.LogError("Error happened for Enumerable query! - {0}", ex.Message);
throw new Exception();
}
}
AutoLogAttribute.cs (this just outputs logs with some request's parameters):
public class AutoLogAttribute : TypeFilterAttribute
{
public AutoLogAttribute() : base(typeof(AutoLogActionFilterImpl))
{
}
private class AutoLogActionFilterImpl : IActionFilter
{
private readonly ILogger<AutoLogAttribute> _logger;
public AutoLogActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AutoLogAttribute>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// perform some business logic work
_logger.LogInformation($"queryString: {context.HttpContext.Request.QueryString}");
_logger.LogDebug($"queryString (Debug): {context.HttpContext.Request.QueryString}");
}
public void OnActionExecuted(ActionExecutedContext context)
{
//TODO: log body content and response as well
_logger.LogInformation($"host: {context.HttpContext.Request.Host}");
_logger.LogInformation($"patheAndQuery: {context.HttpContext.Request.GetEncodedPathAndQuery()}");
_logger.LogDebug($"path (Debug): {context.HttpContext.Request.Path}");
_logger.LogDebug($"host (Debug): {context.HttpContext.Request.Host}");
_logger.LogDebug($"patheAndQuery (Debug): {context.HttpContext.Request.GetEncodedPathAndQuery()}");
}
}
}
appsetting.json
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Loggly" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Debug",
"System": "Debug"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": { "path": "C:\\ODATA\\log.json" }
},
{
"Name": "Loggly",
"Args": {
"customerToken": "mytoken",
"tags": "mytest"
}
}
],
},
"ConnectionStrings": {
"Database": "Server=(localdb)\\MSSQLLocalDB;Initial Catalog=MyTestDB;Integrated Security=true;MultiSubnetFailover=True;MultipleActiveResultSets=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
You can try to clear providers:
Host
****
.ConfigureLogging(logging =>
{
logging.ClearProviders();
})
.UseSerilog();
Also, if you have configured logging on Host, you don't need to put the additional configuration into Startup.cs
What would be the equivalent code of web host builder for generic host builder for console application in 'Microsoft.Extensions.Hosting':
var hostBuilder = WebHost.CreateDefaultBuilder(args).UseConfiguration(config).UseStartup<TStartup>();
I couldn't find exactly analog. I use the next code for that. For my background services.
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureHostConfiguration(configHost => configHost.AddEnvironmentVariables("ASPNETCORE_"))
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(GetConfigDirectoryPath());
configApp.AddJsonFile("appsettings.json", optional: false);
configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true);
})
.ConfigureServices(ConfigureServices)
.UseHostedService<MyBackgroundService>()
.Build();
await host.RunAsync();
}
private static void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(hostBuilderContext.Configuration.GetConnectionString("Default")));
services.AddTransient<IDbContextProvider<AppDbContext>, DefaultDbContextProvider>();
services.AddTransient<IUnitOfWorkManager, UnitOfWorkManager>();
services.AddTransient(typeof(IUnitOfWork), typeof(EfCoreUnitOfWork));
services.AddTransient(typeof(IReadOnlyRepository<>), typeof(EfCoreReadOnlyRepositoryBase<>));
services.AddTransient(typeof(IReadOnlyRepository<,>), typeof(EfCoreReadOnlyRepositoryBase<,>));
services.AddTransient(typeof(IRepository<>), typeof(EfCoreRepositoryBase<>));
services.AddTransient(typeof(IRepository<,>), typeof(EfCoreRepositoryBase<,>));
services.AddDistributedRedisCache(options => options.Configuration = hostBuilderContext.Configuration.GetSection("Redis:Configuration").Value);
//Register AutoMapper profiles
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
services.AddAutoMapper(assemblies);
}
Is implementing background service
public class MyBackgroundService : BackgroundService
public static class Extensions
{
public static IHostBuilder UseHostedService<T>(this IHostBuilder hostBuilder)
where T : class, IHostedService, IDisposable
{
return hostBuilder.ConfigureServices(services =>
services.AddHostedService<T>());
}
}
Would have been nice if you could elaborate what exactly you are looking for but following is from my sample console application written in .net6. wrote this to show-case dependency injection and use of NLog with Microsoft.Extensions.Logging
Skip following and Get the source from Github
Program.cs
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using RedBeans.Examples.Console.DependencyInjectionLoggingExample;
Console.WriteLine("Hello, World!");
await Host.CreateDefaultBuilder(args)
.UseContentRoot(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
.ConfigureServices((context, services) =>
{
services.AddHostedService<MyApplication>()
.AddTransient<AnotherClass>()
.AddOptions<ConfigSection1Settings>().Bind(context.Configuration.GetSection("ConfigSection1"));
})
.ConfigureLogging((context, logging) =>
{
logging.ClearProviders();
logging.AddConfiguration(context.Configuration.GetSection("NLog"));
logging.AddNLog();
})
.RunConsoleAsync();
MyApplication.cs
namespace RedBeans.Examples.Console.DependencyInjectionLoggingExample;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
internal class MyApplication : IHostedService
{
private int? _exitCode;
private readonly ILogger<MyApplication> _logger;
private readonly AnotherClass _another;
private readonly IHostApplicationLifetime _applicationLifetime;
public MyApplication(IHostApplicationLifetime applicationLifetime, AnotherClass another, ILogger<MyApplication> logger)
{
_applicationLifetime = applicationLifetime;
_another = another;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation($"Starting Application with arguments: {string.Join(";", Environment.GetCommandLineArgs())}");
_applicationLifetime.ApplicationStarted.Register(() =>
{
Task.Run(async () =>
{
try
{
_logger.LogInformation("My Application Business logic starts here!!!");
_another.AnotherFunction();
await Task.Delay(1000, cancellationToken);
_exitCode = 0;
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception");
_exitCode = 1;
}
finally
{
_applicationLifetime.StopApplication();
}
});
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogDebug($"Exiting with return code: {_exitCode}");
Environment.ExitCode = _exitCode.GetValueOrDefault(-1);
return Task.CompletedTask;
}
}
AnotherClass.cs
namespace RedBeans.Examples.Console.DependencyInjectionLoggingExample;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
internal class AnotherClass
{
private readonly ILogger _logger;
private readonly IOptions<ConfigSection1Settings> _configSection1;
public AnotherClass(IOptions<ConfigSection1Settings> configSection1, ILogger<AnotherClass> logger)
{
_logger = logger;
_configSection1 = configSection1;
}
public void AnotherFunction()
{
_logger.LogInformation($"I can tell you the value of configSection1.Config1 is {_configSection1.Value.Config1}");
}
}
ConfigSection1Settings.cs
namespace RedBeans.Examples.Console.DependencyInjectionLoggingExample;
internal sealed class ConfigSection1Settings
{
public string? Config1 { get; set; }
}
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.Hosting.Lifetime": "Warning",
"Microsoft.Extensions.Hosting.Internal.Host": "Warning"
}
},
"ConfigSection1": {
"Config1": "Value1"
},
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"logfile": {
"type": "File",
"fileName": "c:/temp/nlog-${shortdate}.log"
},
"logconsole": {
"type": "Console"
}
},
"rules": [
{
"logger": "Microsoft.*",
"maxLevel": "Error",
"final": true
},
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logconsole"
},
{
"logger": "*",
"minLevel": "Debug",
"writeTo": "logfile"
}
]
}
}
Project File
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.1" />
</ItemGroup>
</Project>