How to setup NLog with Sustainsys.Saml2 - c#

I have an ASP.Net Web Forms app where I just integrated Sustainsys.Saml2 library.
I've never used any sort of logging mechanism and I'm trying to figure out how to add or create an ILoggerAdapter for the library stated on their troubleshooting page.
I've decided to use NLog (please feel free to recommend a different one) and either I'm not understanding this well, or am not using the right keyword to search for what I need/want, or their isn't a lot of documentation on it.
Currently, I'm using the HttpModule version of Sustainsys.Saml2. Any other information available upon request.
Any help would be great.
Currently, I'm configuring the Sustainsys.Saml2 library through both web.config and the global.asax files. Here's the class my global.asax calls:
public class Saml2Config {
private static bool _alreadyInitialized;
private static readonly object Lock = new object();
public static void Initialize() {
if (_alreadyInitialized) {
return;
}
lock (Lock) {
if (_alreadyInitialized) {
return;
}
var domain = PageHelper.GetDomainURL(true);
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.EntityId.Id = $"{domain}/federation/Saml2";
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.ModulePath = "/federation/Saml2";
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.ReturnUrl = new Uri($"{domain}/mybarry");
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.PublicOrigin = new Uri($"{domain}");
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.Logger = new NullLoggerAdapter();
_alreadyInitialized = true;
}
}
}

The interface is pretty straightforward
public interface ILoggerAdapter
{
void WriteInformation(string message);
void WriteError(string message, Exception ex);
void WriteVerbose(string message);
}
I would implement it as follows:
public class NLogAdapter : ILoggerAdapter
{
private static Logger Logger = LogManager.GetLogger("Saml2");
public void WriteInformation(string message)
{
Logger.Info(message);
}
public void WriteError(string message, Exception ex)
{
Logger.Error(ex, message);
}
public void WriteVerbose(string message)
{
Logger.Debug(message);
}
}
And finally set it:
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.Logger = new NLogAdapter();

The ILoggerAdapter contains methods for different loglevels. Make an adapter class that implements ILoggerAdapter and writes to NLog. Then set SPOptions.Logger to an instance of your adapter class.
If you want an example, you can check out the adapter for Asp.Net Core that logs to the Asp.Net Core logging system and is the default for the Sustainsys.Saml2.AspNetCore2 package: https://github.com/Sustainsys/Saml2/blob/master/Sustainsys.Saml2.AspNetCore2/AspNetCoreLoggerAdapter.cs
For the Sustainsys.Saml2.HttpModule library the default is the NullLoggerAdapter which simply discards any logs. Only reason to use it is to not have to nullcheck the Logger property everywhere it is used (that code was written before the ?. syntax was introduced.)

Related

Cron with Umbraco

How can I schedule a task with Umbraco Cms. I would like to create scheduled emails with Umbraco 9.
I have tried unsuccessfully to get a documentation that could help. Could you please help or refer me to a documentation
Just write an API-Controller, which does the work. Then use the windows task scheduler or a cron job on linux to call the url of the API.
public class EmailsController : UmbracoApiController
{
[HttpGet]
public string Create()
{
// Do whatever you need
return "OK";
}
}
Then call the url https://yoursite.com/umbraco/api/emails/create
Firstly, have a read about hosted services, which are a feature provided by the .NET 5 framework, and which Umbraco are using themselves.
In Umbraco v9, you can implement your own version by inheriting from RecurringHostedServiceBase
The documentation is lacking on this, but have a look at the Umbraco implementation to see some examples - https://github.com/umbraco/Umbraco-CMS/tree/v9/dev/src/Umbraco.Infrastructure/HostedServices
This is also a useful post that provides some further details - https://our.umbraco.com/forum/umbraco-9/106304-creating-scheduled-tasks-in-umbraco-9
An example:
public class YourScheduledTask : RecurringHostedServiceBase
{
private readonly ILogger<YourScheduledTask> _logger;
public YourScheduledTask(ILogger<YourScheduledTask> logger)
: base(TimeSpan.FromMinutes(2),
TimeSpan.FromMinutes(2))
{
_logger = logger;
}
public override async Task PerformExecuteAsync(object state)
{
try
{
// your logic here
}
catch (Exception e)
{
_logger.LogError("exception when persisting collections: {exception}", e.ToString());
}
}
}
Don't forgot to resgister this in the IOC:
public class IoCComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.AddHostedService<YourScheduledTask>();
}
}

Implementing Airbrake into a ASP.NET MVC 5 project

I have an older .NET 4.8 project that needs to use Airbrake. The project is using Unity for its IoC container, implementing the standard Repository Service pattern.
There's very little in the way of ASP.NET examples.
I am looking to do something like this:
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType(typeof(ILogger<>), typeof(ILogger<>));
container.RegisterType<IMyService, MyService();
}
public class MyController
{
private readonly ILogger<MyController> _logger;
private readonly IMyService _myService;
public MyController(ILogger<MyController> logger, IMyService _myService)
{
_logger = logger;
_myService = myService;
}
public MyMethod()
{
try
{
var x = _myService.DoThis();
}
catch (Exception ex)
{
_logger.LogError(e, e.Message);
}
}
}
I believe I need to either somehow register Airbrake with ILogger or perhaps create my own logging service.
public class Logging : ILogging
{
public void LogError(Exception e, string message)
{
var airbrake = new AirbrakeNotifier(new AirbrakeConfig
{
ProjectId = // pulled from web.config somehow
ProjectKey = // pulled from web.config somehow
});
var notice = airbrake.BuildNotice(ex);
airbrake.NotifyAsync(notice).Result;
}
}
I have tried using this as starting point: https://github.com/airbrake/sharpbrake/blob/master/docs/asp-net-http-module.md
This is excellent, but I need to extend it somehow to be able to use it within my services and not just the .Web project.
I know there's the ASP.NET module that will automatically capture the errors but I am wanting to manually log when I see fit, and avoid having to call the airbrake client every time I want to log an error.
Is there a way to do this or am I completely misunderstanding how this should be working?
You don't actually need to wire it up as part of the .NET ILogger. I am sure there is a way (probably via OWIN) but you nothing stops you from writing a basic logging service as you would any other service and using that via bog standard DI. The answer was pretty much in the question to begin with.

Should I define methods as static in class library to use in Console Application

Scenario: I have a console application which references couple of class libraries. ClassLibEmployee pulls the data from SQL database and returns a List. I need to loop through the list of Employee's and send that to a WebAPI and update SQL DB with status. I created ClassLibPay which a wrapper for WebAPI.
ClassLibEmployee.EmployeeData ed = new ClassLibEmployee.EmployeeData();
var elist = ed.PullEmployees();
foreach (Employee e in elist) {
bool stat = ClassLibPay.ServiceWrap.Sendtopay(e.Id, e.Name, e.Pay, e.ExemptFlag, e.Hours);
ed.ChageStatus(e.Id, e.Name, e.Pay, e.ExemptFlag, e.Hours, stat);
}
In ClassLibEmployee, I defined class as public class EmployeeData
In ClassLibPay, I defined class as public static class ServiceWrap
Questions:
since I will be calling ChangeStatus method in EmployeeData for each employee, should that be a static class?
ServiceWrap is calling a service, is there a way to avoid creating instance of the service, for every Sendtopay call?
Console App
--References ClassLibEmployee
public class EmployeeData
{
public List<Employee> PullEmployees()
{
}
}
ConsoleApp
--References ClassLibPay
-- ClassLibPay calls a WebAPI
public static class ServiceWrap
{
public static bool Sendtopay(int id, string name, decimal pay, bool flg, int hours)
{
using (EDataSvc service = new EDataSvc())
{
service.serviceMethod(id,name,pay,flg,hours);
}
}
}
To prevent creating every time class, you definitely should move to DI way as Michael said.
This is very simple example how to use DI with console application based on Autofac library. Below we have Main console application and two classes where one is our wrapper(where maybe you want to prepare your data, and eDataService which should just send data to back-end. We register both classes as PerLifeTimeScope(here, this is singleton's - in another words have only one instance if we get it from the DI container). Of course you can choose ready frameworks with already integrated DI containers.
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
builder.RegisterType<EDataSvc>().InstancePerLifetimeScope();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var service = scope.Resolve<MyService>();
service.MakeRequestAsync("test");
}
}
}
public class EDataSvc
{
public void SendRequestAsync()
{
//TODO:Send request
}
}
public class MyService : IMyService
{
private EDataSvc _eDataService;
public void MakeRequestAsync(EDataSvc eDataSvc)
{
_eDataService = eDataSvc;
}
public void MakeRequestAsync(string parameter)
{
//TODO prepare your data or additional logic
_eDataService.SendRequestAsync();
}
}
public interface IMyService
{
void MakeRequestAsync(string parameter);
}

ASP.NET is it possible to use Session & HttpContext.Application in a library

I have the following code in a HttpGet method in a Controller
Session["var1"] = "someval1";
HttpContext.Application["var2"] = "someval2";
I wish to put away this code in a library [dll] so that in the library I have
// Inside DLL Library
// namespace MyNS, class MyCl
public void InitVars()
{
Session["var1"] = "someval1";
HttpContext.Application["var2"] = "someval2";
}
And the call this from my controller Get method
// In controller class HttpGet
InitVars();
How do I access the Session & the Application objects in the Library
I get the errors
The name Session does not exist in the current context
The name HttpContext does not exist in the current context
How can this be done?
You just need to open up the code library .csproj in Visual Studio and set a reference to System.Web.dll and the same code will work in the DLL.
You can get a reference to the current HttpContext using the following code:
var context = System.Web.HttpContext.Current;
after which you can simply call
context.Session["var1"] = "someval1";
context.Application["var2"] = "someval2";
This works
void InitLogin(System.Web.HttpSessionStateBase Session,
System.Web.HttpApplicationStateBase Application)
{
Session["var1"] = "someval1";
Application["var2"] = "someval2";
}
and call it as
InitVars(Session, Application);
How do I access the Session & the Application objects in the Library
Don't do it directly, you'll couple your code. I recommend using the Adapter Pattern. Something like this (untested):
Class Library:
public interface IStorage
{
T GetSession<T>(string key);
void SetSession<T>(string key, T value);
T GetGlobal<T>(string key);
void SetGlobal<T>(string key, T value);
}
public void InitVars(IStorage storage)
{
storage.SetSession("var1", "someval1");
storage.SetGlobal("var2", "somval2");
}
Web App:
public class WebStorage : IStorage
{
public T GetSession<T>(string key)
{
var result = Session[key] as T;
return result;
}
public void SetSession<T>(string key, T value)
{
Session[key] = value;
}
// etc with Global
}
InitVars(new WebStorage);
Now you have no dependencies on any web classes. If down the road you decide to use asp.net core (which has no HttpContext.Current etc etc) you can easily modify your WebStorage class without having to change your class library.

How to log to a file without using third party logger in .Net Core?

How to log to a file without using third party logger (serilog, elmah etc.) in .NET CORE?
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
}
Issue http://github.com/aspnet/Logging/issues/441 is closed and MS officially recommends to use 3rd party file loggers. You might want to avoid using heavyweight logging frameworks like serilog, nlog etc because they are just excessive in case if all you need is a simple logger that writes to a file and nothing more (without any additional dependencies).
I faced the same situation, and implemented simple (but efficient) file logger: https://github.com/nreco/logging
can be used in .NET Core 1.x and .NET Core 2.x / 3.x / 4.x / 5.x and .NET 6.0 LTS apps
supports custom log message handler for writing logs in JSON or CSV
implements simple 'rolling file' feature if max log file size is specified
Support daily file format log (eg. 2022-03-31.log, 2022-03-30.log)
.NET Core does not (and probably will not) provide a built-in ILoggerProvider implementation for file logging.
There is a facade which makes trace source logging (the built-in logger framework originated in classic .NET) available for .NET Core applications. It can be ok for those who are already familiar with it, but be prepared that configuration is somewhat cumbersome on .NET Core (for details, see this nice article).
As an alternative, you may try my lightweight ILogger<T> implementation which covers the features of the built-in ConsoleLogger and provides additional essential features and good customizability. My library is free, open-source and has only framework dependencies. It completely conforms with the Microsoft provider implementations.
Usage is as simple as follows:
dotnet add package Karambolo.Extensions.Logging.File
ASP.NET Core 6+ web applications (minimal hosting model):
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddFile(o => o.RootPath = o.RootPath = builder.Environment.ContentRootPath);
var app = builder.Build();
// ...
ASP.NET Core 3.1+ web applications:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureLogging((ctx, builder) =>
{
builder.AddConfiguration(ctx.Configuration.GetSection("Logging"));
builder.AddFile(o => o.RootPath = ctx.HostingEnvironment.ContentRootPath);
})
.UseStartup<Startup>();
});
.NET Core 3.1/.NET 5+ console applications:
// build configuration
// var configuration = ...;
// configure DI
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddConfiguration(configuration.GetSection("Logging"));
builder.AddFile(o => o.RootPath = AppContext.BaseDirectory);
});
// create logger factory
await using (var sp = services.BuildServiceProvider())
{
var loggerFactory = sp.GetService<ILoggerFactory>();
// ...
}
As of v3.3.0 structured logging is also supported. JSON format is available out of the box, as a separate package:
dotnet add package Karambolo.Extensions.Logging.File.Json
For configuration details, see the project site.
Most answers provided a solution using third party libraries. This made it sound that doing anything else will be very complicated. So I have decided to share this easy way of logging to a file without using a third party library. All you have to do is add these 3 classes to your project.
FileLogger:
using Microsoft.Extensions.Logging;
using System;
using System.IO;
namespace WebApp1
{
public class FileLogger : ILogger
{
private string filePath;
private static object _lock = new object();
public FileLogger(string path)
{
filePath = path;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
            //return logLevel == LogLevel.Trace;
            return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (formatter != null)
{
lock (_lock)
{
string fullFilePath = Path.Combine(filePath, DateTime.Now.ToString("yyyy-MM-dd") + "_log.txt");
var n = Environment.NewLine;
string exc = "";
if (exception != null) exc = n + exception.GetType() + ": " + exception.Message + n + exception.StackTrace + n;
File.AppendAllText(fullFilePath, logLevel.ToString() + ": " + DateTime.Now.ToString() + " " + formatter(state, exception) + n + exc);
}
}
}
}
}
FileLoggerProvider:
using Microsoft.Extensions.Logging;
namespace WebApp1
{
public class FileLoggerProvider : ILoggerProvider
{
private string path;
public FileLoggerProvider(string _path)
{
path = _path;
}
public ILogger CreateLogger(string categoryName)
{
return new FileLogger(path);
}
public void Dispose()
{
}
}
}
FileLoggerExtensions:
using Microsoft.Extensions.Logging;
namespace WebApp1
{
public static class FileLoggerExtensions
{
public static ILoggerFactory AddFile(this ILoggerFactory factory, string filePath)
{
factory.AddProvider(new FileLoggerProvider(filePath));
return factory;
}
}
}
Add these lines to your Configure method in Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddFile(Path.Combine(Directory.GetCurrentDirectory(), "logs"));
//...
}
So now you will have to create a folder called "logs" in your project. Your logs will be now written to files created for each date. You can view the created file and compare the logs with the console output.
This answer has been updated. Log file formatting has been fixed. Now it is possible to log to different files for each date.
If you don't like the implementation of my logging provider you can make your own using this info: https://www.codeproject.com/Articles/1556475/How-to-Write-a-Custom-Logging-Provider-in-ASP-NET
Note: This is simple solution and it may not be suitable for busy systems. If you need advanced logging consider using a third party logger.
The ones provided by Adam and Vitaliy are still probably the most simple ones to date (thanks guys btw!).
Since an external NuGet is necessary anyway, worth mentioning that there is also a "standalone" extension of the Serilog rolling file sink that can be simply used as a logging provider for .net core, without completely swapping out the logging pipeline (pulls other dependencies, but I don't see that as an issue if you need the few extra features provided)
As of March 2020, this is the complete picture:
Karambolo.Extensions.Logging.File
NReco.Logging.File
Serilog.Extensions.Logging.File
If you are using IIS, you can enable and view stdout logs:
Edit the web.config file.
Set stdoutLogEnabled to true.
Change the stdoutLogFile path to point to the logs folder (for example, .\logs\stdout).
Save the file.
Make a request to the app.
Navigate to the logs folder. Find and open the most recent stdout log.
For information about stdout logging, see Troubleshoot ASP.NET Core on IIS.
Necromancing.
It's not quite that easy !
First, notice that .NET Core logging is more like Tracing in Full .NET Framework.
So you need to create both a TraceListener (ILoggerProvider), and a TraceWriter (ILogger).
Also, you need the create a LoggerOptions class, where you set the logfile-name, etc.
Furthermore, you can optionally create a class which inherits from ConfigureFromConfigurationOptions<T>, which can be called from ILoggingBuilder.TryAddEnumerable, I presume to configure your options from configuration entries.
Also, you need to create an an Extension-Method class, with which you can add ILoggerProvider to ILoggingBuilder.
The next stumbling-block is, that Microsoft has different "log-categories", e.g. in a Windows-Service
Microsoft.Extensions.Hosting.Internal.ApplicationLifetime
Microsoft.Extensions.Hosting.Internal.Host
Microsoft.Hosting.Lifetime
Now it will create a logger-instance for each of those categories.
Which means if you want to write your log-output to just one file, this will explode, because once the ILoggerProvider has created an instance of ILogger for ApplicationLifetime, and ILogger has created a FileStream and acquired a lock on it, the logger that gets created for the next category (aka Host) will fail, because it can't acquire the lock on the same file - "great" ...
So you need to cheat a little bit - and always return the same ILogger instance for all categories you want to log.
If you do that, you will find your logfile spammed by log-entries from Microsoft.* ...
So you need to only return your singleton for the categories you want to log (e.g. everything whose namespace doesn't start with Microsoft)...
For all other categories, ILoggerProvider.CreateLogger can return NULL. Except that ILoggerProvider.CreateLogger CANNOT return NULL ever, because then the .NET framework explodes.
Thus, you need to create an IgnoreLogger, for all the log-categories you don't want to log...
Then you need to return the same instance (singleton) of logger all the time, for all categories, so it won't create a second instance of Logger and try to acquire a lock on the already locked logfile. Yupee.
Highlighs include, instead of using a singleton with locks, some file-loggers out there put log-statements in a queue, so they can have multiple instances writing to the same file by making the queue static, and periodically flushing that static queue to disk. Of course, if your service EXITS (e.g. crashes) before the queue has been flushed, you will be missing the exact lines in the logfile which would have told you why your service crashed there (or did otherwise funny things)... e.g. your service works fine when debugging with VS or when running on console, but fails as windows-service, because the current-directory when running as windows-serivce is C:\windows\system32, and consequently, your configuration files cannot be found/read. But although you tried to log that, you don't get the error log on that, because the queue hasn't been flushed before the program exited. And tick-tack, just like that, the day is over until you find out what the problem actually was...
So here, my implementation (i don't claim it's good, but it's as simple as it gets, and it works for me, and most importantly, IT'S NOT A <insert expletive here> TICK-TACK QUEUE):
ILoggerProvider:
namespace RamMonitor
{
public class IgnoreLogger
: Microsoft.Extensions.Logging.ILogger
{
public class IgnoreScope
: System.IDisposable
{
void System.IDisposable.Dispose()
{
}
}
System.IDisposable Microsoft.Extensions.Logging.ILogger.BeginScope<TState>(TState state)
{
return new IgnoreScope();
}
bool Microsoft.Extensions.Logging.ILogger.IsEnabled(
Microsoft.Extensions.Logging.LogLevel logLevel)
{
return false;
}
void Microsoft.Extensions.Logging.ILogger.Log<TState>(
Microsoft.Extensions.Logging.LogLevel logLevel
, Microsoft.Extensions.Logging.EventId eventId
, TState state
, System.Exception exception
, System.Func<TState, System.Exception, string> formatter)
{ }
}
public class FileLoggerProvider
: Microsoft.Extensions.Logging.ILoggerProvider
{
protected FileLoggerOptions m_options;
protected IgnoreLogger m_nullLogger;
protected FileLogger m_cachedLogger;
public FileLoggerProvider(Microsoft.Extensions.Options.IOptions<FileLoggerOptions> fso)
{
this.m_options = fso.Value;
this.m_nullLogger = new IgnoreLogger();
this.m_cachedLogger = new FileLogger(this, this.m_options, "OneInstanceFitsAll");
} // End Constructor
Microsoft.Extensions.Logging.ILogger Microsoft.Extensions.Logging.ILoggerProvider
.CreateLogger(string categoryName)
{
// Microsoft.Extensions.Hosting.Internal.ApplicationLifetime
// Microsoft.Extensions.Hosting.Internal.Host
// Microsoft.Hosting.Lifetime
if (categoryName.StartsWith("Microsoft", System.StringComparison.Ordinal))
return this.m_nullLogger; // NULL is not a valid value...
return this.m_cachedLogger;
} // End Function CreateLogger
private bool disposedValue = false; // Dient zur Erkennung redundanter Aufrufe.
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: verwalteten Zustand (verwaltete Objekte) entsorgen.
}
// TODO: nicht verwaltete Ressourcen (nicht verwaltete Objekte) freigeben und Finalizer weiter unten überschreiben.
// TODO: große Felder auf Null setzen.
disposedValue = true;
}
}
// TODO: Finalizer nur überschreiben, wenn Dispose(bool disposing) weiter oben Code für die Freigabe nicht verwalteter Ressourcen enthält.
// ~FileLoggerProvider() {
// // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in Dispose(bool disposing) weiter oben ein.
// Dispose(false);
// }
// Dieser Code wird hinzugefügt, um das Dispose-Muster richtig zu implementieren.
void System.IDisposable.Dispose()
{
// Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in Dispose(bool disposing) weiter oben ein.
Dispose(true);
// TODO: Auskommentierung der folgenden Zeile aufheben, wenn der Finalizer weiter oben überschrieben wird.
// GC.SuppressFinalize(this);
}
} // End Class FileLoggerProvider
}
ILogger:
// using Microsoft.Extensions.Logging;
namespace RamMonitor
{
public class FileLogger
: Microsoft.Extensions.Logging.ILogger
, System.IDisposable
{
protected const int NUM_INDENT_SPACES = 4;
protected object m_scopeLock;
protected object m_lock;
protected Microsoft.Extensions.Logging.LogLevel m_logLevel;
protected Microsoft.Extensions.Logging.ILoggerProvider m_provider;
protected int m_indentLevel;
protected System.IO.TextWriter m_textWriter;
protected System.Collections.Generic.LinkedList<object> m_scopes;
protected System.IO.Stream m_stream;
public FileLogger(Microsoft.Extensions.Logging.ILoggerProvider provider, FileLoggerOptions options, string categoryName)
{
this.m_scopeLock = new object();
this.m_lock = new object();
this.m_logLevel = Microsoft.Extensions.Logging.LogLevel.Trace;
this.m_provider = provider;
this.m_indentLevel = 0;
this.m_scopes = new System.Collections.Generic.LinkedList<object>();
// this.m_textWriter = System.Console.Out;
string logDir = System.IO.Path.GetDirectoryName(options.LogFilePath);
if (!System.IO.Directory.Exists(logDir))
System.IO.Directory.CreateDirectory(logDir);
this.m_stream = System.IO.File.Open(options.LogFilePath, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.Read);
this.m_textWriter = new System.IO.StreamWriter(this.m_stream, System.Text.Encoding.UTF8);
this.m_textWriter.Flush();
this.m_stream.Flush();
} // End Constructor
protected void WriteIndent()
{
this.m_textWriter.Write(new string(' ', this.m_indentLevel * NUM_INDENT_SPACES));
} // End Sub WriteIndent
System.IDisposable Microsoft.Extensions.Logging.ILogger.BeginScope<TState>(TState state)
{
FileLoggerScope<TState> scope = null;
lock (this.m_lock)
{
scope = new FileLoggerScope<TState>(this, state);
this.m_scopes.AddFirst(scope);
this.m_indentLevel++;
WriteIndent();
this.m_textWriter.Write("BeginScope<TState>: ");
this.m_textWriter.WriteLine(state);
this.m_indentLevel++;
// this.m_provider.ScopeProvider.Push(state);
// throw new System.NotImplementedException();
this.m_textWriter.Flush();
this.m_stream.Flush();
}
return scope;
} // End Function BeginScope
public void EndScope<TState>(TState scopeName)
{
lock (this.m_lock)
{
// FooLoggerScope<TState> scope = (FooLoggerScope<TState>)this.m_scopes.First.Value;
this.m_indentLevel--;
WriteIndent();
this.m_textWriter.Write("EndScope ");
// this.m_textWriter.WriteLine(scope.ScopeName);
this.m_textWriter.WriteLine(scopeName);
this.m_indentLevel--;
this.m_scopes.RemoveFirst();
this.m_textWriter.Flush();
this.m_stream.Flush();
}
} // End Sub EndScope
bool Microsoft.Extensions.Logging.ILogger.IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
{
// return this.m_provider.IsEnabled(logLevel);
return logLevel >= this.m_logLevel;
} // End Function IsEnabled
void Microsoft.Extensions.Logging.ILogger.Log<TState>(
Microsoft.Extensions.Logging.LogLevel logLevel
, Microsoft.Extensions.Logging.EventId eventId
, TState state
, System.Exception exception
, System.Func<TState, System.Exception, string> formatter)
{
lock (this.m_lock)
{
WriteIndent();
this.m_textWriter.Write("Log<TState>: ");
this.m_textWriter.WriteLine(state);
this.m_textWriter.Flush();
this.m_stream.Flush();
System.Exception currentException = exception;
while (currentException != null)
{
WriteIndent();
this.m_textWriter.Write("Log<TState>.Message: ");
this.m_textWriter.WriteLine(exception.Message);
WriteIndent();
this.m_textWriter.Write("Log<TState>.StackTrace: ");
this.m_textWriter.WriteLine(exception.StackTrace);
this.m_textWriter.Flush();
this.m_stream.Flush();
currentException = currentException.InnerException;
} // Whend
} // End Lock
} // End Sub Log
void System.IDisposable.Dispose()
{
this.m_textWriter.Flush();
this.m_stream.Flush();
this.m_textWriter.Close();
this.m_stream.Close();
} // End Sub Dispose
} // End Class FileLogger
} // End Namespace RamMonitor
Options:
namespace RamMonitor
{
public class FileLoggerOptions
{
public FileLoggerOptions()
{ }
public string LogFilePath { get; set; }
public Microsoft.Extensions.Logging.LogLevel LogLevel { get; set; } =
Microsoft.Extensions.Logging.LogLevel.Information;
}
}
Extensions
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Options;
namespace RamMonitor
{
public static class FileLoggerExtensions
{
public static Microsoft.Extensions.Logging.ILoggingBuilder AddFileLogger(
this Microsoft.Extensions.Logging.ILoggingBuilder builder
, System.Action<FileLoggerOptions> configure)
{
builder.AddConfiguration();
builder.Services.TryAddEnumerable(Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Singleton<
Microsoft.Extensions.Logging.ILoggerProvider,
FileLoggerProvider
>()
);
builder.Services.TryAddEnumerable(Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Singleton
<IConfigureOptions<FileLoggerOptions>, FileLoggerOptionsSetup>());
builder.Services.TryAddEnumerable(
Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Singleton
<
IOptionsChangeTokenSource<FileLoggerOptions>,
LoggerProviderOptionsChangeTokenSource<FileLoggerOptions
, FileLoggerProvider>
>());
builder.Services.Configure(configure);
return builder;
}
}
}
ScopeClass:
namespace RamMonitor
{
public class FileLoggerScope<TState>
: System.IDisposable
{
protected FileLogger m_logger;
protected TState m_scopeName;
public TState ScopeName
{
get
{
return this.m_scopeName;
}
} // End Property ScopeName
public FileLoggerScope(FileLogger logger, TState scopeName)
{
this.m_logger = logger;
this.m_scopeName = scopeName;
} // End Constructor
void System.IDisposable.Dispose()
{
this.m_logger.EndScope(this.m_scopeName);
} // End Sub Dispose
} // End Class FileLoggerScope
}
OptionsSetup:
namespace RamMonitor
{
internal class FileLoggerOptionsSetup
: Microsoft.Extensions.Options.ConfigureFromConfigurationOptions<FileLoggerOptions>
{
public FileLoggerOptionsSetup(
Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration<FileLoggerProvider>
providerConfiguration
)
: base(providerConfiguration.Configuration)
{
// System.Console.WriteLine(providerConfiguration);
}
}
}
Note:
The scopes will not be thread-safe this way.
If you have a multi-threaded application - remove the scopes, or make it thread-safe.
The way MS implemented scopes, I can't think of a proper way to do this.
If you add a separate ScopeLock, you might get deadlocks by asynchronous calls blocking each other due to logging.
I just wrote a simple C# wrapper for the linux built-in syslog() function that writes messages to the default /var/log. No nuget packages, no dependencies, no file permissions. Just 70 lines of C# code, 1kb file that you can throw into your project if you're targeting linux.
And let the OS take care of everything: log writing, file rotation/rollover etc. etc.
Here's the gist: https://github.com/jitbit/SyslogCore feel free to contribute.
Usage:
Syslog.Write(Syslog.Level.Warning, "MyAwesomeApp", "something went wrong");
Since .Net core (2.2) does not implement this yet, still, we have to use a third party plugin for this.
If you want to log Error, Warning and etc into a txt file in a .Net Core API project.
You can use what I used on my project which is called Serilog.
and you can follow the below blog to configure Serilog on your project.
http://anthonygiretti.com/2018/11/19/common-features-in-asp-net-core-2-1-webapi-logging/

Categories

Resources