ServiceStack 4.5 configure log4net programmatically - servicestack

I am starting a new project with ServiceStack 4.5. Is there any way to configure log4net programmatically? In the documentation I found
LogManager.LogFactory = new Log4NetFactory(configureLog4Net: true);
I added this to the constructor of the AppHost class. However this seems to assume that you put the configuration to the App.config file (I am doing self-hosting on a windows service).
In some other projects I wrote a singleton and then used the Log4Net API to do the configuration:
private static void CreateFileAppender(ref Logger bedInventoryLogger, string logFilePath, Level logLevel, int maxFileSizeInMb, bool filterNh)
{
var filePatternLayout = new PatternLayout
{
ConversionPattern = "%date; [%thread]; %-5level; %logger; [%type{1}.%method]; - %message%newline"
};
filePatternLayout.ActivateOptions();
var bediLogFileAppender = new RollingFileAppender
{
File = logFilePath,
AppendToFile = true,
MaximumFileSize = $"{maxFileSizeInMb}MB",
MaxSizeRollBackups = 5,
RollingStyle = RollingFileAppender.RollingMode.Size,
LockingModel = new FileAppender.MinimalLock(),
Layout = filePatternLayout,
StaticLogFileName = true,
Threshold = logLevel
};
if (filterNh)
{
bediLogFileAppender.AddFilter(new LoggerMatchFilter
{
LoggerToMatch = "NHibernate",
AcceptOnMatch = false
});
bediLogFileAppender.AddFilter(new LoggerMatchFilter
{
LoggerToMatch = "NHibernate.SQL",
AcceptOnMatch = false
});
bediLogFileAppender.AddFilter(new LoggerMatchFilter
{
LoggerToMatch = "FluentNHibernate",
AcceptOnMatch = false
});
}
bediLogFileAppender.ActivateOptions();
bedInventoryLogger.AddAppender(bediLogFileAppender);
}
Since I used several logs, appenders etd and wanted to turn off NHibernate logging (I am using NHibernate 4 as ORM) etc. I found it more convenient to do configuration in C# than in XML.
Is it possible to hook this in with ServiceStack or do I better use Log4Net directly?

The default ServiceStack Log4Net adapter doesn't allow you to inject a configured Log4Net instance however the adapter classes are easy to copy and modify which are just in this 2 files which basically just forward the calls to Log4Net:
Log4NetFactory.cs
Log4NetLogger.cs

Related

No logging with configuration made in code using latest version of slf4net (1.0.0)

I'm using the slf4net.log4net nuget package to handle logging in a project. Because it must be possible for the loglevel to change at runtime, I made the configuration in code. The issue is that this code works fine in slf4net.log4net version 0.1.32.1 but when I upgrade it to version 1.0.0, the logfile is created, but the logs are not present on the logfile. I've created a dummy project to show this issue. I do not see how I can add a zip file here, so I'll just post the code here. It is a console app in net framework 4.7.2;
class Program
{
private static string GetLoggingPath()
{
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData,
Environment.SpecialFolderOption.DoNotVerify), "LoggingTesting");
Directory.CreateDirectory(path);
return path;
}
static void Main(string[] args)
{
var layout = new PatternLayout
{
ConversionPattern = "%d{ABSOLUTE}: %message %newline"
};
layout.ActivateOptions();
var fileAppender = new RollingFileAppender();
fileAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
fileAppender.Layout = layout;
var path = GetLoggingPath();
fileAppender.File = path + System.IO.Path.DirectorySeparatorChar + "LISlogging_.txt";
fileAppender.AppendToFile = true;
fileAppender.PreserveLogFileNameExtension = true;
fileAppender.StaticLogFileName = false;
fileAppender.DatePattern = "yyyy-MM-dd";
fileAppender.MaxSizeRollBackups = 10;
fileAppender.ActivateOptions();
ILoggerRepository repository = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly());
BasicConfigurator.Configure(repository, fileAppender);
var root = (repository as Hierarchy)?.Root;
if (root == null) return;
root.Level = log4net.Core.Level.All;
// Create log4net ILoggerFactory and set the resolver
var factory = new slf4net.log4net.Log4netLoggerFactory();
var resolver = new SimpleFactoryResolver(factory);
slf4net.LoggerFactory.SetFactoryResolver(resolver);
// trigger logging
var log = slf4net.LoggerFactory.GetLogger(typeof(Program));
log.Info("log this line");
}
}
public class SimpleFactoryResolver : IFactoryResolver
{
private readonly slf4net.ILoggerFactory _factory;
public SimpleFactoryResolver(slf4net.ILoggerFactory factory)
{
_factory = factory;
}
public slf4net.ILoggerFactory GetFactory()
{
return _factory;
}
}
This dummy project was created in .net framework, but I need this in a .net core project. That is why I need to version 1.0.0 .
I've also post this issue on the github page of slf4net (because it looks like a bug) : https://github.com/ef-labs/slf4net/issues/6
My main question for here on stack overflow is if there is a workaround so this can work with slf4net.log4net version 1.0.0
I've found a workaround for this. Maybe not the cleanest solution but it works. If anyone knows a cleaner solution please add it here.
When looking at the slf4net.log4net code I found out that when it tries to configure log4net it uses xml files or config files, which is a nightmare if you want to set the loglevel at runtime. You can pass a customconfigurator as parameter of the Log4netLoggerFactory . This customconfigurator needs to implement IXmlConfigurator.
The CustomConfigurator I've made accepts an IAppender and a loglevel (log4net.Core.Level). In the implementation of the Configure(ICollection(ILoggerRepository repository) method. I've set the root log level and Configured with the BasicConfigurator.
The CustomConfigurator looks like this:
public class CustomConfigurator: IXmlConfigurator
{
private readonly IAppender _appender;
private readonly log4net.Core.Level _logLevel;
public CustomConfigurator(IAppender appender, log4net.Core.Level logLevel)
{
_appender = appender;
_logLevel = logLevel;
}
public ICollection Configure(ILoggerRepository repository)
{
var root = (repository as Hierarchy)?.Root;
if (root != null)
{
root.Level = _logLevel;
}
return BasicConfigurator.Configure(repository, _appender);
}
public ICollection Configure(ILoggerRepository repository, XmlElement element)
{
return XmlConfigurator.Configure(repository, element);
}
public ICollection Configure(ILoggerRepository repository, FileInfo configFile)
{
return XmlConfigurator.Configure(repository, configFile);
}
public ICollection ConfigureAndWatch(ILoggerRepository repository, FileInfo configFile)
{
return XmlConfigurator.ConfigureAndWatch(repository, configFile);
}
}
Now you can create an appender in code like shown in the question (until fileappender.ActivateOptions) Then when constructing the log4netLoggerFactory you pass an instance of CustomConfigurator which takes the fileAppender and a loglevel as parameter.
var factory = new slf4net.log4net.Log4netLoggerFactory(new CustomConfigurator(fileAppender, Level.All));
var resolver = new SimpleFactoryResolver(factory);
slf4net.LoggerFactory.SetFactoryResolver(resolver);
This should work.

how distinguish traces from different instances .net core application in Application Insights

I work on .NET Core 2.2 console application that uses Microsoft.Extensions.Logging and is configured to send logs to Azure Application Insights using Microsoft.ApplicationInsights.Extensibility by:
services.AddSingleton(x =>
new TelemetryClient(
new TelemetryConfiguration
{
InstrumentationKey = "xxxx"
}));
...
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddApplicationInsights(serviceProvider, logLevel);
It works ok: I can read logs in Application Insights. But the application can be started simultanously in few instances (in different Docker containers). How can I distinguish traces from different instances? I can use source FileName, but I don't know how I should inject it.
I tried to use Scope:
var logger = loggerFactory.CreateLogger<Worker>();
logger.BeginScope(dto.FileName);
logger.LogInformation($"Start logging.");
It's interesting that my configuration is almost identical as in example: https://github.com/MicrosoftDocs/azure-docs/issues/12673
But in my case I can't see the property "FileName" in Application Insights.
For console project, if you want to use the custom ITelemetryInitializer, you should use this format: .TelemetryInitializers.Add(new CustomInitializer());
Official doc is here.
I test it at my side, and it works. The role name can be set.
Sample code is below:
static void Main(string[] args)
{
TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault();
configuration.InstrumentationKey = "xxxxx";
configuration.TelemetryInitializers.Add(new CustomInitializer());
var client = new TelemetryClient(configuration);
ServiceCollection services = new ServiceCollection();
services.AddSingleton(x => client);
var provider = services.BuildServiceProvider();
var loggerFactory = new LoggerFactory();
loggerFactory.AddApplicationInsights(provider, LogLevel.Information);
var logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("a test message 111...");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
Check the role name in azure portal:
If you really have no way to distinguish them you can use a custom telemetry initializer like this:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = Environment.MachineName;
}
}
and/or you can add a custom property:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if(telemetry is ISupportProperties)
{
((ISupportProperties)telemetry).Properties["MyIdentifier"] = Environment.MachineName;
}
}
}
In this example I used Environment.MachineName but you can of course use something else if needed. Like this work Id parameter of yours.
the wire it up using:
services.AddSingleton<ITelemetryInitializer, CustomInitializer>();

Using a custom sink with ServiceStack.Logging.Serilog?

Is there a non-obvious way (to me at least) to add a custom sink e.g. MongoDB or MicrosoftTeams as part of instantiating the Serilog factory in the ServiceStack framework or will it be a case of rolling your own factory and implementation of ILog?
PM> Install-Package ServiceStack.Logging.Serilog
LogManager.LogFactory = new SerilogFactory();
ServiceStack Logging
Serilog
Example: MongoDB Sink
This works without using the ServiceStack implementation, but is it considered bad form?
public override void Configure(Container container)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.MongoDBCapped("mongodb://mymongourl:27017/mylogs",
collectionName: "mycollectionoflogs", cappedMaxSizeMb: 50,
cappedMaxDocuments: 10000)
.CreateLogger();
SetConfig(new HostConfig
{
DefaultRedirectPath = "/metadata",
DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), false)
});
}
and in the ServiceInterface message implementation:
public object Any(MyRequest request)
{
Log.Information("I'm a lumberjack and I'm OK");
return new MyRequestResponse
{
Result = $"{ results.Chop() }"
};
}
I've just added a constructor overload to use a custom Serilog logger in this commit, this change is available from v5.1.1 that's now available on MyGet.
With this change you can pass a custom Serilog logger with ServiceStack's SerilogFactory, e.g:
LogManager.LogFactory = new SerilogFactory(new LoggerConfiguration()
.WriteTo.MongoDBCapped("mongodb://mymongourl:27017/mylogs",
collectionName: "mycollectionoflogs", cappedMaxSizeMb: 50,
cappedMaxDocuments: 10000)
.CreateLogger());
You can use the Serilog logger directly like in your example except it wont be able to capture ServiceStack's built-in logs or be able to substitute it later with any of the other ServiceStack loggers.

Create multiple Logfiles with dynamic Names with log4net

I'm using log4net in a Windows Service. This Service processes some RFID Reader. Currently we are logging all tasks of all Reader in one Logfile. This works fine.
But now I want to log the tasks of each Reader in a separate File. The Readers are identified by their IP Address. So I want to take the IP Address as part of the Filename.
The option in log4net to create dynamic file appenders seems not to fit for me, because I would have to manage the assignment from Reader to log file, each time I write a log.
Is there an appropriate way to do this in log4net, or is it not possible?
In my Logclass I used a Dictionary<string, ILog> for my Loggers. I've overloaded methods, either they use the Default-Logger or they get the Key for the Dictionary to use the specified Logger.
public static class Log
{
private static readonly Dictionary<string, ILog> loggers = new Dictionary<string, ILog>();
static Log()
{
XmlConfigurator.Configure();
}
public static void Debug(string message)
{
Debug(Logger.Default, message);
}
public static void Debug(string readerIp, string message)
{
GetLoggerInternal(readerIp).Debug(message);
}
private static ILog GetLoggerInternal(string logger)
{
if (!loggers.ContainsKey(logger))
{
var appender = CreateRollingFileAppender(logger);
appender.ActivateOptions();
loggers.Add(logger, LogManager.GetLogger(logger));
((log4net.Repository.Hierarchy.Logger)loggers[logger].Logger).AddAppender(appender);
}
return loggers[logger];
}
private static RollingFileAppender CreateRollingFileAppender(string readingPointIp)
{
var layout = new PatternLayout
{
ConversionPattern = "%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"
};
layout.ActivateOptions();
return new RollingFileAppender
{
Name = readingPointIp,
AppendToFile = true,
DatePattern = "yyyyMMdd",
MaximumFileSize = "1MB",
MaxSizeRollBackups = 10,
RollingStyle = RollingFileAppender.RollingMode.Composite,
File = $"..\\Log\\{readingPointIp}_log.txt",
Layout = layout
};
}
}
It is important to call the .ActivateOptions(); methods, they instantiate the Appender and Layout Classes. I use LogManager.GetLogger to create a new Logger. To add the appender I've to cast the logger, to use AddAppender.
Now I just have to call Log.Debug(readingPoint.IpAddress, "Some readingpoint specific log message."); and I've this message in a file, with the IP Address in it's name.

How do we integrate elmah logging in servicestack

I am new to servicestack and elman logging.
Can any body suggest how do we integrate elmah in service stack applications.
Thank you...
If you have an existing logging solution then you can use the ServiceStack.Logging.Elmah project. It is available via NuGet.
Exceptions, errors and fatal calls will be logged to Elmah in addition to the originally intended logger. For all other log types, only the original logger is used.
So if you are already using Log4Net then you can just configure Elmah like this
ElmahLogFactory factory = new ElmahLogFactory(new Log4NetFactory());
If you don't want to wrap in over an existing log then you can just research adding Elmah to any ASP.NET website. There is no reason it wouldn't work just because you are using ServiceStack.
using ServiceStack.Logging;
using ServiceStack.Logging.Elmah;
using ServiceStack.Logging.NLogger;
public AppHost()
: base(
"description",
typeof(MyService).Assembly)
{
LogManager.LogFactory = new ElmahLogFactory(new NLogFactory());
}
public override void Configure(Container container)
{
this.ServiceExceptionHandler += (request, exception) =>
{
// log your exceptions here
HttpContext context = HttpContext.Current;
ErrorLog.GetDefault(context).Log(new Error(exception, context));
// call default exception handler or prepare your own custom response
return DtoUtils.HandleException(this, request, exception);
};
// rest of your config
}
}
Now your ServiceStack error's appear in Elmah (assuming you've setup web.config etc).
Actually kampsj answer is better than Gavin's as Gavins causes double-logging to elmah by calling explicit elmah logger and then the default servicestack error handling...which itself already does the logging.
So really all you need is this (below assuming you want to wrap NLog with Elmah)
public class YourAppHost : AppHostBase
{
public YourAppHost() //Tell ServiceStack the name and where to find your web services
: base("YourAppName", typeof(YourService).Assembly)
{
LogManager.LogFactory = new ElmahLogFactory(new NLogFactory());
}
//...just normal stuff...
}
You could just have this above:
ElmahLogFactory factory = new ElmahLogFactory();
...but you probably should wrap another type of logger for non-error logging, like Debug and Warn.
This section on configuring Elmah and the Logging.Elmah UseCase for a working example of ServiceStack and Elmah configured together.
The ElmahLogFactory can be configured in your Global.asax before initializing the ServiceStack AppHost, e.g:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
var debugMessagesLog = new ConsoleLogFactory();
LogManager.LogFactory = new ElmahLogFactory(debugMessagesLog, this);
new AppHost().Init();
}
}

Resources