From 51bca5c2ec4b01680507e5807de465493fa48868 Mon Sep 17 00:00:00 2001 From: Levi--G Date: Fri, 31 May 2024 14:40:49 +0200 Subject: [PATCH 1/4] Added different FileRollingConventions --- src/NReco.Logging.File/FileLoggerOptions.cs | 23 ++ src/NReco.Logging.File/FileLoggerProvider.cs | 330 +++++++++++++------ 2 files changed, 259 insertions(+), 94 deletions(-) diff --git a/src/NReco.Logging.File/FileLoggerOptions.cs b/src/NReco.Logging.File/FileLoggerOptions.cs index 4a81cc8..dc09d62 100644 --- a/src/NReco.Logging.File/FileLoggerOptions.cs +++ b/src/NReco.Logging.File/FileLoggerOptions.cs @@ -98,6 +98,29 @@ public class FileLoggerOptions /// public Action HandleFileError { get; set; } + /// + /// Determines the naming convention and order of rolling files. + /// + public FileRollingConvention RollingFilesConvention { get; set; } = FileRollingConvention.Rolling; + + /// + /// Holds the different file rolling convention, the default option being Rolling. + /// + public enum FileRollingConvention + { + /// + /// (Default) New files will get a rolling index, files get rolled after max 0-1-2-3-0-1-2-3. + /// + Rolling, + /// + /// New files will get a rolling index, but the latest file is always the file without index. More performant alt for Unix rolling. 0-1-2-3-1-2-3 + /// + RollingStableBase, + /// + /// Unix like logging, the base will always be stable and contain the latest logs, new files will be incremented and renamed so the highest number is always the oldest. 0-1-2-3 + /// + Unix + } } diff --git a/src/NReco.Logging.File/FileLoggerProvider.cs b/src/NReco.Logging.File/FileLoggerProvider.cs index 2984f02..893b146 100644 --- a/src/NReco.Logging.File/FileLoggerProvider.cs +++ b/src/NReco.Logging.File/FileLoggerProvider.cs @@ -23,13 +23,15 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; -namespace NReco.Logging.File { +namespace NReco.Logging.File +{ /// /// Generic file logger provider. /// [ProviderAlias("File")] - public class FileLoggerProvider : ILoggerProvider { + public class FileLoggerProvider : ILoggerProvider + { private string LogFileName; @@ -45,50 +47,58 @@ public class FileLoggerProvider : ILoggerProvider { private long FileSizeLimitBytes => Options.FileSizeLimitBytes; private int MaxRollingFiles => Options.MaxRollingFiles; - public LogLevel MinLevel { - get => Options.MinLevel; + public LogLevel MinLevel + { + get => Options.MinLevel; set { Options.MinLevel = value; } } /// /// Gets or sets indication whether or not UTC timezone should be used to for timestamps in logging messages. Defaults to false. /// - public bool UseUtcTimestamp { - get => Options.UseUtcTimestamp; - set { Options.UseUtcTimestamp = value; } + public bool UseUtcTimestamp + { + get => Options.UseUtcTimestamp; + set { Options.UseUtcTimestamp = value; } } /// /// Custom formatter for log entry. /// - public Func FormatLogEntry { - get => Options.FormatLogEntry; + public Func FormatLogEntry + { + get => Options.FormatLogEntry; set { Options.FormatLogEntry = value; } } /// /// Custom formatter for the log file name. /// - public Func FormatLogFileName { - get => Options.FormatLogFileName; + public Func FormatLogFileName + { + get => Options.FormatLogFileName; set { Options.FormatLogFileName = value; } } /// /// Custom handler for file errors. /// - public Action HandleFileError { + public Action HandleFileError + { get => Options.HandleFileError; - set { Options.HandleFileError = value; } + set { Options.HandleFileError = value; } } - public FileLoggerProvider(string fileName) : this(fileName, true) { + public FileLoggerProvider(string fileName) : this(fileName, true) + { } - public FileLoggerProvider(string fileName, bool append) : this(fileName, new FileLoggerOptions() { Append = append } ) { + public FileLoggerProvider(string fileName, bool append) : this(fileName, new FileLoggerOptions() { Append = append }) + { } - public FileLoggerProvider(string fileName, FileLoggerOptions options) { + public FileLoggerProvider(string fileName, FileLoggerOptions options) + { Options = options; LogFileName = Environment.ExpandEnvironmentVariables(fileName); @@ -99,58 +109,79 @@ public FileLoggerProvider(string fileName, FileLoggerOptions options) { TaskCreationOptions.LongRunning); } - public ILogger CreateLogger(string categoryName) { + public ILogger CreateLogger(string categoryName) + { return loggers.GetOrAdd(categoryName, CreateLoggerImplementation); } - public void Dispose() { + public void Dispose() + { entryQueue.CompleteAdding(); - try { + try + { processQueueTask.Wait(1500); // the same as in ConsoleLogger - } catch (TaskCanceledException) { } catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { } + } + catch (TaskCanceledException) { } + catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { } loggers.Clear(); fWriter.Close(); } - private FileLogger CreateLoggerImplementation(string name) { + private FileLogger CreateLoggerImplementation(string name) + { return new FileLogger(name, this); } - internal void WriteEntry(string message) { - if (!entryQueue.IsAddingCompleted) { - try { + internal void WriteEntry(string message) + { + if (!entryQueue.IsAddingCompleted) + { + try + { entryQueue.Add(message); return; - } catch (InvalidOperationException) { } + } + catch (InvalidOperationException) { } } // do nothing } - private void ProcessQueue() { + private void ProcessQueue() + { var writeMessageFailed = false; - foreach (var message in entryQueue.GetConsumingEnumerable()) { - try { + foreach (var message in entryQueue.GetConsumingEnumerable()) + { + try + { if (!writeMessageFailed) fWriter.WriteMessage(message, entryQueue.Count == 0); - } catch (Exception ex) { + } + catch (Exception ex) + { // something goes wrong. App's code can handle it if 'HandleFileError' is provided var stopLogging = true; - if (HandleFileError != null) { + if (HandleFileError != null) + { var fileErr = new FileError(LogFileName, ex); - try { + try + { HandleFileError(fileErr); - if (fileErr.NewLogFileName != null) { + if (fileErr.NewLogFileName != null) + { fWriter.UseNewLogFile(fileErr.NewLogFileName); // write failed message to a new log file fWriter.WriteMessage(message, entryQueue.Count == 0); stopLogging = false; } - } catch { + } + catch + { // exception is possible in HandleFileError or if proposed file name cannot be used // let's ignore it in that case -> file logger will stop processing log messages } } - if (stopLogging) { + if (stopLogging) + { // Stop processing log messages since they cannot be written to a log file entryQueue.CompleteAdding(); writeMessageFailed = true; @@ -159,141 +190,201 @@ private void ProcessQueue() { } } - private static void ProcessQueue(object state) { + private static void ProcessQueue(object state) + { var fileLogger = (FileLoggerProvider)state; fileLogger.ProcessQueue(); } - internal class FileWriter { + internal class FileWriter + { readonly FileLoggerProvider FileLogPrv; string LogFileName; + int RollingNumber; Stream LogFileStream; TextWriter LogFileWriter; - internal FileWriter(FileLoggerProvider fileLogPrv) { + internal FileWriter(FileLoggerProvider fileLogPrv) + { FileLogPrv = fileLogPrv; DetermineLastFileLogName(); OpenFile(FileLogPrv.Append); } - string GetBaseLogFileName() { + string GetBaseLogFileName() + { var fName = FileLogPrv.LogFileName; if (FileLogPrv.FormatLogFileName != null) fName = FileLogPrv.FormatLogFileName(fName); return fName; } - void DetermineLastFileLogName() { + void DetermineLastFileLogName() + { var baseLogFileName = GetBaseLogFileName(); __LastBaseLogFileName = baseLogFileName; - if (FileLogPrv.FileSizeLimitBytes>0) { + if (FileLogPrv.FileSizeLimitBytes > 0) + { // rolling file is used - var logFileMask = Path.GetFileNameWithoutExtension(baseLogFileName) + "*" + Path.GetExtension(baseLogFileName); - var logDirName = Path.GetDirectoryName(baseLogFileName); - if (String.IsNullOrEmpty(logDirName)) - logDirName = Directory.GetCurrentDirectory(); - var logFiles = Directory.Exists(logDirName) ? Directory.GetFiles(logDirName, logFileMask, SearchOption.TopDirectoryOnly) : Array.Empty(); - if (logFiles.Length>0) { - var lastFileInfo = logFiles - .Select(fName => new FileInfo(fName)) - .OrderByDescending(fInfo => fInfo.Name) - .OrderByDescending(fInfo => fInfo.LastWriteTime).First(); - LogFileName = lastFileInfo.FullName; - } else { - // no files yet, use default name + if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Rolling) + { + var logFiles = GetExistingLogFiles(baseLogFileName); + if (logFiles.Length > 0) + { + var lastFileInfo = logFiles + .OrderByDescending(fInfo => fInfo.Name) + .OrderByDescending(fInfo => fInfo.LastWriteTime).First(); + LogFileName = lastFileInfo.FullName; + } + else + { + // no files yet, use default name + LogFileName = baseLogFileName; + } + } + else + { LogFileName = baseLogFileName; } - } else { + } + else + { LogFileName = baseLogFileName; } } - void createLogFileStream(bool append) { + void createLogFileStream(bool append) + { var fileInfo = new FileInfo(LogFileName); // Directory.Create will check if the directory already exists, // so there is no need for a "manual" check first. fileInfo.Directory.Create(); LogFileStream = new FileStream(LogFileName, FileMode.OpenOrCreate, FileAccess.Write); - if (append) { + if (append) + { LogFileStream.Seek(0, SeekOrigin.End); - } else { + } + else + { LogFileStream.SetLength(0); // clear the file } LogFileWriter = new StreamWriter(LogFileStream); } - internal void UseNewLogFile(string newLogFileName) { + internal void UseNewLogFile(string newLogFileName) + { FileLogPrv.LogFileName = newLogFileName; DetermineLastFileLogName(); // preserve all existing logic related to 'FormatLogFileName' and rolling files createLogFileStream(FileLogPrv.Append); // if file error occurs here it is not handled by 'HandleFileError' recursively } - void OpenFile(bool append) { - try { + void OpenFile(bool append) + { + try + { createLogFileStream(append); - } catch (Exception ex) { - if (FileLogPrv.HandleFileError!=null) { + } + catch (Exception ex) + { + if (FileLogPrv.HandleFileError != null) + { var fileErr = new FileError(LogFileName, ex); FileLogPrv.HandleFileError(fileErr); - if (fileErr.NewLogFileName!=null) { + if (fileErr.NewLogFileName != null) + { UseNewLogFile(fileErr.NewLogFileName); } - } else { + } + else + { throw; // do not handle by default to preserve backward compatibility } } } - string GetNextFileLogName() { + string GetNextFileLogName() + { var baseLogFileName = GetBaseLogFileName(); // if file does not exist or file size limit is not reached - do not add rolling file index - if (!System.IO.File.Exists(baseLogFileName) || - FileLogPrv.FileSizeLimitBytes <= 0 || - new System.IO.FileInfo(baseLogFileName).Length< FileLogPrv.FileSizeLimitBytes) + if (!System.IO.File.Exists(baseLogFileName) || + FileLogPrv.FileSizeLimitBytes <= 0 || + new System.IO.FileInfo(baseLogFileName).Length < FileLogPrv.FileSizeLimitBytes) return baseLogFileName; - int currentFileIndex = 0; - var baseFileNameOnly = Path.GetFileNameWithoutExtension(baseLogFileName); - var currentFileNameOnly = Path.GetFileNameWithoutExtension(LogFileName); - - var suffix = currentFileNameOnly.Substring(baseFileNameOnly.Length); - if (suffix.Length>0 && Int32.TryParse(suffix, out var parsedIndex)) { - currentFileIndex = parsedIndex; + if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Rolling) + { + //Unchanged default handling just optimized for performance and code reuse + int currentFileIndex = GetIndexFromFile(baseLogFileName, LogFileName); + var nextFileIndex = currentFileIndex + 1; + if (FileLogPrv.MaxRollingFiles > 0) + { + nextFileIndex %= FileLogPrv.MaxRollingFiles; + } + return GetFileFromIndex(baseLogFileName, nextFileIndex); } - var nextFileIndex = currentFileIndex + 1; - if (FileLogPrv.MaxRollingFiles > 0) { - nextFileIndex %= FileLogPrv.MaxRollingFiles; + else if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.RollingStableBase) + { + //Move current base file to next rolling file number + RollingNumber++; + if (FileLogPrv.MaxRollingFiles > 0) + { + RollingNumber %= FileLogPrv.MaxRollingFiles - 1; + } + System.IO.File.Move(baseLogFileName, GetFileFromIndex(baseLogFileName, RollingNumber + 1)); + return baseLogFileName; } - - var nextFileName = baseFileNameOnly + (nextFileIndex>0 ? nextFileIndex.ToString() : "") + Path.GetExtension(baseLogFileName); - return Path.Combine(Path.GetDirectoryName(baseLogFileName), nextFileName ); + else if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Unix) + { + //Move all existing files to index +1 except if they are > MaxRollingFiles + var logFiles = GetExistingLogFiles(baseLogFileName); + if (logFiles.Length > 0) + { + foreach (var finfo in logFiles.OrderByDescending(fInfo => fInfo.Name)) + { + var index = GetIndexFromFile(baseLogFileName, finfo.Name); + if (FileLogPrv.MaxRollingFiles > 0 && index < FileLogPrv.MaxRollingFiles - 1) + { + continue; + } + System.IO.File.Move(finfo.Name, GetFileFromIndex(baseLogFileName, index + 1)); + } + } + return baseLogFileName; + } + throw new NotImplementedException("RollingFilesConvention"); } // cache last returned base log file name to avoid excessive checks in CheckForNewLogFile.isBaseFileNameChanged string __LastBaseLogFileName = null; - void CheckForNewLogFile() { + void CheckForNewLogFile() + { bool openNewFile = false; if (isMaxFileSizeThresholdReached() || isBaseFileNameChanged()) openNewFile = true; - if (openNewFile) { + if (openNewFile) + { Close(); LogFileName = GetNextFileLogName(); OpenFile(false); } - bool isMaxFileSizeThresholdReached() { + bool isMaxFileSizeThresholdReached() + { return FileLogPrv.FileSizeLimitBytes > 0 && LogFileStream.Length > FileLogPrv.FileSizeLimitBytes; } - bool isBaseFileNameChanged() { - if (FileLogPrv.FormatLogFileName!=null) { + bool isBaseFileNameChanged() + { + if (FileLogPrv.FormatLogFileName != null) + { var baseLogFileName = GetBaseLogFileName(); - if (baseLogFileName!=__LastBaseLogFileName) { + if (baseLogFileName != __LastBaseLogFileName) + { __LastBaseLogFileName = baseLogFileName; return true; } @@ -303,8 +394,10 @@ bool isBaseFileNameChanged() { } } - internal void WriteMessage(string message, bool flush) { - if (LogFileWriter != null) { + internal void WriteMessage(string message, bool flush) + { + if (LogFileWriter != null) + { CheckForNewLogFile(); LogFileWriter.WriteLine(message); if (flush) @@ -312,8 +405,54 @@ internal void WriteMessage(string message, bool flush) { } } - internal void Close() { - if (LogFileWriter!=null) { + /// + /// Returns the index of a file or 0 if none found + /// + private int GetIndexFromFile(string baseLogFileName, string filename) + { +#if NETSTANDARD + var baseFileNameOnly = Path.GetFileNameWithoutExtension(baseLogFileName); + var currentFileNameOnly = Path.GetFileNameWithoutExtension(filename); + + var suffix = currentFileNameOnly.Substring(baseFileNameOnly.Length); +#else + var baseFileNameOnly = Path.GetFileNameWithoutExtension(baseLogFileName.AsSpan()); + var currentFileNameOnly = Path.GetFileNameWithoutExtension(filename.AsSpan()); + + var suffix = currentFileNameOnly.Slice(baseFileNameOnly.Length); +#endif + if (suffix.Length > 0 && Int32.TryParse(suffix, out var parsedIndex)) + { + return parsedIndex; + } + return 0; + } + + private string GetFileFromIndex(string baseLogFileName, int index) + { +#if NETSTANDARD + var nextFileName = Path.GetFileNameWithoutExtension(baseLogFileName) + (index > 0 ? index.ToString() : "") + Path.GetExtension(baseLogFileName); + return Path.Combine(Path.GetDirectoryName(baseLogFileName), nextFileName); +#else + var nextFileName = string.Concat(Path.GetFileNameWithoutExtension(baseLogFileName.AsSpan()), index > 0 ? index.ToString() : "", Path.GetExtension(baseLogFileName.AsSpan())); + return string.Concat(Path.Join(Path.GetDirectoryName(baseLogFileName.AsSpan()), nextFileName.AsSpan())); +#endif + } + + FileInfo[] GetExistingLogFiles(string baseLogFileName) + { + var logFileMask = Path.GetFileNameWithoutExtension(baseLogFileName) + "*" + Path.GetExtension(baseLogFileName); + var logDirName = Path.GetDirectoryName(baseLogFileName); + if (String.IsNullOrEmpty(logDirName)) + logDirName = Directory.GetCurrentDirectory(); + var logdir = new DirectoryInfo(logDirName); + return logdir.Exists ? logdir.GetFiles(logFileMask, SearchOption.TopDirectoryOnly) : Array.Empty(); + } + + internal void Close() + { + if (LogFileWriter != null) + { var logWriter = LogFileWriter; LogFileWriter = null; @@ -328,7 +467,8 @@ internal void Close() { /// /// Represents a file error context. /// - public class FileError { + public class FileError + { /// /// Exception that occurs on the file operation. @@ -340,7 +480,8 @@ public class FileError { /// public string LogFileName { get; private set; } - internal FileError(string logFileName, Exception ex) { + internal FileError(string logFileName, Exception ex) + { LogFileName = logFileName; ErrorException = ex; } @@ -354,7 +495,8 @@ internal FileError(string logFileName, Exception ex) { /// If proposed file name also leads to a file error this will break a file logger: errors are not handled recursively. /// /// a new log file name - public void UseNewLogFileName(string newLogFileName) { + public void UseNewLogFileName(string newLogFileName) + { NewLogFileName = newLogFileName; } } From a1863eb9344b405ba24a6494d56cde921d499cfe Mon Sep 17 00:00:00 2001 From: Levi--G Date: Fri, 31 May 2024 15:06:07 +0200 Subject: [PATCH 2/4] Fix moving issues when overwriting --- src/NReco.Logging.File/FileLoggerProvider.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/NReco.Logging.File/FileLoggerProvider.cs b/src/NReco.Logging.File/FileLoggerProvider.cs index 893b146..b02ad2a 100644 --- a/src/NReco.Logging.File/FileLoggerProvider.cs +++ b/src/NReco.Logging.File/FileLoggerProvider.cs @@ -334,7 +334,12 @@ string GetNextFileLogName() { RollingNumber %= FileLogPrv.MaxRollingFiles - 1; } - System.IO.File.Move(baseLogFileName, GetFileFromIndex(baseLogFileName, RollingNumber + 1)); + var moveFile = GetFileFromIndex(baseLogFileName, RollingNumber + 1); + if (System.IO.File.Exists(moveFile)) + { + System.IO.File.Delete(moveFile); + } + System.IO.File.Move(baseLogFileName, moveFile); return baseLogFileName; } else if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Unix) @@ -346,11 +351,16 @@ string GetNextFileLogName() foreach (var finfo in logFiles.OrderByDescending(fInfo => fInfo.Name)) { var index = GetIndexFromFile(baseLogFileName, finfo.Name); - if (FileLogPrv.MaxRollingFiles > 0 && index < FileLogPrv.MaxRollingFiles - 1) + if (FileLogPrv.MaxRollingFiles > 0 && index >= FileLogPrv.MaxRollingFiles - 1) { continue; - } - System.IO.File.Move(finfo.Name, GetFileFromIndex(baseLogFileName, index + 1)); + } + var moveFile = GetFileFromIndex(baseLogFileName, index + 1); + if (System.IO.File.Exists(moveFile)) + { + System.IO.File.Delete(moveFile); + } + System.IO.File.Move(finfo.FullName, moveFile); } } return baseLogFileName; From 49eb57dc3e857ba0041a328c1fd1fa0071bc1ec6 Mon Sep 17 00:00:00 2001 From: Levi--G Date: Mon, 3 Jun 2024 14:09:36 +0200 Subject: [PATCH 3/4] Adjust naming Fix formatting Added editorconfig to prevent more formatting errors --- src/.editorconfig | 231 ++++++++++++++++++ src/NReco.Logging.File/FileLoggerOptions.cs | 16 +- src/NReco.Logging.File/FileLoggerProvider.cs | 237 +++++++------------ test/.editorconfig | 231 ++++++++++++++++++ 4 files changed, 551 insertions(+), 164 deletions(-) create mode 100644 src/.editorconfig create mode 100644 test/.editorconfig diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..f63f9d1 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,231 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = tab +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/src/NReco.Logging.File/FileLoggerOptions.cs b/src/NReco.Logging.File/FileLoggerOptions.cs index dc09d62..d579e6b 100644 --- a/src/NReco.Logging.File/FileLoggerOptions.cs +++ b/src/NReco.Logging.File/FileLoggerOptions.cs @@ -101,25 +101,25 @@ public class FileLoggerOptions /// /// Determines the naming convention and order of rolling files. /// - public FileRollingConvention RollingFilesConvention { get; set; } = FileRollingConvention.Rolling; + public FileRollingConvention RollingFilesConvention { get; set; } = FileRollingConvention.Ascending; /// - /// Holds the different file rolling convention, the default option being Rolling. + /// Holds the different file rolling convention, the default option being Ascending. /// public enum FileRollingConvention { /// - /// (Default) New files will get a rolling index, files get rolled after max 0-1-2-3-0-1-2-3. + /// (Default) New files will get an ascending rolling index, files get rolled after max 0-1-2-3-0-1-2-3. /// - Rolling, + Ascending, /// - /// New files will get a rolling index, but the latest file is always the file without index. More performant alt for Unix rolling. 0-1-2-3-1-2-3 + /// New files will get an ascending rolling index, but the latest file is always the file without index. More performant alt for descending rolling. 0-1-2-3-1-2-3 /// - RollingStableBase, + AscendingStableBase, /// - /// Unix like logging, the base will always be stable and contain the latest logs, new files will be incremented and renamed so the highest number is always the oldest. 0-1-2-3 + /// Unix like descending logging, the base will always be stable and contain the latest logs, new files will be incremented and renamed so the highest number is always the oldest. 0-1-2-3 /// - Unix + Descending } } diff --git a/src/NReco.Logging.File/FileLoggerProvider.cs b/src/NReco.Logging.File/FileLoggerProvider.cs index b02ad2a..ce4439d 100644 --- a/src/NReco.Logging.File/FileLoggerProvider.cs +++ b/src/NReco.Logging.File/FileLoggerProvider.cs @@ -23,15 +23,13 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; -namespace NReco.Logging.File -{ +namespace NReco.Logging.File { /// /// Generic file logger provider. /// [ProviderAlias("File")] - public class FileLoggerProvider : ILoggerProvider - { + public class FileLoggerProvider : ILoggerProvider { private string LogFileName; @@ -47,8 +45,7 @@ public class FileLoggerProvider : ILoggerProvider private long FileSizeLimitBytes => Options.FileSizeLimitBytes; private int MaxRollingFiles => Options.MaxRollingFiles; - public LogLevel MinLevel - { + public LogLevel MinLevel { get => Options.MinLevel; set { Options.MinLevel = value; } } @@ -56,8 +53,7 @@ public LogLevel MinLevel /// /// Gets or sets indication whether or not UTC timezone should be used to for timestamps in logging messages. Defaults to false. /// - public bool UseUtcTimestamp - { + public bool UseUtcTimestamp { get => Options.UseUtcTimestamp; set { Options.UseUtcTimestamp = value; } } @@ -65,8 +61,7 @@ public bool UseUtcTimestamp /// /// Custom formatter for log entry. /// - public Func FormatLogEntry - { + public Func FormatLogEntry { get => Options.FormatLogEntry; set { Options.FormatLogEntry = value; } } @@ -74,8 +69,7 @@ public Func FormatLogEntry /// /// Custom formatter for the log file name. /// - public Func FormatLogFileName - { + public Func FormatLogFileName { get => Options.FormatLogFileName; set { Options.FormatLogFileName = value; } } @@ -83,22 +77,18 @@ public Func FormatLogFileName /// /// Custom handler for file errors. /// - public Action HandleFileError - { + public Action HandleFileError { get => Options.HandleFileError; set { Options.HandleFileError = value; } } - public FileLoggerProvider(string fileName) : this(fileName, true) - { + public FileLoggerProvider(string fileName) : this(fileName, true) { } - public FileLoggerProvider(string fileName, bool append) : this(fileName, new FileLoggerOptions() { Append = append }) - { + public FileLoggerProvider(string fileName, bool append) : this(fileName, new FileLoggerOptions() { Append = append }) { } - public FileLoggerProvider(string fileName, FileLoggerOptions options) - { + public FileLoggerProvider(string fileName, FileLoggerOptions options) { Options = options; LogFileName = Environment.ExpandEnvironmentVariables(fileName); @@ -109,16 +99,13 @@ public FileLoggerProvider(string fileName, FileLoggerOptions options) TaskCreationOptions.LongRunning); } - public ILogger CreateLogger(string categoryName) - { + public ILogger CreateLogger(string categoryName) { return loggers.GetOrAdd(categoryName, CreateLoggerImplementation); } - public void Dispose() - { + public void Dispose() { entryQueue.CompleteAdding(); - try - { + try { processQueueTask.Wait(1500); // the same as in ConsoleLogger } catch (TaskCanceledException) { } @@ -128,17 +115,13 @@ public void Dispose() fWriter.Close(); } - private FileLogger CreateLoggerImplementation(string name) - { + private FileLogger CreateLoggerImplementation(string name) { return new FileLogger(name, this); } - internal void WriteEntry(string message) - { - if (!entryQueue.IsAddingCompleted) - { - try - { + internal void WriteEntry(string message) { + if (!entryQueue.IsAddingCompleted) { + try { entryQueue.Add(message); return; } @@ -146,42 +129,33 @@ internal void WriteEntry(string message) } // do nothing } - private void ProcessQueue() - { + private void ProcessQueue() { var writeMessageFailed = false; - foreach (var message in entryQueue.GetConsumingEnumerable()) - { - try - { + foreach (var message in entryQueue.GetConsumingEnumerable()) { + try { if (!writeMessageFailed) fWriter.WriteMessage(message, entryQueue.Count == 0); } - catch (Exception ex) - { + catch (Exception ex) { // something goes wrong. App's code can handle it if 'HandleFileError' is provided var stopLogging = true; - if (HandleFileError != null) - { + if (HandleFileError != null) { var fileErr = new FileError(LogFileName, ex); - try - { + try { HandleFileError(fileErr); - if (fileErr.NewLogFileName != null) - { + if (fileErr.NewLogFileName != null) { fWriter.UseNewLogFile(fileErr.NewLogFileName); // write failed message to a new log file fWriter.WriteMessage(message, entryQueue.Count == 0); stopLogging = false; } } - catch - { + catch { // exception is possible in HandleFileError or if proposed file name cannot be used // let's ignore it in that case -> file logger will stop processing log messages } } - if (stopLogging) - { + if (stopLogging) { // Stop processing log messages since they cannot be written to a log file entryQueue.CompleteAdding(); writeMessageFailed = true; @@ -190,14 +164,12 @@ private void ProcessQueue() } } - private static void ProcessQueue(object state) - { + private static void ProcessQueue(object state) { var fileLogger = (FileLoggerProvider)state; fileLogger.ProcessQueue(); } - internal class FileWriter - { + internal class FileWriter { readonly FileLoggerProvider FileLogPrv; string LogFileName; @@ -205,109 +177,89 @@ internal class FileWriter Stream LogFileStream; TextWriter LogFileWriter; - internal FileWriter(FileLoggerProvider fileLogPrv) - { + internal FileWriter(FileLoggerProvider fileLogPrv) { FileLogPrv = fileLogPrv; DetermineLastFileLogName(); OpenFile(FileLogPrv.Append); } - string GetBaseLogFileName() - { + string GetBaseLogFileName() { var fName = FileLogPrv.LogFileName; if (FileLogPrv.FormatLogFileName != null) fName = FileLogPrv.FormatLogFileName(fName); return fName; } - void DetermineLastFileLogName() - { + void DetermineLastFileLogName() { var baseLogFileName = GetBaseLogFileName(); __LastBaseLogFileName = baseLogFileName; - if (FileLogPrv.FileSizeLimitBytes > 0) - { + if (FileLogPrv.FileSizeLimitBytes > 0) { // rolling file is used - if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Rolling) - { + if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Ascending) { var logFiles = GetExistingLogFiles(baseLogFileName); - if (logFiles.Length > 0) - { + if (logFiles.Length > 0) { var lastFileInfo = logFiles .OrderByDescending(fInfo => fInfo.Name) .OrderByDescending(fInfo => fInfo.LastWriteTime).First(); LogFileName = lastFileInfo.FullName; } - else - { + else { // no files yet, use default name LogFileName = baseLogFileName; } } - else - { + else { LogFileName = baseLogFileName; } } - else - { + else { LogFileName = baseLogFileName; } } - void createLogFileStream(bool append) - { + void createLogFileStream(bool append) { var fileInfo = new FileInfo(LogFileName); // Directory.Create will check if the directory already exists, // so there is no need for a "manual" check first. fileInfo.Directory.Create(); LogFileStream = new FileStream(LogFileName, FileMode.OpenOrCreate, FileAccess.Write); - if (append) - { + if (append) { LogFileStream.Seek(0, SeekOrigin.End); } - else - { + else { LogFileStream.SetLength(0); // clear the file } LogFileWriter = new StreamWriter(LogFileStream); } - internal void UseNewLogFile(string newLogFileName) - { + internal void UseNewLogFile(string newLogFileName) { FileLogPrv.LogFileName = newLogFileName; DetermineLastFileLogName(); // preserve all existing logic related to 'FormatLogFileName' and rolling files createLogFileStream(FileLogPrv.Append); // if file error occurs here it is not handled by 'HandleFileError' recursively } - void OpenFile(bool append) - { - try - { + void OpenFile(bool append) { + try { createLogFileStream(append); } - catch (Exception ex) - { - if (FileLogPrv.HandleFileError != null) - { + catch (Exception ex) { + if (FileLogPrv.HandleFileError != null) { var fileErr = new FileError(LogFileName, ex); FileLogPrv.HandleFileError(fileErr); - if (fileErr.NewLogFileName != null) - { + if (fileErr.NewLogFileName != null) { UseNewLogFile(fileErr.NewLogFileName); } } - else - { + else { throw; // do not handle by default to preserve backward compatibility } } } - string GetNextFileLogName() - { + string GetNextFileLogName() { var baseLogFileName = GetBaseLogFileName(); // if file does not exist or file size limit is not reached - do not add rolling file index if (!System.IO.File.Exists(baseLogFileName) || @@ -315,52 +267,42 @@ string GetNextFileLogName() new System.IO.FileInfo(baseLogFileName).Length < FileLogPrv.FileSizeLimitBytes) return baseLogFileName; - if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Rolling) - { + if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Ascending) { //Unchanged default handling just optimized for performance and code reuse int currentFileIndex = GetIndexFromFile(baseLogFileName, LogFileName); var nextFileIndex = currentFileIndex + 1; - if (FileLogPrv.MaxRollingFiles > 0) - { + if (FileLogPrv.MaxRollingFiles > 0) { nextFileIndex %= FileLogPrv.MaxRollingFiles; } return GetFileFromIndex(baseLogFileName, nextFileIndex); } - else if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.RollingStableBase) - { + else if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.AscendingStableBase) { //Move current base file to next rolling file number RollingNumber++; - if (FileLogPrv.MaxRollingFiles > 0) - { + if (FileLogPrv.MaxRollingFiles > 0) { RollingNumber %= FileLogPrv.MaxRollingFiles - 1; } var moveFile = GetFileFromIndex(baseLogFileName, RollingNumber + 1); - if (System.IO.File.Exists(moveFile)) - { + if (System.IO.File.Exists(moveFile)) { System.IO.File.Delete(moveFile); } - System.IO.File.Move(baseLogFileName, moveFile); + System.IO.File.Move(baseLogFileName, moveFile); return baseLogFileName; } - else if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Unix) - { + else if (FileLogPrv.Options.RollingFilesConvention == FileLoggerOptions.FileRollingConvention.Descending) { //Move all existing files to index +1 except if they are > MaxRollingFiles var logFiles = GetExistingLogFiles(baseLogFileName); - if (logFiles.Length > 0) - { - foreach (var finfo in logFiles.OrderByDescending(fInfo => fInfo.Name)) - { + if (logFiles.Length > 0) { + foreach (var finfo in logFiles.OrderByDescending(fInfo => fInfo.Name)) { var index = GetIndexFromFile(baseLogFileName, finfo.Name); - if (FileLogPrv.MaxRollingFiles > 0 && index >= FileLogPrv.MaxRollingFiles - 1) - { + if (FileLogPrv.MaxRollingFiles > 0 && index >= FileLogPrv.MaxRollingFiles - 1) { continue; - } - var moveFile = GetFileFromIndex(baseLogFileName, index + 1); - if (System.IO.File.Exists(moveFile)) - { - System.IO.File.Delete(moveFile); - } - System.IO.File.Move(finfo.FullName, moveFile); + } + var moveFile = GetFileFromIndex(baseLogFileName, index + 1); + if (System.IO.File.Exists(moveFile)) { + System.IO.File.Delete(moveFile); + } + System.IO.File.Move(finfo.FullName, moveFile); } } return baseLogFileName; @@ -371,30 +313,24 @@ string GetNextFileLogName() // cache last returned base log file name to avoid excessive checks in CheckForNewLogFile.isBaseFileNameChanged string __LastBaseLogFileName = null; - void CheckForNewLogFile() - { + void CheckForNewLogFile() { bool openNewFile = false; if (isMaxFileSizeThresholdReached() || isBaseFileNameChanged()) openNewFile = true; - if (openNewFile) - { + if (openNewFile) { Close(); LogFileName = GetNextFileLogName(); OpenFile(false); } - bool isMaxFileSizeThresholdReached() - { + bool isMaxFileSizeThresholdReached() { return FileLogPrv.FileSizeLimitBytes > 0 && LogFileStream.Length > FileLogPrv.FileSizeLimitBytes; } - bool isBaseFileNameChanged() - { - if (FileLogPrv.FormatLogFileName != null) - { + bool isBaseFileNameChanged() { + if (FileLogPrv.FormatLogFileName != null) { var baseLogFileName = GetBaseLogFileName(); - if (baseLogFileName != __LastBaseLogFileName) - { + if (baseLogFileName != __LastBaseLogFileName) { __LastBaseLogFileName = baseLogFileName; return true; } @@ -404,10 +340,8 @@ bool isBaseFileNameChanged() } } - internal void WriteMessage(string message, bool flush) - { - if (LogFileWriter != null) - { + internal void WriteMessage(string message, bool flush) { + if (LogFileWriter != null) { CheckForNewLogFile(); LogFileWriter.WriteLine(message); if (flush) @@ -418,8 +352,7 @@ internal void WriteMessage(string message, bool flush) /// /// Returns the index of a file or 0 if none found /// - private int GetIndexFromFile(string baseLogFileName, string filename) - { + private int GetIndexFromFile(string baseLogFileName, string filename) { #if NETSTANDARD var baseFileNameOnly = Path.GetFileNameWithoutExtension(baseLogFileName); var currentFileNameOnly = Path.GetFileNameWithoutExtension(filename); @@ -431,15 +364,13 @@ private int GetIndexFromFile(string baseLogFileName, string filename) var suffix = currentFileNameOnly.Slice(baseFileNameOnly.Length); #endif - if (suffix.Length > 0 && Int32.TryParse(suffix, out var parsedIndex)) - { + if (suffix.Length > 0 && Int32.TryParse(suffix, out var parsedIndex)) { return parsedIndex; } return 0; } - private string GetFileFromIndex(string baseLogFileName, int index) - { + private string GetFileFromIndex(string baseLogFileName, int index) { #if NETSTANDARD var nextFileName = Path.GetFileNameWithoutExtension(baseLogFileName) + (index > 0 ? index.ToString() : "") + Path.GetExtension(baseLogFileName); return Path.Combine(Path.GetDirectoryName(baseLogFileName), nextFileName); @@ -449,8 +380,7 @@ private string GetFileFromIndex(string baseLogFileName, int index) #endif } - FileInfo[] GetExistingLogFiles(string baseLogFileName) - { + FileInfo[] GetExistingLogFiles(string baseLogFileName) { var logFileMask = Path.GetFileNameWithoutExtension(baseLogFileName) + "*" + Path.GetExtension(baseLogFileName); var logDirName = Path.GetDirectoryName(baseLogFileName); if (String.IsNullOrEmpty(logDirName)) @@ -459,10 +389,8 @@ FileInfo[] GetExistingLogFiles(string baseLogFileName) return logdir.Exists ? logdir.GetFiles(logFileMask, SearchOption.TopDirectoryOnly) : Array.Empty(); } - internal void Close() - { - if (LogFileWriter != null) - { + internal void Close() { + if (LogFileWriter != null) { var logWriter = LogFileWriter; LogFileWriter = null; @@ -477,8 +405,7 @@ internal void Close() /// /// Represents a file error context. /// - public class FileError - { + public class FileError { /// /// Exception that occurs on the file operation. @@ -490,8 +417,7 @@ public class FileError /// public string LogFileName { get; private set; } - internal FileError(string logFileName, Exception ex) - { + internal FileError(string logFileName, Exception ex) { LogFileName = logFileName; ErrorException = ex; } @@ -505,8 +431,7 @@ internal FileError(string logFileName, Exception ex) /// If proposed file name also leads to a file error this will break a file logger: errors are not handled recursively. /// /// a new log file name - public void UseNewLogFileName(string newLogFileName) - { + public void UseNewLogFileName(string newLogFileName) { NewLogFileName = newLogFileName; } } diff --git a/test/.editorconfig b/test/.editorconfig new file mode 100644 index 0000000..f63f9d1 --- /dev/null +++ b/test/.editorconfig @@ -0,0 +1,231 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = tab +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case From 254e458f872f38e60a1c4443918ae7c5c8826415 Mon Sep 17 00:00:00 2001 From: Levi--G Date: Mon, 3 Jun 2024 14:12:55 +0200 Subject: [PATCH 4/4] Added tests for new RollingConventions --- test/NReco.Logging.Tests/FileProviderTests.cs | 119 +++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/test/NReco.Logging.Tests/FileProviderTests.cs b/test/NReco.Logging.Tests/FileProviderTests.cs index 005b34b..c1a0742 100644 --- a/test/NReco.Logging.Tests/FileProviderTests.cs +++ b/test/NReco.Logging.Tests/FileProviderTests.cs @@ -7,6 +7,7 @@ using System.Threading; using System.IO; using System.Runtime.InteropServices; +using System.Linq; namespace NReco.Logging.Tests { @@ -79,7 +80,7 @@ public void WriteToFileAndAppend() { } [Fact] - public void WriteRollingFile() { + public void WriteAscendingRollingFile() { var tmpFileDir = Path.GetTempFileName(); // for test debug: "./" System.IO.File.Delete(tmpFileDir); @@ -126,7 +127,121 @@ void createFactoryAndTestLogger() { logger = factory.CreateLogger("TEST"); } - } finally { + } + finally { + Directory.Delete(tmpFileDir, true); + } + } + + [Fact] + public void WriteAscendingStableBaseRollingFile() { + var tmpFileDir = Path.GetTempFileName(); // for test debug: "./" + System.IO.File.Delete(tmpFileDir); + + Directory.CreateDirectory(tmpFileDir); + try { + var logFile = Path.Combine(tmpFileDir, "test.log"); + + LoggerFactory factory = null; + ILogger logger = null; + createFactoryAndTestLogger(); + + for (int i = 0; i < 400; i++) { + logger.LogInformation("TEST 0123456789"); + if (i % 50 == 0) { + System.Threading.Thread.Sleep(20); // give some time for log writer to handle the queue + } + } + factory.Dispose(); + + // check how many files are created, Stablebase will always write to test.log + Assert.Equal(4, Directory.GetFiles(tmpFileDir, "test*.log").Length); + var lastFileSize = new FileInfo(Path.Combine(tmpFileDir, "test.log")).Length; + + // create new factory and continue + createFactoryAndTestLogger(); + logger.LogInformation("TEST 0123456789"); + factory.Dispose(); + Assert.True(new FileInfo(Path.Combine(tmpFileDir, "test.log")).Length > lastFileSize); + + // add many entries and ensure that there are only 5 log files + createFactoryAndTestLogger(); + for (int i = 0; i < 1000; i++) { + logger.LogInformation("TEST 0123456789"); + } + factory.Dispose(); + Assert.Equal(5, Directory.GetFiles(tmpFileDir, "test*.log").Length); + + void createFactoryAndTestLogger() { + factory = new LoggerFactory(); + factory.AddProvider(new FileLoggerProvider(logFile, new FileLoggerOptions() { + FileSizeLimitBytes = 1024 * 8, + MaxRollingFiles = 5, + RollingFilesConvention = FileLoggerOptions.FileRollingConvention.AscendingStableBase + })); + logger = factory.CreateLogger("TEST"); + } + + } + finally { + Directory.Delete(tmpFileDir, true); + } + } + + [Fact] + public void WriteDescendingRollingFile() { + var tmpFileDir = Path.GetTempFileName(); // for test debug: "./" + System.IO.File.Delete(tmpFileDir); + + Directory.CreateDirectory(tmpFileDir); + try { + var logFile = Path.Combine(tmpFileDir, "test.log"); + + LoggerFactory factory = null; + ILogger logger = null; + createFactoryAndTestLogger(); + + for (int i = 0; i < 400; i++) { + logger.LogInformation("TEST 0123456789"); + if (i % 50 == 0) { + System.Threading.Thread.Sleep(20); // give some time for log writer to handle the queue + } + } + factory.Dispose(); + + // check how many files are created, Descending will always write to test.log + Assert.Equal(4, Directory.GetFiles(tmpFileDir, "test*.log").Length); + var lastFileSize = new FileInfo(Path.Combine(tmpFileDir, "test.log")).Length; + + // create new factory and continue + createFactoryAndTestLogger(); + logger.LogInformation("TEST 0123456789"); + factory.Dispose(); + Assert.True(new FileInfo(Path.Combine(tmpFileDir, "test.log")).Length > lastFileSize); + + // add many entries and ensure that there are only 5 log files + createFactoryAndTestLogger(); + for (int i = 0; i < 1000; i++) { + logger.LogInformation("TEST 0123456789"); + } + factory.Dispose(); + Assert.Equal(5, Directory.GetFiles(tmpFileDir, "test*.log").Length); + + //check last time written in order + Assert.Equal(new[] { "test4.log", "test3.log", "test2.log", "test1.log", "test.log" }, new DirectoryInfo(tmpFileDir).GetFiles("test*.log").OrderBy(f => f.LastWriteTimeUtc).Select(f => f.Name).ToArray()); + + void createFactoryAndTestLogger() { + factory = new LoggerFactory(); + factory.AddProvider(new FileLoggerProvider(logFile, new FileLoggerOptions() { + FileSizeLimitBytes = 1024 * 8, + MaxRollingFiles = 5, + RollingFilesConvention = FileLoggerOptions.FileRollingConvention.Descending + })); + logger = factory.CreateLogger("TEST"); + } + + } + finally { Directory.Delete(tmpFileDir, true); } }