Imports System.IO Imports NLog Imports NLog.Config Imports NLog.Targets ''' ''' MODULE: LogConfig ''' ''' VERSION: 0.0.0.1 ''' ''' DATE: 24.08.2018 ''' ''' DESCRIPTION: Module that writes file-logs to different locations: ''' local application data, the current directory or a custom path. ''' Files and directories will be automatically created. ''' ''' Three different logfiles will be generated: ''' ''' - Default: Warn, Error and Fatal Log Levels ''' - Detail: Info Log Level ''' - Debug: Debug Log Level ''' ''' ''' DEPENDENCIES: NLog, >= 4.5.8 ''' ''' PARAMETERS: logPath, PathType ''' The basepath to write logs to. Can be AppData, CurrentDirectory or CustomPath. ''' ''' - AppData: writes to local application data directory ''' - CurrentDirectory: writes to `Log` directory relative to the current directory ''' - CustomPath: writes to custom path specified in `customLogPath` ''' ''' customLogPath, String (optional) ''' If `logPath` is set to custom, this defines the custom logPath. ''' ''' suffix, String (optional) ''' If set to anything other than Nothing, extends the logfile name with this suffix. ''' ''' PROPERTIES: LogFile, String (readonly) ''' Returns the full path of the default log file. ''' ''' LogPath, String (readonly) ''' Returns the path to the log directory. ''' ''' LogFactory, NLog.LogFactory (readonly) ''' Returns the LogFactory that is used to create the Logger object ''' ''' Debug, Boolean ''' Determines if the debug log should be written. ''' ''' EXAMPLES: Class FooProgram ''' Private Logger as NLog.Logger ''' Private LogConfig as DigitalData.Modules.Logging.LogConfig ''' ''' Public Sub New(LogFactory as NLog.LogFactory) ''' LogConfig = new DigitalData.Modules.Logging.LogConfig(args) ''' Logger = LogConfig.LogFactory.GetCurrentClassLogger() ''' End Sub ''' ''' Public Sub Bar() ''' Logger.Info("Baz") ''' End Sub ''' End Class ''' ''' Class FooLib ''' Private Logger as NLog.Logger ''' ''' Public Sub New(LogFactory as NLog.LogFactory) ''' Logger = LogFactory.GetCurrentClassLogger() ''' End Sub ''' ''' Public Sub Bar() ''' Logger.Info("Baz") ''' End Sub ''' End Class ''' ''' REMARKS: If logpath can not be written to, falls back to temp folder as defined in: ''' https://docs.microsoft.com/de-de/dotnet/api/system.io.path.gettemppath?view=netframework-4.7.2 ''' ''' If used in a service, LogPath must be set to CustomPath, otherwise the Log will be written to System32! ''' ''' For NLog Troubleshooting, set the following Environment variables to write the NLog internal Log: ''' - NLOG_INTERNAL_LOG_LEVEL: Debug ''' - NLOG_INTERNAL_LOG_FILE: ex. C:\Temp\Nlog_Internal.log ''' Public Class LogConfig Private Const KEEP_FILES_OPEN As Boolean = False ' MAX_ARCHIVES_FILES works like this (in version 4.5.8): ' 0 = keep ALL archives files ' 1 = only keep latest logfile and NO archive files ' n = keep n archive files Private Const MAX_ARCHIVE_FILES_DEFAULT As Integer = 30 Private Const MAX_ARCHIVE_FILES_DEBUG_DETAIL As Integer = 1 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_DETAIL As String = "${shortdate}-${var:product}${var:suffix}-Detail.log" Private Const FILE_NAME_FORMAT_DEBUG As String = "${shortdate}-${var:product}${var:suffix}-Debug.log" Private Const TARGET_DEFAULT As String = "defaultTarget" Private Const TARGET_DEFAULT_EX As String = "defaultExTarget" Private Const TARGET_DETAIL As String = "detailTarget" Private Const TARGET_DEBUG As String = "debugTarget" Private Const LOG_FORMAT_BASE As String = "${longdate}|${logger}|${level:uppercase=true}" Private Const LOG_FORMAT_DEFAULT As String = LOG_FORMAT_BASE & " >> ${message}" Private Const LOG_FORMAT_EXCEPTION As String = LOG_FORMAT_BASE & " >> ${exception:format=Message}${newline}${exception:format=StackTrace}" Private ReadOnly failSafePath As String = Path.GetTempPath() Private ReadOnly basePath As String = failSafePath Private config As LoggingConfiguration Private isDebug As Boolean = False Public Enum PathType As Integer AppData = 0 CurrentDirectory = 1 CustomPath = 2 End Enum ''' ''' Returns the NLog.LogFactory object that is used to create Loggers ''' ''' LogFactory object Public ReadOnly Property LogFactory As LogFactory ''' ''' Returns the path to the current default logfile ''' ''' Filepath to the logfile Public ReadOnly Property LogFile As String ''' ''' Returns the path to the current log directory ''' ''' Directory path to the log directory Public ReadOnly Property LogDirectory As String ''' ''' Determines if a debug log will be written ''' ''' True, if debug log will be written. False otherwise. Public Property Debug As Boolean Get Return isDebug End Get Set(isDebug As Boolean) Me.isDebug = isDebug ReloadConfig(isDebug) End Set End Property ''' ''' Initializes a new Logger for a specific `Product` and optinally a filename-suffix. ''' ''' The basepath to write logs to. Can be AppData, CurrentDirectory or CustomPath. ''' If `logPath` is set to custom, this defines the custom logPath. ''' If set to anything other than Nothing, extends the logfile name with this suffix. Public Sub New(logPath As PathType, Optional customLogPath As String = Nothing, Optional suffix As String = Nothing) Dim productName As String = My.Application.Info.ProductName Dim companyName As String = My.Application.Info.CompanyName If logPath = PathType.AppData Then Dim appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) basePath = Path.Combine(appDataDir, companyName, productName) ElseIf logPath = PathType.CurrentDirectory Then Dim currentDirectory As String = My.Application.Info.DirectoryPath basePath = Path.Combine(currentDirectory, "Log") Else 'Custom Path basePath = customLogPath End If ' If directory does not exist, try to create it! If Not Directory.Exists(basePath) Then Try Directory.CreateDirectory(basePath) Catch ex As Exception ' If creation fails, use failSafe path basePath = failSafePath End Try End If ' Try to create a file in `basePath` to check write permissions Try Dim fileAccessPath = Path.Combine(basePath, "accessTest.txt") Using fs As FileStream = File.Create(fileAccessPath) fs.WriteByte(0) End Using File.Delete(fileAccessPath) Catch ex As UnauthorizedAccessException ' If creation fails, use failSafe path basePath = failSafePath End Try ' Set the suffix to the given string if it exists Dim logFileSuffix As String = String.Empty If suffix IsNot Nothing AndAlso suffix.Count > 0 Then logFileSuffix = $"-{suffix}" End If ' Create config object and initalize it config = GetConfig(productName, logFileSuffix) ' Save config LogFactory = New LogFactory With { .Configuration = config } ' Save log paths for files/directory LogDirectory = basePath LogFile = GetCurrentLogFilePath() End Sub ''' ''' Returns the initial log configuration ''' ''' The chosen productname ''' The chosen suffix ''' A NLog.LoggingConfiguration object Private Function GetConfig(productName As String, logFileSuffix As String) As LoggingConfiguration config = New LoggingConfiguration() config.Variables("product") = productName config.Variables("suffix") = logFileSuffix ' Add default targets config.AddTarget(TARGET_DEFAULT_EX, GetDefaultLogTargetWithExceptions(basePath)) config.AddTarget(TARGET_DEFAULT, GetDefaultLogTarget(basePath)) config.AddTarget(TARGET_DETAIL, GetDetailLogTarget(basePath)) config.AddTarget(TARGET_DEBUG, GetDebugLogTarget(basePath)) ' Add default rules config.AddRuleForOneLevel(LogLevel.Error, TARGET_DEFAULT_EX) config.AddRuleForOneLevel(LogLevel.Fatal, TARGET_DEFAULT_EX) config.AddRuleForOneLevel(LogLevel.Warn, TARGET_DEFAULT) config.AddRuleForOneLevel(LogLevel.Info, TARGET_DETAIL) Return config End Function ''' ''' Returns the full path of the current default log file. ''' ''' Full path of the current default log file Private Function GetCurrentLogFilePath() Dim logEventInfo As New LogEventInfo() With {.TimeStamp = Date.Now} Dim target As FileTarget = config.FindTargetByName(TARGET_DEFAULT) Dim fileName As String = target.FileName.Render(logEventInfo) Return fileName End Function ''' ''' Reconfigures and re-adds all loggers, optionally adding the debug rule. ''' ''' Adds the Debug rule if true. Private Sub ReloadConfig(Optional Debug As Boolean = False) ' Clear Logging Rules config.LoggingRules.Clear() ' Add default targets config.AddRuleForOneLevel(LogLevel.Error, TARGET_DEFAULT_EX) config.AddRuleForOneLevel(LogLevel.Fatal, TARGET_DEFAULT_EX) config.AddRuleForOneLevel(LogLevel.Warn, TARGET_DEFAULT) config.AddRuleForOneLevel(LogLevel.Info, TARGET_DETAIL) ' Add debug target, if configured If Debug Then config.AddRuleForOneLevel(LogLevel.Debug, TARGET_DEBUG) End If ' Reload all running loggers LogManager.ReconfigExistingLoggers() End Sub #Region "Log Targets" Private Function GetDefaultLogTarget(basePath As String) As FileTarget Dim defaultLog As New FileTarget() With { .FileName = Path.Combine(basePath, FILE_NAME_FORMAT_DEFAULT), .Name = TARGET_DEFAULT, .Layout = LOG_FORMAT_DEFAULT, .MaxArchiveFiles = MAX_ARCHIVE_FILES_DEFAULT, .ArchiveEvery = ARCHIVE_EVERY, .KeepFileOpen = KEEP_FILES_OPEN } Return defaultLog End Function Private Function GetDefaultLogTargetWithExceptions(basePath As String) As FileTarget Dim defaultLogWithExceptionData As New FileTarget() With { .FileName = Path.Combine(basePath, FILE_NAME_FORMAT_DEFAULT), .Name = TARGET_DEFAULT_EX, .Layout = LOG_FORMAT_EXCEPTION, .MaxArchiveFiles = MAX_ARCHIVE_FILES_DEFAULT, .ArchiveEvery = ARCHIVE_EVERY, .KeepFileOpen = KEEP_FILES_OPEN } Return defaultLogWithExceptionData End Function Private Function GetDetailLogTarget(basePath As String) As FileTarget Dim detailLog As New FileTarget() With { .FileName = Path.Combine(basePath, FILE_NAME_FORMAT_DETAIL), .Name = TARGET_DETAIL, .Layout = LOG_FORMAT_DEFAULT, .MaxArchiveFiles = MAX_ARCHIVE_FILES_DEBUG_DETAIL, .ArchiveEvery = ARCHIVE_EVERY, .KeepFileOpen = KEEP_FILES_OPEN } Return detailLog End Function Private Function GetDebugLogTarget(basePath As String) As FileTarget Dim debugLog As New FileTarget() With { .FileName = Path.Combine(basePath, FILE_NAME_FORMAT_DEBUG), .Name = TARGET_DEBUG, .Layout = LOG_FORMAT_DEFAULT, .MaxArchiveFiles = MAX_ARCHIVE_FILES_DEBUG_DETAIL, .ArchiveEvery = ARCHIVE_EVERY, .KeepFileOpen = KEEP_FILES_OPEN } Return debugLog End Function #End Region End Class