How to implement logging in blazor
I’m using Blazor (3.1) Server approach
I want to log (to a file) some events
I have try this extension: https://github.com/BlazorExtensions/Logging but I can’t make it to work as it says.
Can someone point me to a working example? Or Tell me if I’m doing something wrong (I’m getting this error with this approach:
A circular dependency was detected for the service of type 'Microsoft.JSInterop.IJSRuntime)
In order to implement logging for Blazor server you would follow the same approach as you would for a .NET Core or ASP.NET Core application.
Namely in your Program.cs file you would need to modify the CreateHostBuilder method to configure your loggers in a manner such as
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
You could then inject an ILogger into your razor components or throughout the rest of your application using dependency injection.
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; }
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Be sure to check the Microsoft documentation for information on built in loggers, third party loggers, and just logging in general.
you can use Serilog
this implementation for server side
https://www.c-sharpcorner.com/article/log-data-into-file-using-serilog-framework-in-blazor-server-app/
and this for client side
https://nblumhardt.com/2019/11/serilog-blazor/
also you will need to read
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0
Right now, it appears that logging with Blazor.Extensions.Logging is not supported for Blazor Server-Side applications due to the circular dependency with IJSRuntime:
https://github.com/BlazorExtensions/Logging/issues/44
PHeuter: "IJSRuntime may need to log and the logger needs IJSRuntime to do the logging."
Related
I have a question,
Is there any concern if I use ILogger in Serilog on behalf of Microsoft logger?
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Log.Logger);
}
Then use from ILogger in Serilog namespace.
_logger.Error(exception, "Error", exception.Message, exceptionId);
What is your idea?
Microsoft.Extensions.ILogger is an abstraction that decouples your application code (the code that writes log entries) from the underlying logging framework.
Using this abstraction brings the benefit, that you could easily change the Logging Framework underneath (e.g. replace Serilog with NLog) and don't have to update all references in your application code.
Also using Microsoft's abstractions also allows you to configure logging of your application code and logging of Microsoft SDKs you might use at a single place.
The downside of abstractions is that you have to aggree to a common minimum interface provided by all logging frameworks. It's not so easy to use Framework-Specific features this way.
So most of the time i would advise using the abstraction.
If you have very specific features from Serilog you would like to interact with you could think about using ILogger from Serilog directly.
However you can configure serilog in the provider registration as well to a high degree and probably get the best of both worlds.
You would configure Serilog factory interface to be used instead of built-in Logger factory for creating ILogger
First in program.cs, add the Serilog ILoggerFactory to your IHostBuilder with UserSerilog() method in CreateHostBuilder():
public static IHostBuilder CreateHostBuilder(string[] args) =>
new HostBuilder()
.ConfigureHostConfiguration(builder => { /* Host configuration */ })
.ConfigureAppConfiguration(builder => { /* App configuration */ })
.ConfigureServices(services => { /* Service configuration */})
.UseSerilog(); // <- Add this line
}
How the library works behind the scenes
On the face of it, completely replacing the default ASP.NET Core logging system to use a different one seems like a big deal. Luckily, thanks to the use of interfaces, loose coupling, and dependency injection, the code is remarkably simple! The whole extension method we used previously is shown below:
public static class SerilogHostBuilderExtensions
{
public static IHostBuilder UseSerilog(this IHostBuilder builder,
Serilog.ILogger logger = null, bool dispose = false)
{
builder.ConfigureServices((context, collection) =>
collection.AddSingleton<ILoggerFactory>(services => new
SerilogLoggerFactory(logger, dispose)));
return builder;
}
}
The UseSerilog() extension calls the ConfigureServices method on the IHostBuilder and adds an instance of the SerilogLoggerFactory as the application's ILoggerFactory. Whenever an ILoggerFactory is required by the app (to create an ILogger), the SerilogLoggerFactory will be used.
for more Information check this Link
"The downside of abstractions is that you have to aggree to a common minimum interface provided by all logging frameworks. It's not so easy to use Framework-Specific features this way."
I think we can always create extension methods to overcome this downside.
I'd like to add my app's build number to all logs in an ASP.NET Core 3.1 app that is using Application Insights for log storage. Is this possible without having to use BeginScope and EndScope everywhere? I assumed it would be part of the ConfigureLogging startup hook, but didn't see anything. I've done this in the past with Serilog's enrichers, but am not using that library currently.
You can achieve that with TelemetryInitializer. (https://learn.microsoft.com/en-us/azure/azure-monitor/app/api-filtering-sampling#addmodify-properties-itelemetryinitializer)
public class BuildNumberTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
(telemetry as ISupportProperties).Properties.Add("BuildNumber", "ValueForBuildNumber");
}
You need to add this initializer to the config, which is done like below if you are on Asp.Net Core applications.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITelemetryInitializer, BuildNumberTelemetryInitializer >();
}
In my solution I have projects for my API, my Web App and also have another project which includes services, that are getting some information from a database and formatting them, these are currently only used by this API, but these could be used by other API projects in the future.
My API have a couple controllers that are returning JSON data from the result returned by the services.
In some cases the services needs to call the API to process some information before calling the request to the database. Since I have dev/staging/prod environment with their own URL I don't want to hardcode the URLs in the services I want to use DI to get these dynamicaly depending on the context.
In the Startup.cs of my API I have added services.AddHttpContextAccessor(); in the ConfigureServices(IServiceCollection services) section to gain access to the current http context :
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
...
}
With that I know I can now access the information directly into my controller which I tried and it worked :
public class DataController : ControllerBase
{
...
private readonly string _baseUrl;
public FeaturesController(...
,IHttpContextAccessor httpContextAccessor)
{
...
_baseUrl = UrlHelpers.ShowBaseURL(httpContextAccessor) ?? throw new ArgumentNullException(nameof(_baseUrl));
}
}
public static class UrlHelpers
{
public static string ShowBaseURL(IHttpContextAccessor httpcontextaccessor)
{
var request = httpcontextaccessor.HttpContext.Request;
var absoluteUri = string.Concat(
request.Scheme,
"://",
request.Host.ToUriComponent(),
request.PathBase.ToUriComponent());
return absoluteUri;
}
}
I could do just about the same thing in the services but to me they should not act directly on the httpcontext, since this is not the job they are meant to do. I am sure I could do better by adding a class injected of some sort that would have then make the specific value available to my services.
I know I could also pass the _baseUrl directly as an argument when calling the services from my controller but since I am trying to better understand DI and use it I would rather find another way if it is viable.
I can't give credit but I went with Steven solution which make the most sens
I am familiar with using ASP.NET Core with EF Core, where you just define your DBContext in the ConfigureServices method from Startup.cs for DI, like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
However, I have been asked to add Dapper to this project (it will still use EF) but I can't even fetch the connection string. I found Brad Patton's answer here to be along the lines of what I had in mind, but he leaves the setup of the Configuration object up to the reader:
public void ConfigureServices(IServiceCollection services)
{
...
// Add the whole configuration object here.
services.AddSingleton<IConfiguration>(Configuration);
}
After googling around for a couple of hours, I still have no idea of how to implement the IConfiguration interface. Any help is appreciated.
With ASP.NET Core 2.x you no longer need to register the IConfiguration type yourself. Instead, the framework will already register it with the dependency injection container for you. So you can just inject IConfiguration in your services directly.
You can also take a look at the options pattern to see how to implement the configuration for your own service layer. So you could do it like this:
services.Configure<MyDatabaseOptions>(options =>
{
options.ConnectionString = Configuration.GetConnectionString("DefaultConnection");
});
Assuming a MyDatabaseOptions type that you inject into your service using the options pattern.
This is part of my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
...
//bus
services.AddSingleton<IRouteMessages, MessageRouter>();
services.AddSingleton<IBus, DirectBus>();
////
...
}
I'm trying to resolve the instance of IRouteMessages interface in my RegisterCommandHandlersInMessageRouter class:
public class RegisterCommandHandlersInMessageRouter
{
...
public static void BootStrap()
{
var router = CallContextServiceLocator.Locator.ServiceProvider.GetService(typeof (IRouteMessages));
new RegisterCommandHandlersInMessageRouter().RegisterRoutes(router as MessageRouter);
}
...
}
router variable is always null. Yet in my controllers where IRouterMessages is resolved automatically (in constructors) everything is fine.
I'm not sure what other parts of my code could be useful. I will provide more details.
Don't EVER use CallContextServiceLocator, this completely beats the purpose of having dependency injection. And NEVER relay on it.
CallContextServiceLocator is only used in some of the internal ASP.NET Core and is never be supposed to be used by developers creating ASP.NET Core applications. That being said, it can be removed, made internal or inaccessible at any time which would break existing applications.
Additionally, the CallContextServiceLocator only had runtime services registered (DNX Services, deprecated anyways). Source: David Fowl from ASP.NET Core team.
Infact CallContextServiceLocator is being removed in RC2, see the announcement.
Removed support for CallContextServiceLocator. Use PlatformServices and CompilationServices instead.
Instead, only use the built-in dependency injection, like this:
public static class RegisterCommandHandlersInMessageRouter
{
...
// This is extension method now
public static void RegisterCommandHandlers(this IServiceProvider services)
{
var router = services.GetService(typeof (IRouteMessages));
new RegisterCommandHandlersInMessageRouter().RegisterRoutes(router as MessageRouter);
}
...
}
and call it in your Startup.cs
public void Configure(IServiceProvider services)
{
...
services.RegisterCommandHandlers();
...
}