Logging: Add ClearOldLogfiles to remove logfiles older than x, Add ModuleName to create subloggers with separate logfiles

This commit is contained in:
Jonathan Jenne
2021-05-17 14:34:41 +02:00
parent f50fbe7099
commit 9e7c840579
4 changed files with 188 additions and 53 deletions

View File

@@ -71,9 +71,9 @@ Public Class LogConfig
Private Const MAX_ARCHIVE_FILES_DEBUG_DETAIL As Integer = 0 Private Const MAX_ARCHIVE_FILES_DEBUG_DETAIL As Integer = 0
Private Const ARCHIVE_EVERY As FileArchivePeriod = FileArchivePeriod.Day Private Const ARCHIVE_EVERY As FileArchivePeriod = FileArchivePeriod.Day
Private Const FILE_NAME_FORMAT_DEFAULT As String = "${shortdate}-${var:product}${var:suffix}.log" Private Const FILE_NAME_FORMAT_DEFAULT As String = "${shortdate}-${var:product}${var:suffix}${event-properties:item=ModuleName}.log"
Private Const FILE_NAME_FORMAT_DEBUG As String = "${shortdate}-${var:product}${var:suffix}-Debug.log" Private Const FILE_NAME_FORMAT_DEBUG As String = "${shortdate}-${var:product}${var:suffix}${event-properties:item=ModuleName}-Debug.log"
Private Const FILE_NAME_FORMAT_ERROR As String = "${shortdate}-${var:product}${var:suffix}-Error.log" Private Const FILE_NAME_FORMAT_ERROR As String = "${shortdate}-${var:product}${var:suffix}${event-properties:item=ModuleName}-Error.log"
Private Const TARGET_DEFAULT As String = "defaultTarget" Private Const TARGET_DEFAULT As String = "defaultTarget"
Private Const TARGET_ERROR_EX As String = "errorExceptionTarget" Private Const TARGET_ERROR_EX As String = "errorExceptionTarget"
@@ -93,11 +93,14 @@ Public Class LogConfig
Private Const FILE_NAME_ACCESS_TEST = "accessTest.txt" Private Const FILE_NAME_ACCESS_TEST = "accessTest.txt"
Private Const FOLDER_NAME_LOG = "Log" Private Const FOLDER_NAME_LOG = "Log"
Private ReadOnly failSafePath As String = Path.GetTempPath() Private Const FILE_KEEP_RANGE As Integer = 90
Private ReadOnly basePath As String = failSafePath
Private ReadOnly _failSafePath As String = Path.GetTempPath()
Private ReadOnly _basePath As String = _failSafePath
Private _config As LoggingConfiguration
Private _isDebug As Boolean = False
Private config As LoggingConfiguration
Private isDebug As Boolean = False
#End Region #End Region
#Region "Public Properties" #Region "Public Properties"
Public Enum PathType As Integer Public Enum PathType As Integer
@@ -131,11 +134,10 @@ Public Class LogConfig
''' <returns>True, if debug log will be written. False otherwise.</returns> ''' <returns>True, if debug log will be written. False otherwise.</returns>
Public Property Debug As Boolean Public Property Debug As Boolean
Get Get
Return isDebug Return _isDebug
End Get End Get
Set(isDebug As Boolean) Set(isDebug As Boolean)
Me.isDebug = isDebug _isDebug = isDebug
'GetLogger().Debug("=> Debug is now {0}", isDebug)
ReloadConfig(isDebug) ReloadConfig(isDebug)
End Set End Set
End Property End Property
@@ -147,19 +149,27 @@ Public Class LogConfig
''' <returns>A list of log messages</returns> ''' <returns>A list of log messages</returns>
Public ReadOnly Property Logs As List(Of String) Public ReadOnly Property Logs As List(Of String)
Get Get
Dim oTarget = config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY) Dim oTarget = _config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY)
Return oTarget?.Logs.ToList() Return oTarget?.Logs.ToList()
End Get End Get
End Property End Property
Public ReadOnly Property NLogConfig As LoggingConfiguration Public ReadOnly Property NLogConfig As LoggingConfiguration
Get Get
Return config Return _config
End Get End Get
End Property End Property
#End Region #End Region
''' <summary>
''' Initializes a new LogConfig object with the options supplied as a LogOptions object
''' </summary>
''' <param name="Options"></param>
Public Sub New(Options As LogOptions)
MyClass.New(Options.LogPath, Options.CustomLogPath, Options.Suffix, Options.CompanyName, Options.ProductName, Options.FileKeepInterval)
End Sub
''' <summary> ''' <summary>
''' Initializes a new LogConfig object with a logpath and optinally a filename-suffix. ''' Initializes a new LogConfig object with a logpath and optinally a filename-suffix.
''' </summary> ''' </summary>
@@ -168,11 +178,13 @@ Public Class LogConfig
''' <param name="Suffix">If set to anything other than Nothing, extends the logfile name with this suffix.</param> ''' <param name="Suffix">If set to anything other than Nothing, extends the logfile name with this suffix.</param>
''' <param name="CompanyName">CompanyName is used to construct log-path in when LogPath is set to PathType:AppData</param> ''' <param name="CompanyName">CompanyName is used to construct log-path in when LogPath is set to PathType:AppData</param>
''' <param name="ProductName">ProductName is used to construct log-path in when LogPath is set to PathType:AppData</param> ''' <param name="ProductName">ProductName is used to construct log-path in when LogPath is set to PathType:AppData</param>
''' <param name="FileKeepRangeInDays">Amount of days where files are kept and not deleted.</param>
Public Sub New(LogPath As PathType, Public Sub New(LogPath As PathType,
Optional CustomLogPath As String = Nothing, Optional CustomLogPath As String = Nothing,
Optional Suffix As String = Nothing, Optional Suffix As String = Nothing,
Optional CompanyName As String = Nothing, Optional CompanyName As String = Nothing,
Optional ProductName As String = Nothing) Optional ProductName As String = Nothing,
Optional FileKeepRangeInDays As Integer = FILE_KEEP_RANGE)
If LogPath = PathType.AppData And (ProductName Is Nothing Or CompanyName Is Nothing) Then If LogPath = PathType.AppData And (ProductName Is Nothing Or CompanyName Is Nothing) Then
Throw New ArgumentException("Modules.Logging: PathType is AppData and either CompanyName or ProductName was not supplied!") Throw New ArgumentException("Modules.Logging: PathType is AppData and either CompanyName or ProductName was not supplied!")
@@ -184,26 +196,26 @@ Public Class LogConfig
If LogPath = PathType.AppData Then If LogPath = PathType.AppData Then
Dim appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) Dim appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
basePath = Path.Combine(appDataDir, CompanyName, ProductName, FOLDER_NAME_LOG) _basePath = Path.Combine(appDataDir, CompanyName, ProductName, FOLDER_NAME_LOG)
ElseIf LogPath = PathType.Temp Then ElseIf LogPath = PathType.Temp Then
basePath = failSafePath _basePath = _failSafePath
Else 'Custom Path Else 'Custom Path
basePath = CustomLogPath _basePath = CustomLogPath
End If End If
' If directory does not exist, try to create it! ' If directory does not exist, try to create it!
If Not Directory.Exists(basePath) Then If Not Directory.Exists(_basePath) Then
Try Try
Directory.CreateDirectory(basePath) Directory.CreateDirectory(_basePath)
Catch ex As Exception Catch ex As Exception
' If creation fails, use failSafe path ' If creation fails, use failSafe path
basePath = failSafePath _basePath = _failSafePath
End Try End Try
End If End If
' Try to create a file in `basePath` to check write permissions ' Try to create a file in `basePath` to check write permissions
Try Try
Dim fileAccessPath = Path.Combine(basePath, FILE_NAME_ACCESS_TEST) Dim fileAccessPath = Path.Combine(_basePath, FILE_NAME_ACCESS_TEST)
Using fs As FileStream = File.Create(fileAccessPath) Using fs As FileStream = File.Create(fileAccessPath)
fs.WriteByte(0) fs.WriteByte(0)
End Using End Using
@@ -211,7 +223,7 @@ Public Class LogConfig
File.Delete(fileAccessPath) File.Delete(fileAccessPath)
Catch ex As Exception Catch ex As Exception
' If creation fails, use failSafe path ' If creation fails, use failSafe path
basePath = failSafePath _basePath = _failSafePath
End Try End Try
' Set the suffix to the given string if it exists ' Set the suffix to the given string if it exists
@@ -228,30 +240,69 @@ Public Class LogConfig
End If End If
' Create config object and initalize it ' Create config object and initalize it
config = GetConfig(oProductName, logFileSuffix) _config = GetConfig(oProductName, logFileSuffix)
' Save config ' Save config
LogFactory = New LogFactory With { LogFactory = New LogFactory With {
.Configuration = config .Configuration = _config
} }
' Save log paths for files/directory ' Save log paths for files/directory
LogDirectory = basePath LogDirectory = _basePath
LogFile = GetCurrentLogFilePath() LogFile = GetCurrentLogFilePath()
' Clear old Logfiles as defined in `FileKeepInterval`
ClearOldLogfiles(FileKeepRangeInDays)
End Sub End Sub
Private Sub CheckMyApplication() ''' <summary>
Dim oAssembly = Assembly.GetEntryAssembly() ''' Clears old LogFiles from the configured logpath for compliance with the GDPR
Dim oMyApp = Nothing ''' </summary>
For Each oType As Type In oAssembly.DefinedTypes ''' <param name="FileKeepRange">Days in which logfiles should be kept. All files older than `Now - FileKeepInterval` will be deleted.</param>
If oType.Name = "MyApplication" Then ''' <returns>True, if files were deleted as expected or no files were deleted. Otherwise false.</returns>
oMyApp = oType Private Function ClearOldLogfiles(FileKeepRange As Integer) As Boolean
Exit For Dim oClassName As String = GetClassFullName()
Dim oLogger As Logger = GetLogger(oClassName)
Try
Dim oContinueOnError = True
Dim oUnableToDeleteCounter = 0
Dim oDirectory As New DirectoryInfo(LogDirectory)
Dim oDateLimit As Date = Date.Now.AddDays(-FileKeepRange)
Dim oFiles As List(Of FileInfo) = oDirectory.
EnumerateFiles().
Where(Function(oFileInfo As FileInfo) oFileInfo.Extension = ".log" And oFileInfo.LastWriteTime < oDateLimit).
ToList()
If oFiles.Count = 0 Then
oLogger.Info("No logfiles were marked for deletion in the range [last {0} days].", FileKeepRange)
Return True
End If End If
Next
oMyApp.GetType().GetProperty("") oLogger.Info("Deleting [{0}] old logfiles that are marked for deletion in the range [last {1} days].", oFiles.Count, FileKeepRange)
End Sub
For Each oFile As FileInfo In oFiles
Try
oFile.Delete()
Catch ex As Exception
oUnableToDeleteCounter += 1
oLogger.Warn("File {0} could not be deleted!")
End Try
Next
If oUnableToDeleteCounter > 0 Then
oLogger.Info("Delete old logfiles partially. {0} files could not be deleted.", oUnableToDeleteCounter)
Else
oLogger.Info("Deleted [{0}] old logfiles.", oFiles.Count)
End If
Return True
Catch ex As Exception
oLogger.Error(ex)
Return False
End Try
End Function
''' <summary> ''' <summary>
''' Returns the Logger for the calling class ''' Returns the Logger for the calling class
@@ -260,7 +311,25 @@ Public Class LogConfig
<DebuggerStepThrough()> <DebuggerStepThrough()>
Public Function GetLogger() As Logger Public Function GetLogger() As Logger
Dim oClassName As String = GetClassFullName() Dim oClassName As String = GetClassFullName()
Return LogFactory.GetLogger(Of Logger)(oClassName) Return GetLogger(oClassName, String.Empty)
End Function
''' <summary>
''' Returns the Logger for the specified classname
''' </summary>
''' <returns>An object of Logging.Logger</returns>
<DebuggerStepThrough()>
Public Function GetLogger(ClassName As String) As Logger
Return GetLogger(ClassName, String.Empty)
End Function
''' <summary>
''' Returns the Logger for the specified module
''' </summary>
''' <returns>An object of Logging.Logger</returns>
Public Function GetLoggerWithModule(ModuleName As String) As Logger
Dim oClassName As String = GetClassFullName()
Return GetLogger(oClassName, ModuleName)
End Function End Function
''' <summary> ''' <summary>
@@ -269,15 +338,17 @@ Public Class LogConfig
''' <param name="ClassName">The name of the class the logger belongs to</param> ''' <param name="ClassName">The name of the class the logger belongs to</param>
''' <returns>An object of Logging.Logger</returns> ''' <returns>An object of Logging.Logger</returns>
<DebuggerStepThrough()> <DebuggerStepThrough()>
Public Function GetLogger(ClassName As String) As Logger Public Function GetLogger(ClassName As String, ModuleName As String) As Logger
Return LogFactory.GetLogger(Of Logger)(ClassName) Dim oLogger = LogFactory.GetLogger(Of Logger)(ClassName)
oLogger.ModuleName = ModuleName
Return oLogger
End Function End Function
''' <summary> ''' <summary>
''' Clears the internal log ''' Clears the internal log
''' </summary> ''' </summary>
Public Sub ClearLogs() Public Sub ClearLogs()
Dim oTarget = config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY) Dim oTarget = _config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY)
oTarget?.Logs.Clear() oTarget?.Logs.Clear()
End Sub End Sub
@@ -336,21 +407,21 @@ Public Class LogConfig
''' <param name="logFileSuffix">The chosen suffix</param> ''' <param name="logFileSuffix">The chosen suffix</param>
''' <returns>A NLog.LoggingConfiguration object</returns> ''' <returns>A NLog.LoggingConfiguration object</returns>
Private Function GetConfig(productName As String, logFileSuffix As String) As LoggingConfiguration Private Function GetConfig(productName As String, logFileSuffix As String) As LoggingConfiguration
config = New LoggingConfiguration() _config = New LoggingConfiguration()
config.Variables("product") = productName _config.Variables("product") = productName
config.Variables("suffix") = logFileSuffix _config.Variables("suffix") = logFileSuffix
' Add default targets ' Add default targets
config.AddTarget(TARGET_ERROR_EX, GetErrorExceptionLogTarget(basePath)) _config.AddTarget(TARGET_ERROR_EX, GetErrorExceptionLogTarget(_basePath))
config.AddTarget(TARGET_ERROR, GetErrorLogTarget(basePath)) _config.AddTarget(TARGET_ERROR, GetErrorLogTarget(_basePath))
config.AddTarget(TARGET_DEFAULT, GetDefaultLogTarget(basePath)) _config.AddTarget(TARGET_DEFAULT, GetDefaultLogTarget(_basePath))
config.AddTarget(TARGET_DEBUG, GetDebugLogTarget(basePath)) _config.AddTarget(TARGET_DEBUG, GetDebugLogTarget(_basePath))
config.AddTarget(TARGET_MEMORY, GetMemoryDebugTarget()) _config.AddTarget(TARGET_MEMORY, GetMemoryDebugTarget())
' Add default rules ' Add default rules
AddDefaultRules(config) AddDefaultRules(_config)
Return config Return _config
End Function End Function
''' <summary> ''' <summary>
@@ -371,7 +442,7 @@ Public Class LogConfig
''' <returns>Full path of the current default log file</returns> ''' <returns>Full path of the current default log file</returns>
Private Function GetCurrentLogFilePath() Private Function GetCurrentLogFilePath()
Dim logEventInfo As New LogEventInfo() With {.TimeStamp = Date.Now} Dim logEventInfo As New LogEventInfo() With {.TimeStamp = Date.Now}
Dim target As FileTarget = config.FindTargetByName(TARGET_DEFAULT) Dim target As FileTarget = _config.FindTargetByName(TARGET_DEFAULT)
Dim fileName As String = target.FileName.Render(logEventInfo) Dim fileName As String = target.FileName.Render(logEventInfo)
Return fileName Return fileName
@@ -383,15 +454,15 @@ Public Class LogConfig
''' <param name="Debug">Adds the Debug rule if true.</param> ''' <param name="Debug">Adds the Debug rule if true.</param>
Private Sub ReloadConfig(Optional Debug As Boolean = False) Private Sub ReloadConfig(Optional Debug As Boolean = False)
' Clear Logging Rules ' Clear Logging Rules
config.LoggingRules.Clear() _config.LoggingRules.Clear()
' Add default rules ' Add default rules
AddDefaultRules(config) AddDefaultRules(_config)
' Add debug rule, if configured ' Add debug rule, if configured
If Debug Then If Debug Then
config.AddRuleForAllLevels(TARGET_DEBUG) _config.AddRuleForAllLevels(TARGET_DEBUG)
config.AddRuleForAllLevels(TARGET_MEMORY) _config.AddRuleForAllLevels(TARGET_MEMORY)
End If End If
' Reload all running loggers ' Reload all running loggers

View File

@@ -0,0 +1,10 @@
Imports DigitalData.Modules.Logging.LogConfig
Public Class LogOptions
Property LogPath As PathType
Property CustomLogPath As String = Nothing
Property Suffix As String = Nothing
Property CompanyName As String = Nothing
Property ProductName As String = Nothing
Property FileKeepInterval As Integer = 0
End Class

View File

@@ -4,6 +4,59 @@ Public Class Logger
Inherits NLog.Logger Inherits NLog.Logger
Implements ILogger Implements ILogger
''' <summary>
''' Optional ModuleName to create different LogFiles for different Modules (arbitrary entities, User, File, etc.)
''' </summary>
''' <returns>The current ModuleName of the logger, if any.</returns>
Public Property ModuleName As String = Nothing
Public Overloads Sub Info(Message As String)
LogWithModuleName(Message, LogLevel.Info)
End Sub
Public Overloads Sub Warn(Message As String)
LogWithModuleName(Message, LogLevel.Warn)
End Sub
Public Overloads Sub [Error](Exception As Exception)
LogWithModuleName(Exception)
End Sub
Public Overloads Sub Debug(Message As String)
LogWithModuleName(Message, LogLevel.Debug)
End Sub
Public Overloads Sub Trace(Message As String)
LogWithModuleName(Message, LogLevel.Trace)
End Sub
Private Sub LogWithModuleName(Exception As Exception)
Dim oEventInfo As New LogEventInfo() With {
.Exception = Exception,
.Level = LogLevel.Error,
.LoggerName = Name,
.Message = Exception.Message
}
oEventInfo = MaybeSetModuleName(oEventInfo)
Log(oEventInfo)
End Sub
Private Sub LogWithModuleName(Message As String, LogLevel As LogLevel)
Dim oEventInfo As New LogEventInfo(LogLevel, Name, Message)
oEventInfo = MaybeSetModuleName(oEventInfo)
Log(oEventInfo)
End Sub
Private Function MaybeSetModuleName(LogEventInfo As LogEventInfo) As LogEventInfo
If ModuleName IsNot Nothing AndAlso ModuleName <> String.Empty Then
LogEventInfo.Properties.Item("ModuleName") = ModuleName
End If
Return LogEventInfo
End Function
''' <summary> ''' <summary>
''' Prints a preformatted Block including a block identifier ''' Prints a preformatted Block including a block identifier
''' </summary> ''' </summary>

View File

@@ -74,6 +74,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="LogConfig.vb" /> <Compile Include="LogConfig.vb" />
<Compile Include="Logger.vb" /> <Compile Include="Logger.vb" />
<Compile Include="LogOptions.vb" />
<Compile Include="My Project\AssemblyInfo.vb" /> <Compile Include="My Project\AssemblyInfo.vb" />
<Compile Include="My Project\Application.Designer.vb"> <Compile Include="My Project\Application.Designer.vb">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>