-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bye bye Log4Net, Hello Serilog #2845
Conversation
Removes references to Log4Net from Web.Config's
… build output happy
…ckages Removes AppDomainTokenConvertor - as we can add to SeriLog via a custom Enrich Property
…h Serilog CloseAndFlush- not 100% sure we need to do this as think LightInject tears it down nicely
…ckage & a custom one for the AppDomainAppId
…o Log4Net Unsure about the methods that take in args - have commented these out for now (Need to talk with Seb or Stephan about this)
…only diff currently is the log level type in the log file) Ensures we use the calling type in the Umbraco ILogger and passes it onto Serilog
# Conflicts: # src/Umbraco.Web.UI/web.Template.Debug.config
Configured to read from a custom .config file location as opposed to root web.config appsettings Enable Serilog Debugging aka Selflog
…LEF format so that it can be imported into a local copy of SEQ to help with filtering/finding log items
…ig can use it to save files with a relative path
…template so its more in sync with Serilog signature & was an easy way to find some more logs I missed too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow! epic PR, i've left some comments/questions... mostly minor stuff
@@ -33,7 +33,6 @@ if ($project) { | |||
robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log | |||
|
|||
# Delete files Umbraco ships with | |||
if(Test-Path $umbracoBinFolder\log4net.dll) { Remove-Item $umbracoBinFolder\log4net.dll -Force -Confirm:$false } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure what this does and if we need it in v8 but if we do doesn't all the serilog DLLs need to be in here too? Might need to ask seb about that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was related to when we were shipping a custom Log4Net DLL as Serilog will be a nupkg dependancy reference.
Any thoughts @nul800sebastiaan
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK after a quick slack chat with Seb this is fine & was a specific hack to Log4Net. So should all be good @Shazwazza
@@ -27,7 +27,7 @@ public void Log<T>(string text, [CallerMemberName] string callerName = null, [Ca | |||
{ | |||
// Using LogHelper since the ImageProcessor logger expects a parameterless constructor. | |||
var message = $"{callerName} {lineNumber} : {text}"; | |||
Current.Logger.Error<T>(string.Empty, new ImageProcessingException(message)); | |||
Current.Logger.Error<T>(new ImageProcessingException(message), string.Empty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make an extension method for Error to be able to pass in just an exception which in turn will pass in an empty string to the real method.
return true; | ||
case TraceEventType.Error: | ||
_logger.Error(_type.Value, string.Format("Event Id: {0}, state: {1}", eventId, state), exception ?? new Exception("Error")); | ||
_logger.Error(_type.Value, exception ?? new Exception("Error"), "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe have an extension method for error that allows us to just pass in a message and it will create the underlying exception?
/// This is used to create a new property in Logs called 'Log4NetLevel' | ||
/// So that we can map Serilog levels to Log4Net levels - so log files stay consistent | ||
/// </summary> | ||
public class Log4NetLevelMapperEnricher : ILogEventEnricher |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make internal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep just a bad habit of mine that I make everything public - will fix up
src/Umbraco.Core/Models/Member.cs
Outdated
+ $" but the {logPropertyAlias} property does not exist on the member type so a default value is returned. Ensure that you have a property type with alias: " | ||
+ logPropertyAlias + $" configured on your member type in order to use the '{logPropertyName}' property on the model correctly."); | ||
{ | ||
//WB: TODO Check that we do not need to specify properties again if used later on again in message template |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this TODO still need a follow up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to test this out - will check & verify
var e = new Exception(m); | ||
Logger.Error<ContentTypeRepository>(m, e); | ||
var e = new Exception($"ContentType '{entity.Name}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias."); | ||
Logger.Error<ContentTypeRepository>(e, "ContentType '{EntityName}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", entity.Name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another case where we could have an extension method to just pass in a message
var m = $"Property Type '{pt.Name}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias."; | ||
var e = new InvalidOperationException(m); | ||
Logger.Error<ContentTypeRepositoryBase<TEntity>>(m, e); | ||
var e = new InvalidOperationException($"Property Type '{pt.Name}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another case where we could have an extension method to just pass in a message ? this ones is a little more tricky with the typed exception and the message formatting, but might work somehow?
|
||
//Not sure if we need to do this - as my POC approach I never had to deal with this | ||
//As the LightInject container when tearing down will dispose of Serilog AFAIK | ||
Log.CloseAndFlush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it doesn't crash anything then might be better to just have it. Is it also IDisposable? i.e. would calling .Dispose() do the same thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes the logger itself is IDisposable AFAIK and hence my comment/note to say that I am not 100% sure if we need to keep in this explicit call to Log.CloseAndFlush()
@@ -356,7 +356,6 @@ internal static IEnumerable<Assembly> GetBinAssemblies() | |||
"Dynamic,", | |||
"HtmlDiff,", | |||
"Iesi.Collections,", | |||
"log4net,", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
serilog should be added here, you'd want to put Serilog.
with the dot as i think that means anything that starts with serilog will be ignored
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -29,7 +29,10 @@ public override void Log(ExceptionLoggerContext context) | |||
&& context.ExceptionContext.ActionContext.ControllerContext.Controller != null | |||
&& context.Exception != null) | |||
{ | |||
_logger.Error(context.ExceptionContext.ActionContext.ControllerContext.Controller.GetType(), "Unhandled controller exception occurred", context.Exception); | |||
var requestUrl = context.ExceptionContext.ControllerContext.Request.RequestUri.AbsoluteUri; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prob need to do some null checks on this (can use ?.
) since i think there might be cases where it's null
…add an empty string for the message
… contains only an ex - and under the hood it will pass on an empty string to the message
…t 0,1 etc- check & verified all good :)
@warrenbuckley everything looks good from my tests, only a couple more comments about the ILogger changes:
What do you think? |
…e error so all logger code goes from Fatal -> Verbose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm gonna go ahead and merge this, all looks good, i've fixed up some small things.
@@ -38,16 +38,17 @@ internal enum LogType | |||
_endMessage = endMessage; | |||
_failMessage = failMessage; | |||
_thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; | |||
_timingId = Guid.NewGuid(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the timing ID should not include spaces so that when you are viewing the log and you double click on one of these IDs, it will highlight the entire thing which means it will also highlight the other affiliated log entries. So should be Guid.NewGuid.ToString("N") and change _timingId to a string
src/Umbraco.Core/Logging/Logger.cs
Outdated
if (logger == null || logger.IsWarnEnabled == false) return; | ||
logger.Warn(format); | ||
var logger = Log.Logger; | ||
logger?.Warning(format); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is incorrect as it doesn't take into account the type, I've fixed up in the latest commit
@warrenbuckley We could simplify the void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues);
void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues);
void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues);
void Info(Type reporting, string messageTemplate, params object[] propertyValues);
void Debug(Type reporting, string messageTemplate, params object[] propertyValues);
void Verbose(Type reporting, string messageTemplate, params object[] propertyValues); For ... But then I realized that this is the meaning of "Leaky abstraction" in that now the implementer and consumer need to be aware of the implementation details. So i think the only changes needed would be to add additional ILogger methods for I've pushed a commit with those changes. I found another issue that i pushed a change for since Warn wasn't taking into account the Type in some cases, example:
Another thing I've noticed is that a new log file is created when a new AppDomain starts up. This is a known issue: serilog/serilog#380 So i'm not going to merge this in just yet! ... i think we need to solve this issue first. |
…p restarts we don't get _001 or _002 files etc
…nd examples on what can be setup & configured
@Shazwazza updated the PR File Sink with the |
|
I was going to join in the fun on this one until I realised that it was a branch I couldn't contribute to easily haha. Also I agree @Shazwazza I was thinking the same thing about the implementation etc having to care about null values. Although it could also be excused in that any decent logging platform (as Serilog says on their wiki) should handle invalid arguments being sent through as there is always unforseen situations with run-time which is the whole point of having logging in the first place to track down unexpected circumstances. |
This PR contains the initial configuration work of getting rid of Log4Net and swapping it out to use Serilog so we have better control of logging output with structured logs eventually; that's to come in a seperate PR
Things to look for
Try and check some of the following things
Testing Notes
The C# configured logged by default will generate two rolling log files (one for each day)
Two config files have been added to configure the logger
config/serilog.config
config/serilog.user.config
See if you can experiment with the two configuration files with different values (See config & filtering sections below) to output the logs you require.
Importing the JSON log file into SEQ
Install SEQ locally on your machine for free
https://getseq.net
Use the CLEF (SEQ log import tool)
https://github.com/datalust/clef-tool/releases
Details on usage here
https://docs.getseq.net/docs/importing-log-files
Using a 3rd party Serilog Sink
There are many sinks available
https://github.com/serilog/serilog/wiki/Provided-Sinks
I recommend adding/installing
PM> Install-Package Serilog.Sinks.Seq
from Nuget then add the following to theserilog.user.config
Config files
The config files uses
serilog.Settings.AppSettings
NuGet package & the project readme details some usage, however I have tried to put in some examples in theserilog.user.config
file. That hopefuly gives some examples or usages on what could be put in here.https://github.com/serilog/serilog-settings-appsettings
Filtering
Its possible for the user definied configuration to include or exclude logs based on expressions, but in the example I have shown how you would configure it so that all sinks definied in the user config will only log out messages that start with my namespace.
This is optional and could be removed alltogether. If the user wants to configure their sink to capture all of Umbraco logs AND any logs they are writing to the logger too.
https://github.com/serilog/serilog-filters-expressions
Configure/Overide logging
After a suggestion from @Shazwazza its now possible to configure Serilog in code if that is easier than trying to configure a complex Serilog pipeline only via the
serilog.user.config
file.Example that developers coulld implement (instead of serilog.user.config)
Update Global.asax
Then they will need to update the
Global.asax
file like so<%@ Application Inherits="MyNamespace.WarrensLogging" Language="C#" %>
Any Questions?
Anything doesn't make sense or is not clear leave a comment & I will try my best to describe what is going on.