Log4j2 Creating LoggerConfig programmatically with empty Appender refs - log4j2

I am using log4j2 v2.17.2, and need to programmatically add loggers/appenders at runtime.
I am creating LoggerConfig as shown in: https://logging.apache.org/log4j/2.x/manual/customconfig.html
Section : Programmatically Modifying the Current Configuration after Initialization
including this snippet:
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
final Layout layout = PatternLayout.createDefaultLayout(config);
Appender appender = FileAppender.createAppender("target/test.log", "false", "false", "File", "true",
"false", "false", "4000", layout, null, "false", null, config);
appender.start();
config.addAppender(appender);
AppenderRef ref = AppenderRef.createAppenderRef("File", null, null);
AppenderRef[] refs = new AppenderRef[] {ref};
LoggerConfig loggerConfig = LoggerConfig.createLogger("false", "info", "org.apache.logging.log4j",
"true", refs, null, config, null );
loggerConfig.addAppender(appender, null, null);
config.addLogger("org.apache.logging.log4j", loggerConfig);
ctx.updateLoggers();
When creating the LoggerConfig with : LoggerConfig.createLogger(..)
(Now I do it with builders) we pass the Appender "refs" that includes a new Appender reference.
I checked, and my logging works with that new LoggerConfig even without passing any object to "refs", meaning "refs" is an empty array, so my question is, why are we instructed to add it to new loggerConfig?
When we create loggerConfig in static/file configuration, we need to add the Appender references to the tag (which is analogue to LoggerConfig object) so it seems needed, but programmatically we add the appender directly to the LoggerConfig and it seems to work as well.
Thanks.

The list of AppenderRefs is used only when the Configuration starts, to configure the AppenderControl. Afterwards it is no longer needed.
Therefore you don't need it, when you add appenders to a running configuration, but you can add it for coherence: they are e.g. available through JMX and a monitoring software might use them to look up appenders.

Related

How to format json with jsonformatter in serilog and also add class name and method name

I am using serilog in asp.net MVC (.net framework 4.6).
The jsonformatter allows to emit json logs in the text file using rollingfile. How can the output format be controlled for this so that we can change the json template and also add delimiters so that when the file is produced it has [ at the top and ] at the end after writting all the logs, plus comma , after ever log?
Also how to add classname and methodname in output json logs. I tried using enrich property but it is creating another object called properties. Rather than creating another object can it create a simple property like this:
{"Timestamp":"2022-10-27T16:25:44.8339002+05:30","Level":"Information","MessageTemplate":"Starting the program....","ClassName":"Program","MethodName":"Main"}
And for multiple such logs the file looks like below:
[{"Timestamp":"2022-10-27T16:25:44.8339002+05:30","Level":"Information","MessageTemplate":"Starting the program....","ClassName":"Program","MethodName":"Main"},
{"Timestamp":"2022-10-27T16:25:44.8339002+05:30","Level":"Information","MessageTemplate":"Starting the program....","ClassName":"Program","MethodName":"Main"},
{"Timestamp":"2022-10-27T16:25:44.8339002+05:30","Level":"Information","MessageTemplate":"Starting the program....","ClassName":"Program","MethodName":"Main"}]
var log = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.RollingFile(new JsonFormatter(), LogFileNameWithPath, fileSizeLimitBytes: 5242880, retainedFileCountLimit: null)
.CreateLogger();

Serilog, Change the loglevel at runtime for a specific namespace (> MinimumLevel)

This is my default Serilog configuration
SeriLogLevelSwitch.MinimumLevel = LogEventLevel.Information;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(SeriLogLevelSwitch)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.Enrich.FromLogContext()
....
How can i change the loglevel to Debug for a specific namespace at runtime when the default is Information?
Each of your MinimumLevel.Override can have its own LoggingLevelSwitch, which allows you to control the log level for each particular override at run-time.
Create individual LoggingLevelSwitch for each override that you intend to modify whilst the app is running, and store these instances in a place that you can access from other parts of your application, which will allow you to change the MinimumLevel of these LoggingLevelSwitch(es).
e.g.
public class LoggingLevelSwitches
{
// Logging level switch that will be used for the "Microsoft" namespace
public static readonly LoggingLevelSwitch MicrosoftLevelSwitch
= new LoggingLevelSwitch(LogEventLevel.Warning);
// Logging level switch that will be used for the "Microsoft.Hosting.Lifetime" namespace
public static readonly LoggingLevelSwitch MicrosoftHostingLifetimeLevelSwitch
= new LoggingLevelSwitch(LogEventLevel.Information);
}
Configure your Serilog logging pipeline to use these LoggingLevelSwitch instances:
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LoggingLevelSwitches.MicrosoftLevelSwitch)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime",
LoggingLevelSwitches.MicrosoftHostingLifetimeLevelSwitch)
.Enrich.FromLogContext()
.CreateLogger();
// ...
}
Then somewhere in your application, for example, in the code that handles your application configuration that can be changed at run-time, update the LoggingLevelSwitch instance(s) to the new LogEventLevel that you want:
public class AppSettings
{
void ChangeLoggingEventLevel()
{
LoggingLevelSwitches.MicrosoftHostingLifetimeLevelSwitch
.MinimumLevel = LogEventLevel.Error;
LoggingLevelSwitches.MicrosoftHostingLifetimeLevelSwitch
.MinimumLevel = LogEventLevel.Warning;
// ...
}
}
As you can see, the LogEventLevel is controlled by the LoggingLevelSwitch instances, so it's up to you to decide where in your application (and how) these instances will be modified, to affect the logging pipeline.
The example above I'm assuming you have a screen (or API) in your application that a user would be able to configure the logging levels.
If you don't have that, then another approach is to have a background thread that periodically checks a configuration file, an environment variable, or query a database, etc. to determine what these logging levels should be.
If you're using a .NET Core Host, you can use the Options pattern which can handle the refresh of the configuration for you, and allow you to execute code when the configuration changes (where you'd change the MinimumLevel of your LoggingLevelSwitch(es) you have.
You can use an environment variable paired with another MinimumLevel.Override to change the loglevel to Debug for a specific namespace at runtime like so:
using System;
...
SeriLogLevelSwitch.MinimumLevel = LogEventLevel.Information;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(SeriLogLevelSwitch)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.MinimumLevel.Override(Environment.GetEnvironmentVariable("SPECIFIC_NAMESPACE"), LogEventLevel.Debug)
.Enrich.FromLogContext()
....
Then, ensure the environment variable SPECIFIC_NAMESPACE is accessible by your application at runtime. Note that "namespace" is synonymous with "source context prefix"
Environment.GetEnvironmentVariable

How can I create the logger with an enricher in my baseclass that has to use something in the class?

I was using the following in my static constructor of the base class
static ApplicationBase()
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.Async(a => a.RollingFile(
new RenderedCompactJsonFormatter(),
#"c:\logs\log-{Date}.txt", fileSizeLimitBytes: 4194304))
.CreateLogger();
}
Now I need to attach one of our own custom enricher. The enricher is expecting a function that must use a modifier of the actual Application class.
For example, I need to do
.Enrich.WithStoreData(()=>GetStoreData)
Well, it doesn't have to be a function, the bottom line is, the call of GetStoreData is using an object instantiated in the actual child application class (and I cannot change the lifecycle of that object), so I can't access the object from the static constructor.
That means I have to move the logger creation to the normal base constructor. Because it has many children, how can I ensure the logger creation is executed only once? That means I have to apply a lock and check if the logger has been created already. That's really ugly.
And I am not using any container like autofac, so I will not want to create a wrapper of the logger.
At this point, I can only think of the idea creating the logger in the base constructor, and protect it with a lock.
Any other suggestion?
You can only use enrichment while configuring Serilog, and if you are configuring your static logger in your base class, then you cannot change the enrichment later at run time.
But you can use Contextual loggers at run time to add additional properties to your logger: Serilog Context and Correlation
Adding Log Context
// Log.Logger is initialized in your static base
var StudentLogger = Log.Logger.ForContext<Student>();
StudentLogger.Error(/* log message */);
Adding correlation:
// Log.Logger is initialized in your static base
var orderId = "some value";
var corrLog = Log.Logger.ForContext("orderId", orderId)
corrLog.Error(/* log message */);

Log4J 2.8.1 using PropertiesConfigurationBuilder to load properties from strings

I am looking for a way to prepare configuration and initialize a logger context from hard coded properties string; not from properties file. Following is the code that seems to be working; it logs the lines that I am expecting as per the log level. However there are few things which I am not able to figure out. I have asked them as inline questions in the code itself; appreciate inputs on the same.
Thanks in advance.
-WinCPP
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("status", "TRACE");
properties.setProperty("appenders", "CONSOLE");
properties.setProperty("appender.CONSOLE.name", "ConsoleAppender");
properties.setProperty("appender.CONSOLE.type", "Console");
properties.setProperty("appender.CONSOLE.layout.type", "PatternLayout");
properties.setProperty("appender.CONSOLE.layout.pattern", "%r [%t] %p %c %notEmpty{%x }- %m%n");
properties.setProperty("rootLogger.level", "ALL");
properties.setProperty("rootLogger.appenderRefs", "theConsoleRef");
properties.setProperty("rootLogger.appenderRef.theConsoleRef.ref", "ConsoleAppender");
PropertiesConfigurationBuilder pcb = new PropertiesConfigurationBuilder();
// Q1: Is it correct to call setConfigurationSource with 'null'?
pcb.setConfigurationSource(null).setRootProperties(properties);
PropertiesConfiguration config = pcb.build();
Configurator.initialize(config);
/* Q2: How do I do the setStatusLevel e.g. the 2nd line in commented
code below, when I am using property route to initialize the logger? Even
if I set rootLogger.level to 'ALL' (above code), I still do not see
Log4j 2.x internal messages. I can see those if I use ConfigurationBuilder, though.
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
builder.setStatusLevel(Level.TRACE);
EDIT: Add "properties.setProperty("status", "TRACE");"
*/
Logger logger = LogManager.getRootLogger();
logger.error("This is error message");
logger.info("This is info message");
logger.trace("This is trace message");
}

Serilog separate rolling files for each SourceContext

this is my serilog configuration :
Log.Logger = new LoggerConfiguration()
.WriteTo.RollingFile(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
#"Logs\log-{Date}.txt"))
.MinimumLevel.ControlledBy(LogLevelSwitch)
.CreateLogger();
i'm logging like this :
var log = Serilog.Log.ForContext<SomeClassName>();
log.Information("some log text");
My question :
I need a modification in my configuration to create separate logs for each SourceContext.
I need log files somthing like Logs\log-{Date}-{SourceContext}.txt
thanks pros...
One way to implement this is to create your own ILogEventSink implementation (the interface is very simple) that internally dispatches to one of several RollingFileSinks based on the SourceContext property of the LogEvent.
To configure it (let's say you call your sink SourceRollingFileSink) you can use WriteTo.Sink():
Log.Logger = new LoggerConfiguration()
.WriteTo.Sink(new SourceRollingFileSink())
.CreateLogger();
There's no out-of-the-box implementation so some work is required, but it should not be complicated. Be aware that implementations of ILogEventSink need to be thread-safe.

Resources