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 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_DEBUG As String = "${shortdate}-${var:product}${var:suffix}-Debug.log"
Private Const FILE_NAME_FORMAT_ERROR As String = "${shortdate}-${var:product}${var:suffix}-Error.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}${event-properties:item=ModuleName}-Debug.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_ERROR_EX As String = "errorExceptionTarget"
@@ -93,11 +93,14 @@ Public Class LogConfig
Private Const FILE_NAME_ACCESS_TEST = "accessTest.txt"
Private Const FOLDER_NAME_LOG = "Log"
Private ReadOnly failSafePath As String = Path.GetTempPath()
Private ReadOnly basePath As String = failSafePath
Private Const FILE_KEEP_RANGE As Integer = 90
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
#Region "Public Properties"
Public Enum PathType As Integer
@@ -131,11 +134,10 @@ Public Class LogConfig
''' <returns>True, if debug log will be written. False otherwise.</returns>
Public Property Debug As Boolean
Get
Return isDebug
Return _isDebug
End Get
Set(isDebug As Boolean)
Me.isDebug = isDebug
'GetLogger().Debug("=> Debug is now {0}", isDebug)
_isDebug = isDebug
ReloadConfig(isDebug)
End Set
End Property
@@ -147,19 +149,27 @@ Public Class LogConfig
''' <returns>A list of log messages</returns>
Public ReadOnly Property Logs As List(Of String)
Get
Dim oTarget = config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY)
Dim oTarget = _config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY)
Return oTarget?.Logs.ToList()
End Get
End Property
Public ReadOnly Property NLogConfig As LoggingConfiguration
Get
Return config
Return _config
End Get
End Property
#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>
''' Initializes a new LogConfig object with a logpath and optinally a filename-suffix.
''' </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="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="FileKeepRangeInDays">Amount of days where files are kept and not deleted.</param>
Public Sub New(LogPath As PathType,
Optional CustomLogPath As String = Nothing,
Optional Suffix 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
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
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
basePath = failSafePath
_basePath = _failSafePath
Else 'Custom Path
basePath = CustomLogPath
_basePath = CustomLogPath
End If
' If directory does not exist, try to create it!
If Not Directory.Exists(basePath) Then
If Not Directory.Exists(_basePath) Then
Try
Directory.CreateDirectory(basePath)
Directory.CreateDirectory(_basePath)
Catch ex As Exception
' If creation fails, use failSafe path
basePath = failSafePath
_basePath = _failSafePath
End Try
End If
' Try to create a file in `basePath` to check write permissions
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)
fs.WriteByte(0)
End Using
@@ -211,7 +223,7 @@ Public Class LogConfig
File.Delete(fileAccessPath)
Catch ex As Exception
' If creation fails, use failSafe path
basePath = failSafePath
_basePath = _failSafePath
End Try
' Set the suffix to the given string if it exists
@@ -228,30 +240,69 @@ Public Class LogConfig
End If
' Create config object and initalize it
config = GetConfig(oProductName, logFileSuffix)
_config = GetConfig(oProductName, logFileSuffix)
' Save config
LogFactory = New LogFactory With {
.Configuration = config
.Configuration = _config
}
' Save log paths for files/directory
LogDirectory = basePath
LogDirectory = _basePath
LogFile = GetCurrentLogFilePath()
' Clear old Logfiles as defined in `FileKeepInterval`
ClearOldLogfiles(FileKeepRangeInDays)
End Sub
Private Sub CheckMyApplication()
Dim oAssembly = Assembly.GetEntryAssembly()
Dim oMyApp = Nothing
For Each oType As Type In oAssembly.DefinedTypes
If oType.Name = "MyApplication" Then
oMyApp = oType
Exit For
''' <summary>
''' Clears old LogFiles from the configured logpath for compliance with the GDPR
''' </summary>
''' <param name="FileKeepRange">Days in which logfiles should be kept. All files older than `Now - FileKeepInterval` will be deleted.</param>
''' <returns>True, if files were deleted as expected or no files were deleted. Otherwise false.</returns>
Private Function ClearOldLogfiles(FileKeepRange As Integer) As Boolean
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
oLogger.Info("Deleting [{0}] old logfiles that are marked for deletion in the range [last {1} days].", oFiles.Count, FileKeepRange)
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
oMyApp.GetType().GetProperty("")
End Sub
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>
''' Returns the Logger for the calling class
@@ -260,7 +311,25 @@ Public Class LogConfig
<DebuggerStepThrough()>
Public Function GetLogger() As Logger
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
''' <summary>
@@ -269,15 +338,17 @@ Public Class LogConfig
''' <param name="ClassName">The name of the class the logger belongs to</param>
''' <returns>An object of Logging.Logger</returns>
<DebuggerStepThrough()>
Public Function GetLogger(ClassName As String) As Logger
Return LogFactory.GetLogger(Of Logger)(ClassName)
Public Function GetLogger(ClassName As String, ModuleName As String) As Logger
Dim oLogger = LogFactory.GetLogger(Of Logger)(ClassName)
oLogger.ModuleName = ModuleName
Return oLogger
End Function
''' <summary>
''' Clears the internal log
''' </summary>
Public Sub ClearLogs()
Dim oTarget = config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY)
Dim oTarget = _config.FindTargetByName(Of MemoryTarget)(TARGET_MEMORY)
oTarget?.Logs.Clear()
End Sub
@@ -336,21 +407,21 @@ Public Class LogConfig
''' <param name="logFileSuffix">The chosen suffix</param>
''' <returns>A NLog.LoggingConfiguration object</returns>
Private Function GetConfig(productName As String, logFileSuffix As String) As LoggingConfiguration
config = New LoggingConfiguration()
config.Variables("product") = productName
config.Variables("suffix") = logFileSuffix
_config = New LoggingConfiguration()
_config.Variables("product") = productName
_config.Variables("suffix") = logFileSuffix
' Add default targets
config.AddTarget(TARGET_ERROR_EX, GetErrorExceptionLogTarget(basePath))
config.AddTarget(TARGET_ERROR, GetErrorLogTarget(basePath))
config.AddTarget(TARGET_DEFAULT, GetDefaultLogTarget(basePath))
config.AddTarget(TARGET_DEBUG, GetDebugLogTarget(basePath))
config.AddTarget(TARGET_MEMORY, GetMemoryDebugTarget())
_config.AddTarget(TARGET_ERROR_EX, GetErrorExceptionLogTarget(_basePath))
_config.AddTarget(TARGET_ERROR, GetErrorLogTarget(_basePath))
_config.AddTarget(TARGET_DEFAULT, GetDefaultLogTarget(_basePath))
_config.AddTarget(TARGET_DEBUG, GetDebugLogTarget(_basePath))
_config.AddTarget(TARGET_MEMORY, GetMemoryDebugTarget())
' Add default rules
AddDefaultRules(config)
AddDefaultRules(_config)
Return config
Return _config
End Function
''' <summary>
@@ -371,7 +442,7 @@ Public Class LogConfig
''' <returns>Full path of the current default log file</returns>
Private Function GetCurrentLogFilePath()
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)
Return fileName
@@ -383,15 +454,15 @@ Public Class LogConfig
''' <param name="Debug">Adds the Debug rule if true.</param>
Private Sub ReloadConfig(Optional Debug As Boolean = False)
' Clear Logging Rules
config.LoggingRules.Clear()
_config.LoggingRules.Clear()
' Add default rules
AddDefaultRules(config)
AddDefaultRules(_config)
' Add debug rule, if configured
If Debug Then
config.AddRuleForAllLevels(TARGET_DEBUG)
config.AddRuleForAllLevels(TARGET_MEMORY)
_config.AddRuleForAllLevels(TARGET_DEBUG)
_config.AddRuleForAllLevels(TARGET_MEMORY)
End If
' 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
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>
''' Prints a preformatted Block including a block identifier
''' </summary>

View File

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